在C#包装Win32 dll中的函数指针[转]

在C#中使用Delegate可以方便的包装Win32非托管dll中的函数指针(回调函数)。
如下面的Win32API:
typedef void (*PFNCLIENTCONNECTED)(DWORD
clientId, DWORD addr);
typedef void (*PFNCLIENTDISCONNECTED)(DWORD clientId,
DWORD addr);
typedef void (*PFNMESSAGERECEIVED)(DWORD clientId, DWORD addr,
LPCSTR message);

 

extern "C"
__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

 

 

重试,问题解决!

 

 

这种方式虽然能够解决上面的问题,但每次修改程序时,都需要重新进行上面的操作,非常不方便。

 

posted on 2011-01-18 12:41  曹兵强  阅读(727)  评论(1编辑  收藏  举报