【问题标题】:Clarion 6.3 DLL, *CSTRING parameter exporte function - adds an invisible parameter?Clarion 6.3 DLL,*CSTRING 参数导出功能 - 添加不可见参数?
【发布时间】:2019-12-02 14:09:43
【问题描述】:

我需要将一个函数调用从我的 Delphi 应用程序中协商到 Clarion 6.3 中提供的 DLL 中。 我需要传递一个或两个字符串参数(一个带有两个参数的函数或两个单参数函数)。 我们很快决定使用 1 字节的 0 结尾字符串(C 术语中的 char*,Clarion 术语中的 CSTRING,Delphi 术语中的 PAnsiChar),这就是事情变得有点不可预测和难以理解的地方。

我们得到的工作解决方案是传递伪装成 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# 之间的互操作之类的文章没有帮助,因为它们融合了两种语言的特性并且提供的关于“裸机”级别的信息更少。

Google 图书指出“Clarion 提示和技巧 - David Harms” - 它似乎对 Clarion 经验丰富的人有有趣的见解,但我是 Clarion 零。至少我无法从中找出低级互操作的细节。

有没有办法让 Clarion 6.3 为其制作的 DLL 保存“列表文件”,可能是标准的 *.H 头文件?


所以,重复一遍,正如预期的那样,一个函数在 Delphi 端传递指针(procedure ...(const param1, param2: PAnsiChar); stdcall; 应该转换为 C stdcall 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; 应该转换为 C stdcall 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; 应该转换为 C stdcall void ...(int garbage, char* p1)

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

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

我可以假设不可见参数是 Clarion 想要存储函数返回值的位置(如果在 Clarion 源中对其进行了任何赋值),跨越 stdcall/PASCAL 约定,但汇编程序结尾代码显示清晰为此使用 EAX 寄存器,“double-LONG”函数也不使用它。

而且,所以,虽然我制作了“在我的机器上工作”的质量代码,它成功调用了 Clarion 函数,但自愿插入了一个垃圾参数 - 我感觉很模糊,因为我不明白 Clarion 是什么以及为什么在那里做,因此,在任何看似无关的变化之后,它可以在未来突然开始做。

那个不可见的参数是什么?为什么会发生在那里?可以期待什么?

【问题讨论】:

  • 将您的问题传递给了几个 Clarion Skype 聊天组。顺便说一句,Clarion 6.3 是一个 16 位 IDE,大约 13 岁。任何想知道我们在说什么的人的参考:en.wikipedia.org/wiki/Clarion_(programming_language) 有没有办法让 Clarion 6.3 为它制作的 DLL 保存“列表文件”,也许是标准的 *.H 头文件?不,没有。
  • @riffrazor 是的,是的。但是遗留软件就是这样,它就是这样。到目前为止,最合理的想法似乎是 *STRING 和 *CSTRING 实际上与 &STRING 和 &CSTRING 没有什么不同,它们实际上都是两个参数 - 缓冲区长度后跟缓冲区指针。这对于空终止类型的输入参数没有意义,因此实际上并未使用。但仍有望在堆栈上。

标签: string dll calling-convention stdcall clarion


【解决方案1】:

如果您使用来自 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% 认为语法是合法的。)

【讨论】:

  • “知道最大长度”对于输出参数/缓冲区有好处。但是对于输入缓冲区,它可以说违反了 DRY 原则。如果那里的价值观不一致怎么办?哪个是正确的?好的,理论上我可以使用像strncpy 这样的函数,它至少需要两个长度。但是...但是strncpy 预计会受 DESTINATION 的长度约束,而不是受 SOURCE 的第二个长度约束。而且,Clarion 无论如何都不会使用“最大长度”参数。 IOW 被调用者不能依靠在那里传递正确的长度来获得额外的安全性,这将是一种比已知的不安全更糟糕的安全错觉:-0
  • 我想知道在 Clarion x86/x64 中是否有关于本机级互操作的公开和官方文档... 逆向工程这样的事情总是感觉如履薄冰。不可靠。您无法将编译器错误与故意行为区分开来。您不知道编译器版本的小更新或编译器选项的更改是否会彻底改变它。您切换到cdecl/c 而不是stdcall/pascal 的提示很有趣,这将使堆栈损坏的可能性降低。您的over 技巧很有趣,但根据那本“技巧”书,可能应该使用*cstring 而不是&cstring
  • 我会告诉更多关于倒车的信息。第一次尝试是尝试在 Clarion 中制作void(char **),我会说Procedure(**Cstring parm1)。但是由于 Clarion 中没有完整的类型系统 - 这是不可能的。但是我希望有一个*group(C 术语中的*struct,Pascal 术语中的^packed record)参数,该组只有一个*cstring 成员,从而模拟双指针。为什么?因为我注意到 Clarion 将指向文本的指针视为文本本身。这是一个可以从没有理解的逆向工程中推断出的非常“明显的简单和错误的解决方案”
  • 好的,看来这个线程已经死了,没有新的程序员会来这里做出更好的答案。可悲,但这就是比特腐烂的方式:-( 接受这一点,无需等待 :-D
猜你喜欢
  • 1970-01-01
  • 2017-09-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-07
  • 1970-01-01
  • 2012-08-24
  • 2016-10-22
相关资源
最近更新 更多