Lazarus在Linux上使用自定义动态库

前置知识 Windows上叫动态链接库,通常以*.dll形式命名;Linux上叫共享库,通常以lib*.so形式命名。(此处统一叫动态库) Windows上dll路径的一般搜索顺序为:当前目录->系统目录(如:C:\Windows\System32、C:\Windows\SysWOW64)->Windows目录(如C:\Windows)->PATH环境变量指定的目录。 Linux上so路径的一般搜索顺序为:编译时使用-rpath指定的路径->LD_LIBRARY_PATH环境变量指定的路径->系统默认库路径(如/lib、/usr/lib等)->/etc/ld.so.conf和/etc/ld.so.conf.d/目录中配置的路径。 Lazarus/Delphi调用动态库有两种形式:静态调用和动态调用。 静态调用:主程序启动时加载,若动态库不存在或不匹配,则主程序抛异常并中止;主程序退出时卸载。 动态调用:主程序在需要时可随时加载,不需要时可随时卸载;动态库发生异常时,一般不会导致主程序中止。 使用动态库 动态库 动态库本身不需要特殊设置,正常编译、构建即可。(以名为dll的动态库为例,实际文件名为libdll.so) 主程序 编译时 静态调用方式 主程序直接编译会报错:Warning: linker:/usr/bin/ld: cannot find -ldll: No such file or directory,意思是链接器找不到名为dll的动态库文件。 解决方案 打开Project Options->Compiler Options->Compilation and Linking,勾选Pass options to linker with "-k", delimiter is space,并在下方填入-L’动态库所在路径’,保存即可正常编译。 动态调用方式 主程序直接编译即可。 运行时 静态调用方式 此时编译后的程序并不能正常运行,会报错error while loading shared libraries: libdll.so: cannot open shared object file: No such file or directory,意思是加载动态库时找不到名为libdll.so的动态库文件。 这是前面提到的路径搜索顺序导致的,可将动态库放入相应的搜索路径下,也可使用-rpath指定路径。对于自定的动态库,建议使用-rpath指定路径,最好指定的路径为当前路径(与Windows保持一致)。 解决方案 打开Project Options->Compiler Options->Compilation and Linking,勾选Pass options to linker with "-k", delimiter is space,并在下方填入-rpath='$ORIGIN'(与之前的-L’动态库所在路径’之间要添加一个空格),保存并重新编译,即可正常运行。...

2025-02-20 21:51:24 · 1 分钟 · 慢步道人

Lazarus的dll卸载问题

从其它平台迁移而来 事件 计划做一个插件式的桌面应用框架,一方面练练手,另一方面算是自身的技术积累吧。 在练手过程中,发现一个巨难受的问题,dll卸载不掉,程序直接假死! 虽然可以不直接调用卸载,依赖主程序退出时卸载的特性,但做为插件式应用,必须能在运行中加载/卸载才算完整。即便不是插件式应用,dll的正常卸载也应该是很常用的功能,现在不正常,那么一定是代码写得有问题。 写了测试Demo,一行一行加代码,结果都能正常卸载,这就杯具了……测试了无数次,直到想把每一步都输出到日志时,dll无法卸载了。一点点分析源码后,发现很可能是日志中为了方便使用,加的initialization节和finalization节导致的,注释掉之后就真的正常了。 分析 initialization节应该是在Application.Initialize;时执行的,finalization节应该是在Application.Terminate;之后的某个时间点执行的(具体执行时机没深入研究过)。而我的dll是要做成插件的,不可避免会有可视化窗体,所以Application.Initialize;不可避免(Lazarus是这样,Delphi不是),而在initialization节中创建了日志记录器实例,在finalization节中进行释放,这样,在卸载dll时就出现了锁死的情况:卸载时有内存(实例)未释放,需要等内存释放了才能完全卸载,而未卸载又导致执行不到finalization节,就不能释放实例……于是,dll无法卸载,程序就进入假死状态。 以上只是初步分析,鉴于对底层机制了解不深,可能分析不完全正确,甚至是错误的。 结论 在编写的dll中不要在initialization节和finalization节中进行内存管理的工作。 对于无对象、纯函数式的dll,initialization节和finalization节会不会产生影响尚未测试,目前也暂无这方面需求,待以后遇到了再详细测试吧。

2022-12-10 22:26:44 · 1 分钟 · 慢步道人

Lazarus编写dll与接口注意事项小结

从其它平台迁移而来 之前用lazarus编写了使用IInterface的dll,可惜没有成功。当把IInterface编译到exe里时,功能正常,编译到dll里再在exe里调用就不正常,原因未深究,不过大致也知道是哪一类问题,至于还有没有其它问题,暂未可知。 闲来有空,有写了点Demo来,有了不少新发现,在此记录下: 只有使用exports导出的函数才能在dll外部调用 只有使用stdcall修饰的函数传参规则才与标准C的传参规则相同,其它遵守标准C传参规则的语言可以正常调用;否则,只有lazarus编写的程序可以正常调用 入参为string类型时,无论是否使用stdcall修饰,lazarus编写的程序调用正常,其它语言未测试 返回值或出参为string类型时,调用报External: ACCESS VIOLATION错误;但参数为PChar时,调用正常 入参/出参/返回值为结构体时,调用正常 string类型传参是指针/引用拷贝,结构体传参是值拷贝 string做入参时,由主调函数分配内存并增加引用计数,被调函数执行时再次增加引用计数,被调函数结束时减少引用计数,此时引用计数不为0不释放内存,主调函数结束时再次减少引用计数,引用计数为0释放内存,该内存由exe分配,因此释放不报错;做出参时,由被调函数执行时分配内存并增加引用计数,被调函数返回时先赋值给主调函数的变量,增加引用计数,再结束被调函数,减少引用计数,主调函数结束时再次减少引用计数,此时引用计数为0释放内存,但该内存是由dll分配,因此产生External: ACCESS VIOLATION错误 string类型不适合在dll和exe之间传参(尤其是出参) 返回值为对象时,调用异常;入参/出参为exe创建的对象时,调用正常 结论 根据测试得出的结论,可能不严谨: 指针做为形参(入参/出参)传递没有问题,但必须遵守谁创建谁释放的原则;指针做为返回值可能会有隐患 对象传参实际传的是对象的指针,规则与指针相同 返回值适合传递值拷贝的类型,如整型、浮点型、布尔型、结构体、指针(但指针指向的内存要遵守谁创建谁释放的原则,不过一般不直接使用)等 字符串、结构体的生命周期是由编译器维护的,使用需慎重

2022-09-13 20:40:27 · 1 分钟 · 慢步道人