我需要将一个函数调用从我的 Delphi 应用程序中协商到 Clarion 6.3 中提供的 DLL 中。我需要传递一个或两个字符串参数(一个带有两个参数的函数或两个单参数函数)。我们很快决定使用 1 字节的 0 结尾字符串(char*
用 C 术语、CSTRING
Clarion 术语、PAnsiChar
Delphi 术语),这就是事情变得有点不可预测和难以理解的地方。
我们得到的工作解决方案是传递伪装成 32 位整数的无类型指针,然后 Clarion 制造的 DLL 使用 Clarion 程序员称为“pick”或“peek”的东西来遍历内存。还有一些关于 Clarion 和 Visual Basic 之间互操作的论坛文章,其中解决了将字符串从 VB 传递到 Clarion 的问题,并且 Clarion 开发人员从我的肩膀后面瞥了一眼,说“我不需要它的副本,我已经知道了,它很典型”。
然而,从长远来看,这给我们带来了更多负担,因为低级无类型代码在样板文件上更加“丰富”并且容易出错。键入的代码会感觉更好的解决方案。
我在这里寻求的不是“这是复制粘贴并使事情不假思索地工作的模式”——我们已经有了它——更多的是理解,幕后发生的事情,以及我如何依赖它,以及我对 Clarion DLL 的期望。为了避免最终陷入“偶然工作”的解决方案。
当我从他的背后瞥了一眼 Clarion 6.3 的帮助时,这种帮助对低级细节没有帮助。这完全是关于从 Clarion 调用 DLL,而不是关于被调用。我的机器上也没有 Clarion,我不想,咳咳,借它。正如我被告知的那样,开发人员也无法使用 Clarion 6.3 运行时的源代码。
诸如 Clarion 和 VB 之间或 Clarion 和 C# 之间的互操作之类的文章没有帮助,因为它们融合了两种语言的特性,并且提供的关于“裸机”级别的信息更少。
谷歌图书指出“Clarion Tips & Techniques - David Harms”——它似乎对 Clarion 经验丰富的人有有趣的见解,但我是 Clarion 零。至少我无法从中找出支持互操作的低级细节。
有没有办法让 Clarion 6.3 为它制作的 DLL 保存“列表文件”,可能是标准的 *.H 头文件?
因此,重复一遍,正如预期的那样,一个在 Delphi 端传递指针的函数(procedure ...(const param1, param2: PAnsiChar); stdcall;
它应该转换为 Cstdcall void ...(char* p1, char* p2)
并且据称在 Clarion 中看起来像(LONG, LONG), LONG, pascal, RAW
.
该函数以相反的顺序从堆栈中获取两个 32 位参数,使用它们并退出,在 EAX 寄存器中传递返回值(实际上是未使用的垃圾)并从堆栈中清除参数。几乎完全stdcall
一样,除了它似乎出于某种模糊的原因保留了 EBX 寄存器。
号角功能入口:
04E5D38C 83EC04 sub esp,$04 ' allocate local vars
04E5D38F 53 push ebx ' ????????
04E5D390 8B44240C mov eax,[esp+$0c]
04E5D394 BBB4DDEB04 mov ebx,$04ebddb4
04E5D399 B907010000 mov ecx,$00000107
04E5D39E E889A1FBFF call $04e1752c ' clear off local vars before use
以及它的出口
00B8D500 8B442406 mov eax,[esp+$06] ' pick return value
00B8D504 5B pop ebx ' ????
00B8D505 83C41C add esp,$1c ' remove local vars
00B8D508 C20800 ret $0008 ' remove two 32-bits params from stack
除了我无法解释使用 EBX 操作并返回垃圾结果之外 - 它按预期工作。但是 - 需要 Clarion 源中的无类型低级操作。
现在据称只接受一个字符串参数的函数:在 Delphi 端 -procedure ...(const param1: PAnsiChar); stdcall;
它应该转换为 Cstdcall void ...(char* p1)
并且据称在 Clarion 中看起来像(*CSTRING), LONG, pascal, RAW
.
号角功能入口:
00B8D47C 83EC1C sub esp,$1c ' allocate local vars
00B8D47F 53 push ebx ' ????????
00B8D480 8D44240A lea eax,[esp+$0a]
00B8D484 BB16000000 mov ebx,$00000016
00B8D489 B990FEBD00 mov ecx,$00bdfe90
00B8D48E BA15000000 mov edx,$00000015
00B8D493 E82002FBFF call $00b3d6b8 ' clear off local vars before use
以及它的出口
04E5D492 8B442404 mov eax,[esp+$04] ' pick return value
04E5D496 5B pop ebx ' ????
04E5D497 83C404 add esp,$04 ' remove local vars
04E5D49A C20800 ret $0008 ' remove TWO 32-bits params from stack
这里令人震惊的是,该函数以某种方式期望两个参数,并且只使用了第二个参数(我在 x86 asm 代码中没有看到对第一个参数的任何引用)。该函数似乎工作正常,如果被调用为procedure ...(const garbage: integer; const param1: PAnsiChar); stdcall;
which 应该转换为 C stdcall void ...(int garbage, char* p1)
。
这个“不可见”参数看起来很像面向对象语言方法函数中的 Self/This 指针,但 Clarion 程序员肯定地告诉我没有涉及任何对象。更重要的是,他的“双整数”函数似乎也不期望不可见的参数。
前面提到的“提示”一书将Clarion 类型描述&CSTRING
为&STRING
实际上是引擎盖后面的两个参数,指向缓冲区的指针和缓冲区长度。但是,它没有提供有关它们在堆栈上传递的具体程度的信息。但有人说 Clation 拒绝使用导出的&CSTRING
参数化函数制作 DLL。
我可以假设不可见参数是 Clarion 想要存储函数返回值的位置(如果在 Clarion 源代码中对其进行了任何分配),交叉stdcall
/PASCAL
约定,但汇编程序结尾代码显示明确使用 EAX 寄存器,并且再次'double-LONG' 函数不使用它。
而且,所以,当我制作了“在我的机器上工作”的质量代码时,它成功地调用了 Clarion 函数,通过自愿插入一个垃圾参数——我感觉很模糊,因为我不明白 Clarion 在那里做什么以及为什么做,因此,在任何看似无关的变化之后,它可以在未来突然开始做什么。
那个看不见的参数是什么?为什么会发生在那里?可以期待什么?