ctypes里的bug?

从其它平台迁移而来 起因 以前使用Delphi调用海康SDK时,专门改写过HCNetSDK.h,当时大部分桌面应用还都是32位的,毕竟64位还没彻底普及开(即便现在,还是有一部分桌面考虑兼容性依然是32位)。后来也搞过64位版的,编译没问题,运行就不成功。虽然没成功,但心里还是清楚这基本上是数据类型的问题,由于对64位了解不多,也就一直搁置着。 转Lazarus之后,又搞过一次64位版,还是没成功。后来知道有ctypes这个单元,也知道这是专门针对c语言数据类型的,但一直没去看过。近来又想起这个事,就想一探究竟。 探 直接看源码,其实就是给pascal的数据类型取了个c的别名。要想了解透彻,自已撸码跑一下还是很有必要的: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 program test; uses SysUtils, ctypes; begin writeln(Format('%-16s%s', ['type', 'size'])); writeln('--------------------'); writeln(Format('%-16s%d', ['cint8', SizeOf(cint8)])); writeln(Format('%-16s%d', ['cuint8', SizeOf(cuint8)])); writeln(Format('%-16s%d', ['cchar', SizeOf(cchar)])); writeln(Format('%-16s%d', ['cschar', SizeOf(cschar)])); writeln(Format('%-16s%d', ['cuchar', SizeOf(cuchar)])); writeln(Format('%-16s%d', ['cint16', SizeOf(cint16)])); writeln(Format('%-16s%d', ['cuint16', SizeOf(cuint16)])); writeln(Format('%-16s%d', ['cshort', SizeOf(cshort)])); writeln(Format('%-16s%d', ['csshort', SizeOf(csshort)])); writeln(Format('%-16s%d', ['cushort', SizeOf(cushort)])); writeln(Format('%-16s%d', ['cint32', SizeOf(cint32)])); writeln(Format('%-16s%d', ['cuint32', SizeOf(cuint32)])); writeln(Format('%-16s%d', ['cint64', SizeOf(cint64)])); writeln(Format('%-16s%d', ['cuint64', SizeOf(cuint64)])); writeln(Format('%-16s%d', ['clonglong', SizeOf(clonglong)])); writeln(Format('%-16s%d', ['cslonglong', SizeOf(cslonglong)])); writeln(Format('%-16s%d', ['culonglong', SizeOf(culonglong)])); writeln(Format('%-16s%d', ['cbool', SizeOf(cbool)])); writeln(Format('%-16s%d', ['cint', SizeOf(cint)])); writeln(Format('%-16s%d', ['csint', SizeOf(csint)])); writeln(Format('%-16s%d', ['cuint', SizeOf(cuint)])); writeln(Format('%-16s%d', ['clong', SizeOf(clong)])); writeln(Format('%-16s%d', ['cslong', SizeOf(cslong)])); writeln(Format('%-16s%d', ['culong', SizeOf(culong)])); writeln(Format('%-16s%d', ['csigned', SizeOf(csigned)])); writeln(Format('%-16s%d', ['cunsigned', SizeOf(cunsigned)])); writeln(Format('%-16s%d', ['csize_t', SizeOf(csize_t)])); writeln(Format('%-16s%d', ['cfloat', SizeOf(cfloat)])); writeln(Format('%-16s%d', ['cdouble', SizeOf(cdouble)])); writeln(Format('%-16s%d', ['clongdouble', SizeOf(clongdouble)])); Readln(); end....

2023-04-24 21:20:06 · 4 分钟 · 慢步道人

FPHTTPClient请求https

从其它平台迁移而来 在客户端不需要证书的情况下,简单两步操作即可让TFPHTTPClient实现https的请求: 在源码中引用opensslsockets单元 在生成目录下加入动态库libcrypto-1_1.dll和libssl-1_1.dll

2023-02-17 20:59:53 · 1 分钟 · 慢步道人

Lazarus连数据库的那点坑

从其它平台迁移而来 环境 Lazarus v2.2.4(32位/64位) MySQL 8.0.31 64位 PostgreSQL 15.1 64位 坑 MySQL Lazarus v2.2.4已内置MySQL8.0的TMySQL80Connection组件,但是连接时却提示Can not load MySQL library "libmysql.dll". Please check your installation.使用64位编译,放进64位MySQL8.0.31的libmysql.dll仍然报该错,一直到把版本降到5.7系列才正常,换32位编译,同样的结果:使用5.7系列的libmysql.dll可正常使用。 PostgreSQL 连接PostgreSQL时,同样遇到与MySQL类似的报错:Can not load PostgreSQL client library "libpq.dll". Check your installation.按照与MySQL相同的思路,版本从15.1一直降到目前支持的最低版本9.2.24,仍然报错! 查一下官方论坛,各种方式试了一通,发现64位的程序除libpq.dll外,v11+还需要libcrypto-3-x64.dll、libiconv-2.dll、libintl-9.dll、libssl-3-x64.dll和libwinpthread-1.dll;v9.4~v10.23还需要libcrypto-1_1-x64.dll、libiconv-2.dll、libintl-8.dll、libssl-1_1-x64.dll。 而32位程序,最高可用版本为v10.23,除libpq.dll外,还需要libcrypto-1_1.dll、libiconv-2.dll、libintl-8.dll、libssl-1_1.dll,必要时可能还需要VC运行时。

2022-12-24 22:03:38 · 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 debug的坑

从其它平台迁移而来 最近在研究Lazarus写dll,已经踩了不少坑了,这下又踩了个不大不小的坑,记录下。 问题 在dll工程里,断点失效,根本没办法调试 解决方案 网上查了N多资料,个中辛酸在此不表,终于找到些蛛丝马迹。 Lazarus在windows上默认使用的是FpDebug内置的Dwarf,这货本身就不支持在dll中调试,法了个克!切换为gdb,立马OK! 不过,据说gdb在windows上有bug,这……反正FpDebug的bug遇到了也不是一个两个了,先用着再说。

2022-11-20 23:44:48 · 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 分钟 · 慢步道人

*.frf报表中字段计算的坑

从其它平台迁移而来 老坑 *.frf是FastReport 2.x的报表模板,现维护的老项目中有使用到该报表。 问题描述 报表通过数据集DS和报表变量V1、V2、V3等获取数据,并在模板中进行展示。 单独展示数据集中的某个字段:[DS."D1"] 展示数据集中的某两个字段的运算结果:[[DS."D1"]+[DS."D2"]] 单独展示某个变量:[V1] 展示数据集中的某个字段与变量的运算结果:[[DS."D1"]+[V2]] 以上都是没有问题的,但当需要展示两个变量的运算结果时就不行了,[V1]+[V2]实际展示的是两个变量的字符串连接,因为报表变量本来就是字符串。查看模板其它写法,发现有使用尖括号<>的,尝试后也不行,[V1]+<V2>实际展示的只有V2的值。 由于报表变量本身就是字符串,想着通过数据类型转换来解决,但非常遗憾,报表没有提供把字符串转为数值的函数。 实在不行就只能考虑在代码中计算完之后再赋值给报表变量了,但这样就牺牲了报表的灵活性了,乃是没有办法的最后的办法了。 最后灵光一现,既然数据集字段与变量可以运算,那加个0是不是也可以运算?一试果然可行! 结果 展示某两个变量的运算结果:[0+[V1]+[V2]-[V3]] 问题解决了,不过总感觉是个偏方,不晓得正统的解决方案是什么。 不过话说回来,FastReport 2.x已经很古老了,除了老项目维护基本不可能会用到,因为现在连FastReport 3+用得都很少了,毕竟现在已经是什么乱七八糟的牛鬼蛇神都有的元宇宙时代了,能解决问题就好。

2022-08-31 23:40:11 · 1 分钟 · 慢步道人

Lazarus压缩/解压zip乱码问题

从其它平台迁移而来 Lazarus压缩/解压zip文件可以使用Zipper单元中的TZipper/TUnZipper类来实现,但是在有中文文件名时需要注意,否则会出现乱码。 压缩 TZipper的Zip类方法无需创建实例即可直接生成zip压缩文件。 TZipper实例的ZipFile方法是压缩一个指定的文件生成zip压缩文件,ZipFiles方法是压缩多个指定的文件生成zip压缩文件,UnZipAllFiles方法是配合Entries等属性生成zip压缩文件。 解压 TUnZipper的UnZip类方法无需创建实例即可直接解压zip文件。 TUnZipper实例的UnZipFile方法是解压出一个指定的文件,UnZipFiles方法是解压出多个指定的文件,UnZipAllFiles方法可以从zip文件中解压出所有文件。 乱码 无中文文件名的情况下,以上类方法和实例方法使用都是正常的,与其它压缩/解压工具交叉使用也不会出现问题。 当存在中文文件名时,成对使用以上压缩/解压方法,从结果上来说是没什么问题的,但与其它压缩/解压工具交叉使用时就会出现文件名乱码问题。 乱码其实还是字符编码的问题,Lazarus默认使用UTF8编码,windows默认使用OEM对应的编码,对于中文windows就是GBK编码,于是就出问题了。 解决方案 TZipper有UseLanguageEncoding属性,TUnZipper有UseUTF8属性,均设置为True,再进行压缩/解压即可,因此,类方法是肯定不行的了。 不过,这两个属性在语意上却十分让人费解,因为属性为False时,zip文件头里的文件名实际使用的是UTF8编码,而当属性为True时,zip文件头里的文件名实际使用的却是GBK编码,搞不懂这些老外的想法。 对了,好像要使用FPC 3.2.0+的编译器版本才可以。

2022-08-24 22:10:38 · 1 分钟 · 慢步道人

idhttp POST的坑

从其它平台迁移而来 坑 最近要和一个平台提供的http接口进行对接,由于服务不是部署在公网上的,所以没办法直接在开发环境下直接调试,又不能在客户的测试机上部署开发环境,只能选择加日志这种古老的方法。 在对接过程中却出现了莫名其妙的问题:POST的内容是一样的,但返回的内容却不一样,更准确点说是有的接口返回正常有的返回不正常(其实还是测试有限,测试多的话还会出现同一接口有的请求正常有的不正常)。 平台提供的word接口文档写得真的是……一言难尽,由此也对接口的准确性、可靠性等保持怀疑。不过,这次用我们提供的数据,平台方给出了相应的中间结果(中间结果一致)及返回结果,还有postman和curl的测试截图,充分证明接口以及数据请求是没有问题的。 在客户测试机上装了curl测试也证明接口没问题。(问为什么一开始没想到用curl?因为windows默认没有,而且utf8还会显示乱码,习惯性的就没想起来用) 请求头也改了,没用一样的效果。 最后实在找不出问题,就只能进行TCP抓包了,这一抓还就真抓出问题了: 原始数据 1 /knwRBV5D4Qtk7RzoQhtBUSMXQig3zURYmeSQBB17NVr6qddCkTxS7e0oO/qfNCkuX14vGsxEoLLN4KS5vtQWDs5on+GeAE5LadCZuHDCe7M25GitNKqnsCfC5qVmti1LyxLxYg2JpLP6GylWERWBOmbP2yO2Aiuey6V526rlRICnidM1W0A4ziNMSjGCng0n5Md4so/RjswNdcE4C5F0kBEGRWH0ZK+QdkZMs4zL84Pu/aiSMpiID9Mm+jtdLZuk0m0UtEd1MmxnU2KPdQz9057JAtM5LIj4YenZl3J86OPWtb9DE6a+410CWBawZY1vItZ+43Kh/QPnkw6Qe4toXRnF84X/ijtiWkTt4moJT87xDpf/TXEd7LRkddWPRRGF3KHnUKacLySwMmXkU6CQMazE1Kh1ZGML6zl1weaq4xVulFwhGfp2XKzYdZfT3uq4yqVr30AaEqYoy2/P1MkhTs5on+GeAE54r939gdklpefguipLJVBpWk/B5S9QsCDxDbfGYNxJo3rIAxDL7sjjlmVL1RjU86sHsNIbmRii7dL70pf5qcIMJzryGmtn94SAE2HjLw0abfr0A9amRsdIzs5on+GeAE5RIxdCKDfNRFnPqLzNU5stMSDcpuafJpNhUXDbQOMlPungXJHHZAgbE2XtoI4geIRYjiyBLDMQuB0l5WvfDhpRnYo1VaEu7J0tRuezwUplK61p4I8giLwAtO+yNMRV+J65g9XaT8vmf8gxmtK3wEFrve7HwsYH0QhlQQiMilY/JVsiHZS6+ID/w== 发送后抓到的却是 1 /knwRBV5D4Qtk7RzoQhtBUSMXQig3zURYmeSQBB17NVr6qddCkTxS7e0oO/qfNCkuX14vGsxEoLLN4KS5vtQWDs5on+GeAE5LadCZuHDCe7M25GitNKqnsCfC5qVmti1LyxLxYg2JpLP6GylWERWBOmbP2yO2Aiuey6V526rlRICnidM1W0A4ziNMSjGCng0n5Md4so/RjswNdcE4C5F0kBEGRWH0ZK+QdkZMs4zL84Pu/aiSMpiID9Mm+jtdLZuk0m0UtEd1MmxnU2KPdQz9057JAtM5LIj4YenZl3J86OPWtb9DE6a+410CWBawZY1vItZ+43Kh/QPnkw6Qe4toXRnF84X/ijtiWkTt4moJT87xDpf/TXEd7LRkddWPRRGF3KHnUKacLySwMmXkU6CQMazE1Kh1ZGML6zl1weaq4xVulFwhGfp2XKzYdZfT3uq4yqVr30AaEqYoy2/P1MkhTs5on+GeAE54r939gdklpefguipLJVBpWk/B5S9QsCDxDbfGYNxJo3rIAxDL7sjjlmVL1RjU86sHsNIbmRii7dL70pf5qcIMJzryGmtn94SAE2HjLw0abfr0A9amRsdIzs5on+GeAE5RIxdCKDfNRFnPqLzNU5stMSDcpuafJpNhUXDbQOMlPungXJHHZAgbE2XtoI4geIRYjiyBLDMQuB0l5WvfDhpRnYo1VaEu7J0tRuezwUplK61p4I8giLwAtO+yNMRV+J65g9XaT8vmf8gxmtK3wEFrve7HwsYH0QhlQQiMilY/JVsiHZS6+ID/w=%3D 最后的=被转码为%3D了! 解决 找到原因就好办了 1 2 //idhttp.HTTPOptions := [hoForceEncodeParams]; //默认强制对参数进行转码 idhttp.HTTPOptions := []; //去掉就OK了

2022-07-12 21:47:37 · 1 分钟 · 慢步道人

Lazarus构造/析构等方法的执行顺序

从其它平台迁移而来 AfterConstruction、BeforeDestruction是TObject本身就有的方法,Loaded是从TComponent才有的方法,好好利用的话就可以更精准的控制对象的生命周期或者初始化/清理工作。虽然知道这点,而且也经常在自己的程序中使用,但还是会经常搞错执行顺序,因此专门记录一下以备忘备查。 Form graph TD f1[inherited Create 前] --> f2[inherited Loaded 前] --> f3[inherited Loaded 后] --> f4[inherited Create 后] --> f5[inherited AfterConstruction 前] --> f6[FormCreate] --> f7[inherited AfterConstruction 后] --> f8[FormResize] --> f9[FormShow] --> f10[FormCloseQuery] --> f11[FormClose] --> f12[inherited BeforeDestruction 前] --> f13[FormHide] --> f14[FormDestroy] --> f15[inherited BeforeDestruction 后] --> f16[inherited Destroy 前] --> f17[inherited Destroy 后] DataModule graph TD d1[inherited Create 前] --> d2[inherited Loaded 前] --> d3[inherited Loaded 后] --> d4[inherited Create 后] --> d5[inherited AfterConstruction 前] --> d6[DataModuleCreate] --> d7[inherited AfterConstruction 后] --> d8[inherited BeforeDestruction 前] --> d9[DataModuleDestroy] --> d10[inherited BeforeDestruction 后] --> d11[inherited Destroy 前] --> d12[inherited Destroy 后] Frame graph TD f1[inherited Create 前] --> f2[inherited Loaded 前] --> f3[inherited Loaded 后] --> f4[inherited Create 后] --> f5[inherited AfterConstruction 前] --> f6[inherited AfterConstruction 后] --> f7[FrameResize] --> f8[inherited BeforeDestruction 前] --> f9[inherited BeforeDestruction 后] --> f10[inherited Destroy 前] --> f11[inherited Destroy 后] 总结 Loaded是在Create的过程执行的,应该是用来做一些加载资源之类或其它辅助构造的工作...

2022-06-24 22:53:40 · 1 分钟 · 慢步道人