在C#包装Win32 dll中的函数指针[转]
时间:2011-01-18 来源:曹兵强
extern "C"
__declspec(dllexport) BOOL InitializeServer(USHORT port);
__declspec(dllexport) BOOL UninitializeServer();
__declspec(dllexport) void RegisterClientConnectedCallback(PFNCLIENTCONNECTED
clientConnected);
__declspec(dllexport) void
RegisterClientDisconnectedCallback(PFNCLIENTDISCONNECTED
clientDisconnected);
__declspec(dllexport) void RegisterMessageReceivedCallback(PFNMESSAGERECEIVED
messageReceived);
class Win32Wrapper
{
public delegate void ClientConnected(uint clientID, uint address);
public delegate void ClientDisconnected(uint clientID, uint address);
public delegate void MessageReceived(uint clientID, uint address, StringBuilder message);
public static extern bool InitializeServer(ushort port);
public static extern bool UninitializeServer();
public static extern int RegisterClientConnectedCallback(Delegate callback);
public static extern int RegisterClientDisconnectedCallback(Delegate callback);
public static extern int RegisterMessageReceivedCallback(Delegate callback);
}
2.0)中都存在这个问题。经过研究发现C#中Delegate默认使用stdcall,而包装的win32
callback为cdecl,因此再调用时会出现这个问题。要避免这个问题发生,就需要将delegate的调用类型改为cdecl。但目前在C#代码中并不支持自定义delegate类型的调用方式,C#编译器也没有提供这方便的编译选项。因为要想解决这个问题,只能从元语言入手。
找到原始的delegate定义节,如下:
.class private auto
ansi beforefieldinit MessageReceiver.Win32Wrapper
extends
[mscorlib]System.Object
{
.class auto ansi sealed nested public
ClientConnected
extends
[mscorlib]System.MulticastDelegate
{
.method public hidebysig
specialname rtspecialname
instance void .ctor(object
'object',
native int 'method') runtime
managed
{
} // end of method ClientConnected::.ctor
instance void
Invoke(uint32 clientID,
uint32 address)
runtime managed
{
} // end of method ClientConnected::Invoke
instance class
[mscorlib]System.IAsyncResult
BeginInvoke(uint32 clientID,
uint32 address,
class [mscorlib]System.AsyncCallback callback,
object 'object') runtime managed
{
} // end of method
ClientConnected::BeginInvoke
instance void
EndInvoke(class [mscorlib]System.IAsyncResult result) runtime managed
{
} // end of method ClientConnected::EndInvoke
(3)改其中的调用定义,即Invoke定义,将其定义为cdecl类型:
.method public hidebysig newslot
virtual
instance void
modopt([mscorlib]System.Runtime.CompilerServices.CallConvCdecl) Invoke(uint32
clientID,
uint32 address) runtime
managed
{
} // end of method
ClientConnected::Invoke
我们在其中添加了modopt([mscorlib]System.Runtime.CompilerServices.CallConvCdecl),修改其默认的sdtcall
__declspec(dllexport) BOOL InitializeServer(USHORT port);
extern "C"
__declspec(dllexport) BOOL UninitializeServer();
extern "C"
__declspec(dllexport) void RegisterClientConnectedCallback(PFNCLIENTCONNECTED
clientConnected);
extern "C"
__declspec(dllexport) void
RegisterClientDisconnectedCallback(PFNCLIENTDISCONNECTED
clientDisconnected);
extern "C"
__declspec(dllexport) void RegisterMessageReceivedCallback(PFNMESSAGERECEIVED
messageReceived);
可以使用类似下面的C#代码来进行调用:
class Win32Wrapper
{
public delegate void ClientConnected(uint clientID, uint address);
public delegate void ClientDisconnected(uint clientID, uint address);
public delegate void MessageReceived(uint clientID, uint address, StringBuilder message);
[DllImport("LogCenter.dll")]
public static extern bool InitializeServer(ushort port);
[DllImport("LogCenter.dll")]
public static extern bool UninitializeServer();
[DllImport("LogCenter.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int RegisterClientConnectedCallback(Delegate callback);
[DllImport("LogCenter.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int RegisterClientDisconnectedCallback(Delegate callback);
[DllImport("LogCenter.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int RegisterMessageReceivedCallback(Delegate callback);
}
看起来没有问题,但当Callback在调用几次后,系统就会抛出NullReference异常。 目前.NET的版本(1.0, 1.1,
2.0)中都存在这个问题。经过研究发现C#中Delegate默认使用stdcall,而包装的win32
callback为cdecl,因此再调用时会出现这个问题。要避免这个问题发生,就需要将delegate的调用类型改为cdecl。但目前在C#代码中并不支持自定义delegate类型的调用方式,C#编译器也没有提供这方便的编译选项。因为要想解决这个问题,只能从元语言入手。
方法如下:
(1)使用ildasm打开编译出来的托管dll,将其反编译成元语言
(2)使用记事本打开生成的il文件。找到delegate定型的定义段
我们以上面示例中ClientConnected为例。
找到原始的delegate定义节,如下:
.class private auto
ansi beforefieldinit MessageReceiver.Win32Wrapper
extends
[mscorlib]System.Object
{
.class auto ansi sealed nested public
ClientConnected
extends
[mscorlib]System.MulticastDelegate
{
.method public hidebysig
specialname rtspecialname
instance void .ctor(object
'object',
native int 'method') runtime
managed
{
} // end of method ClientConnected::.ctor
.method public hidebysig newslot virtual
instance void
Invoke(uint32 clientID,
uint32 address)
runtime managed
{
} // end of method ClientConnected::Invoke
.method public hidebysig newslot virtual
instance class
[mscorlib]System.IAsyncResult
BeginInvoke(uint32 clientID,
uint32 address,
class [mscorlib]System.AsyncCallback callback,
object 'object') runtime managed
{
} // end of method
ClientConnected::BeginInvoke
.method public hidebysig newslot virtual
instance void
EndInvoke(class [mscorlib]System.IAsyncResult result) runtime managed
{
} // end of method ClientConnected::EndInvoke
} // end of class ClientConnected
(3)改其中的调用定义,即Invoke定义,将其定义为cdecl类型:
.method public hidebysig newslot
virtual
instance void
modopt([mscorlib]System.Runtime.CompilerServices.CallConvCdecl) Invoke(uint32
clientID,
uint32 address) runtime
managed
{
} // end of method
ClientConnected::Invoke
我们在其中添加了modopt([mscorlib]System.Runtime.CompilerServices.CallConvCdecl),修改其默认的sdtcall
同样修改其他几个delegate的定义
(4)使用ilasm重新将il文件编译成dll
重试,问题解决!
这种方式虽然能够解决上面的问题,但每次修改程序时,都需要重新进行上面的操作,非常不方便。
相关阅读 更多 +