如何释放传递给 C# 的 Delphi PByte?

How to free a Delphi PByte passed to C#?

我设法将 PByte 从 Delphi DLL 传递到 C# 并读取 PByte:

public void DoSomething(string company, string claimNumber, string language)
{
    var buffersize = 0;
    IntPtr pnt = new IntPtr();

    try
    {
        //Get some data from Delphi dll
        if (DelphiController.DoSomething(ref buffersize, ref pnt))
        {
            byte[] managedArray = new byte[buffersize];
            Marshal.Copy(pnt, managedArray, 0, buffersize);
            //Do something with managedArray...
        }
    }
    finally
    {
        //how to free pnt?
        DelphiController.FreeMemory(pnt, buffersize);
    }
}

Delphi函数:

function DoSomething(var buffersizeArr: integer; var pnt: PByte): Wordbool; stdcall; export;
var
  arrB: Tarray<Byte>;
begin
    //some code
    arrB := TFile.ReadAllBytes(fileName);
    buffersizeArr := length(arrB);
    pnt := @arrB[0];
    //some more code
end;

到目前为止一切正常,所以现在我想释放 pnt 分配的内存。我试图将 pnt 传递回 Delphi DLL,但我无法释放内存并且总是得到 Invalid Pointer Operation 异常。

function FreeMemory(pnt: Pointer; size: integer): Wordbool; stdcall; export;
var
  p: Pointer;
begin
  try
    FreeMem(pnt, size); //throws invalid pointer exception

    result := true;
  except
    on e:Exception do
    begin
      result := false;
    end;
  end;
end;

那么此时释放内存的正确方法是什么?

Delphi 代码已损坏。您正在返回在函数 returns 之前销毁的动态数组的地址。换句话说,您的 Delphi 代码 returns 指向已释放内存的指针。你的问题不应该是 "how do I deallocate the memory" 而是你应该问 "how do I stop the memory from being deallocated"!

相反,您应该这样做:

function DoSomething(var buffersizeArr: integer; var pnt: PByte): Wordbool; stdcall;
var
  arrB: Tarray<Byte>;
begin
  arrB := ...;
  buffersizeArr := Length(arrB);
  pnt := CoTaskMemAlloc(buffersizeArr);
  Move(Pointer(arrB)^, pnt^, buffersizeArr);
  Result := ...;
end;

然后您可以使用 Marshal.FreeCoTaskMem.

在 C# 代码中释放数组

我选择在这里使用 CoTaskMemAlloc 是因为它是从共享堆分配的,因此您可以轻松地从 C# 中解除分配。您同样可以使用 LocalAlloc,然后在 C# 中使用 Marshal.FreeHGlobal 解除分配。如果您使用 GetMem 在 Delphi 中分配,那么您还需要导出 Delphi 函数来执行释放,因为 GetMem 使用内部 Delphi 堆。

一些小笔记:

  • export Delphi 关键字被忽略,您可以将其删除。
  • newIntPtr 一起使用毫无意义。你可以写 IntPtr pnt = IntPtr.Zero.
  • 如果您声明 p/invoke 使用 out 参数而不是 ref 那么您不需要初始化作为参数传递的变量。