前言

先前就尝试过使用Lazarus写一个插件式的桌面应用框架,主程序只负责整体的插件管理以及为插件提供页签式的展示容器,具体的功能实现都由插件(动态库)来完成。

但是之前比较零散的几次尝试,多或少有些问题,比较有代表性的如:

  • 无法以模式窗口显示插件内的子窗口

  • 插件内的窗口在任务栏上显示的是和主程序分离的两个程序

  • 动态库无法卸载

  • 使用接口无法释放动态库里的对象

  • 无法跨平台(主要针对windows

近来又进行了一次系统性的尝试,以前的一些也针对性的研究了下,并找到了目前来看比较合适的解决方案,并将阶段性成果开源了,具体地址见文末。

旧问题解决

模式窗口

这个问题其实早在Lazarus官方的wikiForm in Dll)上就有解决方案了。

在此基础上也尝试进行一些魔改,过程就不多说了,经验总结如下:

  • DisableFormsCallBackEnableFormsCallback两个回调必须为普通过程,改为类方法会导致模式窗口失效

  • TApplicationCallback可以和插件动态库内的管理类合并,做为插件入口的统一管理,甚至做为插件统一接口的实现

视觉上为同一程序

Form in Dll中已经涉及,即在CreateParams中将Params.WndParent赋值为主程序中对应容器的句柄。

卸载/释放

对于动态库的卸载,在Lazarus的dll卸载问题中已有提到,本次直接避免。

对于对象的释放,这前的尝试犯了一个很严重的错误,即未遵守谁创建谁释放这一原则。接口中增加专门的释放函数,用于释放通过接口创建的对象。但,对于接口创建的对象,主程序除了释放外,也不应该(事实上也不能)进行其它操作,否则会抛内存访问异常。

共享内存管理器

如果仅使用基本数据类型的话,这就是个伪命题,但若使用高阶数据类型的话还是会方便很多,毕竟字符串其实并不算是真正的基本类型。

文章在Lazarus中使用ShareMem解决了在Windows平台上共享内存管理器的问题。在QQ群的交流中,群友啊D提出使用GetMemoryManagerSetMemoryManager,目前来看是能解决该问题的。

跨平台一致性

Lazarus本身就是跨平台的,只注意避免使用平台专用的api即可,或者对不同平台的api进行封装。

对于插件式的动态库,exports导出的函数统一使用Name关键字强制命名。同时,对于32位CPU,导出函数统一使用cdecl而不是stdcall进行传参约束;对于64位CPU,导出函数统一使用默认的传参约束,即不使用任何关键字,由编译器管理。

新的问题

  • 基于有限的测试,Linuxgtk2中显示模式窗口时主窗体还能进行最大化、最小化、移动等操作,但不能操作窗体内的元素,这与Windows上的行为表现不太一致

  • 插件动态库创建的窗体,嵌入主程序容器后,并不能像嵌入自身容器内那样可以方便的自适应大小和位置

TODO

  • 解决新发现的问题

  • 主程序页签式容器的实现

  • 主程序插件的管理

开源地址