我该如何在Pascal/Delphi中调用下列C函数?

3

我遇到了以下情况:

我想从我的Pascal程序中调用一个C函数。该C函数应该使用传递的指针填充值。

这是C函数:

DLLEXPORT int dpstate_callPluginFunction(const char* plugin, const char* function, bool synchronous, const char* p0, const char* p1, const char* p2, const char* p3, const char* p4, const char* p5, const char* p6, char** o0, char** o1, char** o2, char** o3, char** o4, char** o5, char** o6)

“p”参数是输入参数,“o”参数是输出参数。我尝试在我的Pascal程序中这样调用函数:

C函数调用声明:

var dpstate_callPluginFunction: function(plugin, method: PAnsiChar; synchronous: boolean; p0, p1, p2, p3, p4, p5, p6: PAnsiChar; o0, o1, o2, o3, o4, o5, o7: PPAnsiChar): integer; cdecl;

C函数调用加载:

@dpstate_callPluginFunction:= GetProcAddress(mConnectorLibrary, 'dpstate_callPluginFunction');                

函数调用声明:

function callPluginFunction(plugin, method: PAnsiChar; synchronous :boolean; param, returnParam:array of PAnsiChar): integer;

应该调用该函数的函数:

procedure TForm1.btn_pluginFunctionClick(Sender: TObject);
var param, returnParam: array of PAnsiChar;
begin
SetLength(param, 7);
SetLength(returnParam, 7);
param[0]:= 'Param1';
param[1]:= 'Param2';
connector.callPluginFunction('dpserverplugin', 'showconfigdialog', true, param, returnParam);

output.Append(returnParam[0]);
output.Append(returnParam[1]);
end; 

功能:

function PConnect.callPluginFunction(plugin, method: PAnsiChar; synchronous :boolean; param, returnParam:array of PAnsiChar): integer;
  var i, error: integer;
  var p: array[0..6] of PAnsiChar;
  var o: array[0..6] of PPAnsiChar;
begin
  for i:=0 to 6 do
            p[i]:= param[i]; 
  dpstate_callPluginFunction(plugin, method, synchronous, p[0], p[1], p[2], p[3], p[4], p[5], p[6], @o[0], @o[1], @o[2], @o[3], @o[4], @o[5], @o[6]);

  for i:=0 to 6 do
            if o[i] <> Nil then
                returnParam[i]:= o[i]^; 
end;

我的问题是,输出“returnParam”始终包含“地址xxxxxx超出范围”的提示。希望您能快速回答 :)

5
如果您能提供足够的信息,那我会很高兴为您提供快速回答。以下是您在提问时的错误:1. 您要求快速答案。2. 您没有包含导入函数的Pascal声明。3. 您没有告诉我们参数是如何传递的,只有它们的类型。哪些是输入的,哪些是输出的等等。谁分配内存?4. 您没有告诉我们您看到了什么错误。总之,在提问时,您需要更加努力。请改进问题。-1 - David Heffernan
2
你没有解决我原始列表中的第一项。不要要求快速答案。当你传递@o[0]时,实际上是一个指向PPAnsiChar的指针。但函数期望的是PPAnsiChar。所以这是不匹配的。但我们仍然不知道接口契约是什么。描述一下char **参数的规则。在我看来,你需要大大简化接口才能帮助我们。尝试使用单个char **参数的版本,并查看是否可以使其正常工作。 - David Heffernan
2个回答

9

我认为你混淆了动态数组和开放数组。虽然它们在不同的上下文中使用相同的措辞。

可以参考这个链接:http://rvelthuis.de/articles/articles-openarr.html

试试这个:

type TPAnsiCharDynArray = array of PAnsiChar;

function callPluginFunction(plugin, method: PAnsiChar; 
         synchronous :boolean; param: array of PAnsiChar;
     out returnParam: TPAnsiCharDynAttay): integer;

还有这一行存在问题:

for i:=0 to 6 do
         p[i]:= param[i]; 

如果您确定您的数组始终为0..6,那么使用动态数组就没有意义。

type TDLLVectorIndex = 0..6; 
     TDLLPCharArray = array [TDLLVectorIndex] of PAnsiChar;

function callPluginFunction(const plugin, method: PAnsiChar; 
         synchronous :boolean; 
         const param: TDLLPCharArray;
         out returnParam: TDLLPCharArray): integer;

不要忘记使用参数修饰符const/var/out来记录您的调用协议,并使传递的值成为引用而不是克隆并作为值传递。

或者,如果您不知道确切的尺寸,则不应该假设6这个神奇数字,并迭代到传递进来的数组的实际大小。

for i:=0 to High(param) do
        p[i]:= param[i]; 

然而,这似乎与你的情况不符,但非常引人注目的是,你明确声明了参数“god-only-knows-what-length-array”,然后将其与硬编码的魔术常量一起使用。
同样地,如果你制作Pascal桥,则使用Pascal字符串比使用C字符指针更好。
function callPluginFunction(const plugin, method: AnsiString; 
         synchronous :boolean; 
         .....

查找C语言中的bool类型。它真的是单字节布尔值吗?还是一些Windows bools,可以占用1、2甚至4个字节?有时候也会存在关于true的微小二进制不兼容性,+1或-1。

但是没有这个bool的不确定性,应用参数限定符后,您的fn声明会更好地显示。

type fn_dpstate_callPluginFunction = 
   function(const plugin, method: PAnsiChar; synchronous: boolean;
   const  p0, p1, p2, p3, p4, p5, p6: PAnsiChar; 
   var o0, o1, o2, o3, o4, o5, o7: PAnsiChar): integer; cdecl;
var dpstate_callPluginFunction: fn_dpstate_callPluginFunction;

在C++中,按引用传递参数并不常见,在C语言中可能不存在。但是在普通的Pascal代码中,通过引用传递参数被认为比传递指针更常见和更安全。


这涉及桥接重新编码:

type TDLLVectorIndex = 0..6; 
     TDLLPCharArray = array [TDLLVectorIndex] of PAnsiChar;

function PConnect.callPluginFunction(const plugin, method: AnsiString; 
         synchronous :boolean; 
         const p: TDLLPCharArray;
         out o: TDLLPCharArray): integer;

  var Error: integer;
begin
  Error :=
    dpstate_callPluginFunction( PAnsiChar(plugin), PAnsiChar(method), synchronous, 
        p[0], p[1], p[2], p[3], p[4], p[5], p[6],
        @o[0], @o[1], @o[2], @o[3], @o[4], @o[5], @o[6]);
  Result := Error + 10;
// or something like that - you did had the reason
// to declare the var and declare function return type afterall
end;

procedure TForm1.btn_pluginFunctionClick(Sender: TObject);
var param, returnParam: TDLLPCharArray;
begin
  FillChar(param, 0, SizeOf(TDLLPCharArray)); // maybe redundant, but to be on safe side
  param[0]:= 'Param1';
  param[1]:= 'Param2';
  connector.callPluginFunction('dpserverplugin', 'showconfigdialog', true, param, returnParam);
....

PS. 最后但并非最不重要的,你的连接器里有没有任何真实的成员变量?我的意思是非静态的那些,它们在多个连接器实例之间可能是不同的。如果没有,你可能会将 callPluginFunction 设为一个 类函数,而根本不创建连接器类的实例。

  param[0]:= 'Param1';
  param[1]:= 'Param2';
  PConnect.callPluginFunction('dpserverplugin', 'showconfigdialog', true, param, returnParam);

附言:David在合同方面非常正确,尤其是关于“谁分配内存?”即使DLL只返回字符串常量,也存在一个巧妙的问题:加载DLL,获取指向字符串的指针,卸载DLL,尝试使用指针-访问冲突。因此,虽然上面的桥接是我认为的正确接口翻译,但由于David指出的问题,它在更大的画面下不一定有效。

附言2:数组数据类型声明可能看起来很冗余,但它克服了我认为Delphi现在的一个愚蠢限制:为什么“array of string”的两个别名被不同对待?

附言3:使用out参数是有争议的。例如,David认为,由于Delphi实际上并没有为大多数数据类型实现它们,所以几乎总是应该使用var参数。个人认为,使用out参数有助于记录您的合同,并且有助于与FPC和其他编译器兼容。


2
另一个小问题是“布尔”类型。Pascal 布尔通常假定 false=0,true=1,其余未定义,与 GTK gbooleans 类似。
C 语言布尔通常假定 0=false,其他任何值=true。
最近的 Free Pascal 在不同大小方面都有完整的布尔类型集合。Pascal 方面有 Boolean8..boolean64,C 方面有 bytebool、wordbool、longbool 等。

我提到了这一点。虽然“rest undefined”似乎有点过于宽泛,但是是的,Windows/OLE布尔类型倾向于将-1作为规范真值,而i386汇编和Pascal倾向于将+1作为规范真值。然而在理论上,它们应该将任何非零数字视为真... - Arioch 'The
不,这并不过于宽泛。Pascal将正好为1的值视为真。 - Marco van de Voort
但是它是否将2、3、4...视为未定义? - Arioch 'The
是的。行为取决于生成的汇编代码(因此取决于架构和优化级别)。 - Marco van de Voort

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接