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 越界”。我会很高兴得到一个快速的答案:)

4

2 回答 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; 
         .....

追查什么是Cbool类型。它真的是单字节布尔值吗?或者是一些 Windows 布尔值,可能需要 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一个class function并且根本不创建连接器类的实例。

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

聚苯乙烯。大卫关于合同是非常正确的。特别是关于“谁分配内存?” 即使 DLL 只返回字符串常量,也有一个巧妙的陷阱:加载 DLL,获取指向字符串的指针,卸载 DLL,尝试使用指针 - 访问冲突。因此,虽然我认为上面的桥是正确的界面翻译,但由于 David 指出的问题,它不一定在更大的范围内工作。

购买力平价。数组数据类型声明可能看起来很冗长,但它克服了我认为 Delphi novadays 的一个愚蠢限制:为什么“字符串数组”的两个别名处理不同?

购买力平价。使用out参数是有争议的。例如,David 认为由于 Delphi 实际上并没有为大多数数据类型实现它们,因此几乎应该总是使用var参数来代替。我个人认为使用out参数有利于记录您的合同,并且有利于与 FPC 和其他编译器的兼容性。

于 2013-02-20T10:29:41.180 回答
2

另一个小问题是“布尔”类型。帕斯卡布尔值通常假设 false=0 true=1 并且未定义,很像 GTK gbooleans。

C 布尔值通常假设 0=false,其他任何值=true。

Recent Free Pascals have a complete set of both in various sizes. Boolean8..boolean64 for the pascal side, bytebool,wordbool,longbool etc for the C side.

于 2013-02-20T12:21:12.510 回答