我有一个原生的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?
}
据我了解,结构体变量是在Delphi的堆栈中创建的,存储在DLL深处,并在客户端应用程序中将其指针存储在堆中,这是一种纯粹的冒险行为。
我不是C#的大粉丝/专家,所以请原谅我的天真问题,马歇尔是否在幕后做一些事情,例如将结构体复制到堆中或类似的操作,还是应用程序有时能正常工作纯属偶然?
提前致谢。