(Delphi) 用函数指针参数调用DLL

4
我卡在了调用外部DLL并传递函数(指针)作为参数上。 最近我遇到了不同的问题,需要将一些参数传递给DLL,你帮了我。 希望有人知道如何做到这一点....
以下是需要从Delphi调用的DLL中的函数声明:
typedef void (*PTR_Allocate)(char**, unsigned long*); typedef void (*PTR_Deallocate)(char*);
extern "C" export_dll_function void SetAllocateFunction(PTR_Allocate); extern "C" export_dll_function void SetDeallocateFunction(PTR_Deallocate);
void Allocate(char** pbuffer, unsigned long* psize) { *psize = *psize * 2; *pbuffer = new char[*psize]; }
void Deallocate(char* buffer) { delete[] buffer; }
请问您能否帮我重写这个Delphi(7)代码呢?
以下是我尝试的内容,但它抛出了异常(“External exception”):
type PByte = ^TByte; TByte = array of byte; TFunc = function(var pbuffer: PByte; var psize: Cardinal): integer; cdecl; Procedure _SetAllocateFunction(var f: TFunc); cdecl;
implementation
function Allocate(var pbuffer: PByte; var psize: Cardinal): Integer; cdecl; begin psize := psize * 2; GetMem(pbuffer, psize); end;
var Func: TFunc; Func := @Allocate; _SetAllocateFunction(Func);
非常感谢!

1
不确定是否有很大的区别,但TFunc和Allocate应该声明为“procedure”,而不是函数吗?也许堆栈会因来自Delphi函数的返回参数而混乱,这些参数实际上不应该是函数?但我不是100%自信 :-) - shunty
4个回答

12

如果您不确定自己在做什么,请始终从最字面的翻译开始。函数原型表示它接收指向指针的指针,因此这就是您应该使用的内容:

type
  PTR_Allocate = procedure(param1: ^^Char; param2: ^LongWord); cdecl;

一旦你确定正确性,然后开始用更类似于Delphi的等效物替换事物。如果你跳过这一步,可能永远不会得到正确的结果,因为你只会不断地对一些本来就错误的东西进行更改。
那么,你确定上面的内容是正确的吗?并非完全正确。在Delphi中,Char的含义可能因产品版本而异。你正在使用Delphi 7,但你可能会升级,所以你可能会与其他人分享这段代码,因此你应该明确你想要什么大小的Char。当你需要一个单字节类型时,请使用AnsiChar。
type
  PTR_Allocate = procedure(param1: ^^AnsiChar; param2: ^LongWord); cdecl;

现在我们可以开始让它看起来更像Delphi。一个级别的指针参数可以被替换为“var”或“out”指令。对每个参数都执行这个操作:
type
  PTR_Allocate = procedure(out param1: ^AnsiChar; var param2: LongWord); cdecl;

指向AnsiChar的指针是一种常见的类型,Delphi已经为其命名为PAnsiChar。请使用惯用名称:PAnsiChar。
type
  PTR_Allocate = procedure(out param1: PAnsiChar; var param2: LongWord); cdecl;

最后,您可能希望在整个概念中加入一些自由,即根本不存在字符。显然,您正在为任意字节缓冲区分配内存,因此Byte可能比任何字符类型更好。最近的Delphi版本声明了一个指向字节的指针类型,请使用它:

type
  PTR_Allocate = procedure(out param1: PByte; var param2: LongWord); cdecl;

现在我们来看一下SetAllocateFunction。它接收一个PTR_Allocate参数,这是一个指向函数的指针。Delphi的过程类型是隐式指针,因此我们上面声明的类型已经完全适用于Delphi的等效类型。不要通过额外的"var"指令以引用方式传递它,否则你会遇到你所见过的问题,即使在程序尝试分配任何内存之前也是如此。这是其他答案忽略的事情。
procedure SetAllocateFunction(param: PTR_Allocate); cdecl;

不要在名称开头加下划线,除非你希望在自己的代码中调用时变得不方便。如果从DLL中导出时使用了不同的名称,则编写函数实现时请使用“name”子句:

procedure SetAllocateFunction; extern 'foo.dll' name '_SetAllocateFunction';

最后,如何实现分配函数。从与PTR_Allocate签名匹配的内容开始,尽可能地从原始的C++代码进行直译,并进行实现。

procedure Allocate(out pbuffer: PByte; var psize: LongWord; cdecl;
begin
  psize := psize * 2;
  GetMem(pbuffer, psize);
end;

您可以使用之前的函数来设置它:
SetAllocateFunction(Allocate);

请注意,我不需要单独的变量,并且没有使用@运算符。如果您需要使用@运算符来提及函数指针,在大多数情况下,您正在错误地使用它。通常您不需要它。使用该运算符可能会在程序中隐藏错误,例如签名不匹配,因为默认设置是@运算符为未定义类型。使用它会从函数指针中删除类型,并且无类型指针与Delphi中的任何其他函数指针类型兼容,包括具有错误签名的指针类型。
只有当编译器已经指示尝试调用函数时(例如通过提及您没有足够的参数或提及函数的返回类型),才在函数指针上使用@

每个方面都有很好的解释!看起来它能够工作。谢谢! - benjamin
+1:这是一个非常详细的答案,解释了每一步。大拇指向上! - Remko

1

我预见到声明 TByte 为动态数组类型可能会出现问题。动态数组本身就是一个指针。

要么将其声明为数组。

type 
  PByte = ^TByte;
  TByte = Array[0..1.) of Byte;

或者删除PByte声明

type
  TByte = Array of Byte;
  TFunc = function(var buffer:TByte; var pSize: Cardinal): Integer; cdecl

function Allocate(var buffer:TByte; Var pSize: Cardinal): Integer; cdecl;
 begin
  SetLength(buffer,pSize*2);
 end;

+1 原始的诊断是正确的,我认为第二个代码片段仍然是错误的,它在与 C 通信时仍然使用了动态数组。 - Marco van de Voort
IT使用动态数组,但它不传递指针而是传递引用,这实际上是一个指针,所以它可以工作。两个代码片段都可以正确地工作。 - PA.
谢谢PA,它编译通过了,但是在传递给setLength之前,“pSize”应该乘以2,因为它是“var”参数。 我会检查它是否能够正确地分配和释放内存。 顺便问一下,SetLength(buffer,0)是否会释放内存,还是应该使用GetMem和FreeMem? 我认为Deallocate函数没有“var”: function Deallocate(data: TByte): Integer; cdecl; - benjamin
你可以使用动态数组,但是需要传递指向第一个元素的指针而不是指向整个数组的指针。 - Remko

0

你只需要将指针的类型更改为 PByte = ^byte;

type
   PByte = ^byte;
   TByte = array of byte;
   TFunc = function(var pbuffer: PByte; var psize: Cardinal): integer; cdecl;
   Procedure _SetAllocateFunction(var f: TFunc); cdecl;

implementation

function Allocate(var pbuffer: PByte; var psize: Cardinal): Integer; cdecl;
begin
  psize := psize * 2;
  GetMem(pbuffer, psize);
end;

0
正如shunty已经指出的那样,Allocate和Deallocate都应该是Delphi中的过程。此外,psize参数应该像c声明所述一样是一个指针。 请记住,您必须实现两个函数才能使其正常工作,否则一个内存管理器将分配,而另一个将释放相同的内存,所以即使Allocate的实现正确,示例中显示的实现也将继续失败。

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