我有一个用Delphi编写的本地DLL,它主动使用回调机制:“注册”了一个回调函数,然后从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#的狂热者/专家,所以请原谅我的天真的问题,编码员是否在幕后做一些事情,例如将结构复制到堆上或类似的事情,或者应用程序有时可以正常工作的事实仅仅是一个纯粹的问题。机会?
先感谢您。
最佳答案
C#结构是一种值类型。意思就是
SomeObjectList.Add(Struct)
将复制该结构。因此,无需担心。
实际上,在
CallbackProc
中,您没有对在Delphi代码中分配的对象进行操作。这是因为p/调用编码器必须采用接收到的原始指针并将其转换为TStructType
对象。 TStructType
包含C#字符串,这些字符串绝对不能用这些Delphi字符数组进行变色。因此,编码人员已经在您的C#代码和Delphi代码之间添加了一层。由于该函数通过ref接收结构,因此发生的情况如下:
CallbackProc
之前,编码(marshal)处理程序将未托管的原始指针反序列化为TStructType
对象。 CallbackProc
对象传递给TStructType
。 CallbackProc
返回时,p/调用编码器将TStructType
对象序列化回原始的原始非托管指针。 其后果之一是,您对
TStructType
对象所做的更改对于Delphi代码而言是不可见的,直到回调过程返回。与调用Delphi过程将变量作为var
参数传递时所发生的情况形成对比。在这种情况下,该过程中的任何更改都可以在该过程外部立即看到。