7

我有一个本机 DLL,用 Delphi 编写,积极使用回调机制:回调函数被“注册”,然后从 DLL 内部调用:

function RegisterCallback(CallbackProc: TCallbackProc): Integer; stdcall;

大多数回调函数通过引用传递普通结构,如下所示:

TCallbackProc = procedure(Struct: PStructType); stdcall;

其中 PStructType 被声明为

TStructType = packed record 
  Parameter1: array[0..9] of AnsiChar;
  Parameter2: array[0..19] of AnsiChar;
  Parameter3: array[0..29] of AnsiChar;
end;
PStructType = ^TStructType;

此 DLL 由用 C# 编写的 .NET 应用程序使用。C# 代码的编写非常粗心,整个应用程序的行为不可靠,显示出难以识别的异常,在不同的地方引发了从运行到运行。

我没有理由怀疑 DLL,因为它已经证明自己是一个非常强大的软件,用于许多其他应用程序。我目前关心的是这些结构在 C# 中的使用方式。

假设,上面的记录在 C# 中重新声明如下:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
public struct TStructType
{
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 10)]
    public string Parameter1;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 20)]
    public string Parameter2;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 30)]
    public string Parameter3;
}

并且回调被声明为

[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void CallbackProc(ref TStructType Struct);

现在有趣的事情开始了。假设,在 DLL 中,注册的回调以这种方式调用:

var
  Struct: TStructType;
begin
  // Struct is initialized and filled with values
  CallbackProc(@Struct);
end;

但是我在 C# 应用程序中看到的,以及我根本不喜欢的是,编组结构被保存为指针以供将来使用:

private void CallbackProc(ref TStructType Struct)
{
    SomeObjectList.Add(Struct); // !!! WTF?
}

据我了解,Struct 变量是在 DLL 深处的 Delphi 堆栈上创建的,并将指向它的指针存储在客户端应用程序的堆上 - 纯粹是一次冒险。

我不是 C# 的忠实粉丝/专家,所以请原谅我的幼稚问题,编组器是否在幕后做了一些事情,比如将结构复制到堆上或类似的事情,或者应用程序有时工作的事实是纯粹的问题机会?

先感谢您。

4

1 回答 1

4

AC# struct 是一种值类型。意思就是

SomeObjectList.Add(Struct)

will make a copy of the struct. So, nothing to get concerned about.

In fact, in CallbackProc you are not operating on the object that was allocated in your Delphi code. That's because the p/invoke marshaller had to take the raw pointer that it received and convert that into a TStructType object. And a TStructType contains C# strings which are most definitely not blittable with those Delphi character arrays. So the marshaller has already added a layer in between your C# code and the Delphi code.

Since the function receives the structure by ref what happens is as follows:

  1. Before calling CallbackProc the marshaller de-serializes the raw unmanaged pointer to a TStructType object.
  2. CallbackProc is then passed that TStructType object by reference.
  3. When CallbackProc returns, the p/invoke marshaller serializes the TStructType object back to the original raw unmanaged pointer.

One of the consequences of this is that changes you make to the TStructType object are not visible to the Delphi code until the callback procedure returns. Contrast that to what happens when you call a Delphi procedure passing a variable as a var parameter. In that case any changes in the procedure are visible immediately outside that procedure.

于 2012-12-06T16:12:30.543 回答