Clarion 6.3 DLL,*CSTRING 参数导出函数 - 添加不可见参数?

Clarion 6.3 DLL, *CSTRING parameter exporte function - adds an invisible parameter?

我需要协商一个函数调用,从我的 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 开发人员从我的肩膀后面瞥了一眼,说了类似 "i don't need copy of it, i already know it, it is typical" 的内容。

然而,从长远来看,这会给我们带来更多负担,因为低级非类型化代码在样板文件上有很多 "richer" 并且容易出错。输入代码会感觉更好。

我在这里寻求的不是 "That is the pattern to copy-paste and make things work without thinking" - 我们已经拥有它 - 而是更多的理解,引擎盖背后发生了什么,我如何依赖它,以及我应该从中得到什么Clarion DLL。为了避免最终陷入 "works by chance" 解决方案。

当我从他的肩膀后面浏览 Clarion 6.3 帮助时,该帮助对低级细节没有帮助。这完全是关于从 Clarion 调用 DLL,而不是关于被调用。我的机器上也没有 Clarion,我不想,咳咳,借用它。正如我被告知的那样,Clarion 6.3 运行时的源代码也不对开发人员可用。

诸如 Clarion 与 VB 之间或 Clarion 与 C# 之间的互操作之类的文章没有帮助,因为它们融合了两种语言的特性并且提供的有关 "bare metal" 级别的信息更少。

Google 书籍指向 "Clarion Tips & Techniques - David Harms" - 它似乎对 Clarion 经验丰富的人有有趣的见解,但我是 Clarion 零。至少我无法从中找出低级别的互操作支持细节。

有没有办法让 Clarion 6.3 为它制作的 DLL 保存 'listing files',也许是一个标准的 *.H 头文件?


所以,重复一下,正如预期的那样,有效的是一个在 Delphi 端传递指针的函数( procedure ...(const param1, param2: PAnsiChar); stdcall; 应该转换为 C stdcall void ...(char* p1, char* p2) 并且据称在Clarion 类似 (LONG, LONG), LONG, pascal, RAW.

这个函数从堆栈中以相反的顺序取出两个32位参数,使用它们,然后退出,在EAX寄存器中传递return值(实际上是未使用的垃圾)并从堆栈中清除参数。几乎完全 stdcall,只是它似乎出于某种不明原因保留了 EBX 寄存器。

号角功能入口:

04E5D38C 83EC04           sub esp,        ' allocate local vars
04E5D38F 53               push ebx           '  ????????
04E5D390 8B44240C         mov eax,[esp+[=10=]c]
04E5D394 BBB4DDEB04       mov ebx,ebddb4
04E5D399 B907010000       mov ecx,[=10=]000107
04E5D39E E889A1FBFF       call e1752c     ' clear off local vars before use

及其出口

00B8D500 8B442406         mov eax,[esp+] ' pick return value
00B8D504 5B               pop ebx           ' ????
00B8D505 83C41C           add esp,c       ' remove local vars
00B8D508 C20800           ret [=11=]08         ' remove two 32-bits params from stack

除了我无法解释的 EBX 操作和 returning 垃圾结果 - 它按预期工作。但是 - 需要 Clarion 来源中的非类型化低级操作。

现在据称只接受一个字符串参数的函数:在 Delphi 一侧 - procedure ...(const param1: PAnsiChar); stdcall; 应该转换为 C stdcall void ...(char* p1) 并且据称在 Clarion 中看起来像 (*CSTRING), LONG, pascal, RAW.

号角功能入口:

00B8D47C 83EC1C           sub esp,c    ' allocate local vars
00B8D47F 53               push ebx       '  ????????
00B8D480 8D44240A         lea eax,[esp+[=12=]a]
00B8D484 BB16000000       mov ebx,[=12=]000016
00B8D489 B990FEBD00       mov ecx,[=12=]bdfe90
00B8D48E BA15000000       mov edx,[=12=]000015
00B8D493 E82002FBFF       call [=12=]b3d6b8 ' clear off local vars before use

及其出口

04E5D492 8B442404         mov eax,[esp+]  ' pick return value
04E5D496 5B               pop ebx            ' ????
04E5D497 83C404           add esp,        ' remove local vars
04E5D49A C20800           ret [=13=]08          ' remove TWO 32-bits params from stack

这里令人吃惊的是,函数以某种方式需要两个参数,并且只使用了第二个(我在 x86 asm 代码中没有看到对第一个参数的任何引用)。该函数似乎工作正常,如果被称为 procedure ...(const garbage: integer; const param1: PAnsiChar); stdcall; 应该转换为 C stdcall void ...(int garbage, char* p1)

这个 "invisible" 参数看起来很像面向对象语言方法函数中的 Self/This 指针,但 Clarion 程序员肯定地告诉我没有涉及任何对象。更重要的是,他的 'double-int' 函数似乎也不期望不可见参数。

前面提到的 'Tips' 书中将 &CSTRING&STRING Clarion 类型描述为实际上是引擎盖后面的两个参数,指向缓冲区的指针和缓冲区长度。但是,它没有提供有关它们在堆栈上传递的具体方式的信息。但有人说 Clation 拒绝制作带有导出 &CSTRING 参数化函数的 DLL。

我可以假设不可见参数是 Clarion 想要存储函数的 return 值的地方(如果在 Clarion 源中有任何赋值的话),穿过 stdcall/PASCAL 约定,但汇编程序尾声代码清楚地显示了 EAX 寄存器的使用,而且 'double-LONG' 函数也没有使用它。

因此,当我编写 "works on my machine" 质量代码时,通过自愿插入一个垃圾参数成功调用了 Clarion 函数 - 我感觉很模糊,因为我无法理解 Clarion 的内容和原因正在那里做,因此,在任何看似无关的更改之后,它将来可以突然开始做什么。

那个看不见的参数是什么?为什么会发生在那里?对它有何期待?

如果您使用 来自 Clarion 的 DLL,您可以使用 RAW 制作原型 - 但 Clarion DLL 中的程序不能这样做。 所以在 Clarion DLL 中,他们可以原型为

Whatever   Procedure(*Cstring parm1, *Cstring parm2),C,name('whatever')

并且,正如您所注意到的,从您的角度来看,您应该将其视为 4 个参数:长度、指针、长度、指针。 (无论如何,从安全的角度来看,知道明确的最大长度并不是一件坏事。)

备选方案是

Whatever   Procedure(Long parm1, Long parm2),C,name('whatever')

那么在您这边只有 2 个地址。 但是他这边还有一些代码可以将这些传入地址转换为内存指针。 (是的,他可以使用 PEEK 和 POKE,但这有点矫枉过正) (根据记忆,他可以将局部变量声明为

parm1String  &cstring,over(parm1)
parm2String  &cstring,over(parm2)

但我已经几十年没有这样做了,所以我不是 100% 语法是合法的。)