Platform Invoke in CLR (5)--封送委托实现回调函数

C++中有函数指针,而C#中与之对应的便是委托。那在PInvoke的过程中传入回调函数的方法应该是传入委托。PInvoke会将委托封送为函数指针

传递到C++中。

Sample:

C++代码:

typedef char* (*__cdecl AddCallBack)(const char* a,const char* b);

_declspec(dllexport) void _cdecl CallBackDelegate(AddCallBack callback)
{
        char* result = 0;
        
        if(callback)
            result = callback("My name is ","Jensen");
        
        printf("Call delegate completly,the result is %s",result);
}

托管包装类代码:

[return:MarshalAs(UnmanagedType.LPStr)]
// Use cdecl calling convention here since we need get the result from the stack of the call back method
[UnmanagedFunctionPointer(System.Runtime.InteropServices.CallingConvention.Cdecl)]
public delegate string StrCatHandler([MarshalAs(UnmanagedType.LPStr)]string a, [MarshalAs(UnmanagedType.LPStr)]string b);

[DllImport("native.dll", EntryPoint = "CallBackDelegate", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
public static extern void CallBackDelegate([MarshalAs(UnmanagedType.FunctionPtr)]StrCatHandler handler);


托管调用代码:

public static string StrCat(string source1, string source2)
{
            return source1 + source2;
}
public static main(string[] args)
{
   Console.WriteLine("CallBackDelegate Method");
 
   NativeWrapper.CallBackDelegate(StrCat);
}

这样,我们便把托管环境中的方法以委托的形式传递到非托管环境中进行回调,并获取了托管环境中的返回值。

这里需要注意的是需要让StrCat方法的生命周期在非托管代码的可能的调用范围之外。不能让GC将StrCat所在的对象进行回收。

有两种方法:

1. 将回调方法实现为静态方法,这样方法的生命周期与应用程序生命周期一致。

2. 将加调方法实现为实例方法,然后在非托管代码的可能回调范围的结尾处调用GC.KeepAlive方法让对象实例的生命周期至少到该时刻(调用GC.KeepAlive处)。


我们知道Delegate是可以表示多个方法的,如果我们将表示多个方法的委托传递给非托管环境会怎么样呢?

C#调用代码:

public static string StrCat(string source1, string source2)
{
    return source1 + source2;
}

public static string StrCat2(string source1, string source2)
{
    return source1 +"[StrCat2]"+ source2;
}

public static main(string[] args)
{
    StrCatHandler handler = new StrCatHandler(StrCat);
    handler += new StrCatHandler(StrCat2);
    NativeWrapper.CallBackDelegate(handler);
}


结果是两个方法均被调用,非托管环境中获得的返回值是委托中添加方法顺序中最后一个方法的返回值。这个与托管环境中委托的调用一致。

这证明在传递委托的时候,PInvoke应该不是直接传递的委托所代表的方法的内存指针。因为如果是这样的话,当委托代表多个方法时便没有办法表示。

关于传递委托给非托管环境的内部机制,下次再进一步研究一下。今天先到这....

posted @ 2012-11-18 14:10  self.refactoring  阅读(1434)  评论(0编辑  收藏  举报