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由.NET应用程序使用,用C#编写. 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#的忠实粉丝/专家,所以请原谅我天真的问题,编组人员在幕后做些什么,比如将结构复制到堆上或类似的东西,或者应用程序有时工作的事实是纯粹的问题机会?
先感谢您.
解决方法
SomeObjectList.Add(Struct)
将制作结构的副本.所以,没什么值得关注的.
实际上,在CallbackProc中,您不会对Delphi代码中分配的对象进行操作.那是因为p / invoke marshaller必须获取它收到的原始指针并将其转换为TStructType对象. TStructType包含C#字符串,这些字符串绝对不能与那些Delphi字符数组一起使用.所以marshaller已经在你的C#代码和Delphi代码之间添加了一个层.
由于函数通过ref接收结构,所发生的事情如下:
>在调用CallbackProc之前,marshaller将原始非托管指针反序列化为TStructType对象.
>然后通过引用传递那个TStructType对象的CallbackProc.
>当CallbackProc返回时,p / invoke marshaller将TStructType对象序列化回原始的非原始非托管指针.
其中一个后果是,在回调过程返回之前,您对TStructType对象所做的更改对Delphi代码不可见.对比当你调用Delphi过程将变量作为var参数传递时会发生什么.在这种情况下,程序中的任何更改都会立即显示在该过程之外.