.NET Interop入门-P/Invoke和Reverse P/Invoke

 

最近在论坛上经常看到一些基本的interop的问题,给我动力写完之前的.net interop入门系列,给刚刚涉足.NET  interop的朋友们一个大体上的概念。

每每谈及.NET interop,我的脑中总是出现下面一幅图:

interop

该图代表了.net interop的四个典型场景。之前我的同事和我讨论了.NET和COM互操作的应用:

今天我主要讲一下P/Invoke和Reverse P/Invoke,和COM interop相比,P/Invoke无需注册组件,使用上更轻量,更绿色。

1. P/Invoke

P/Invoke(platform invoke)是.NET调用本地代码(native code)的一种比较轻便的方式。只需要将本地代码编写成动态链接库,然后在c#代码中,声明一个外部静态函数,并且用DllImport属性指明动态连接库的入口。举例如下:

 using System;
using System.Runtime.InteropServices;

class PInvoke
{
    [DllImportAttribute("user32.dll", EntryPoint = "MessageBoxW")]
    public static extern  int MessageBoxW(
        [In]System.IntPtr hWnd,
        [In][MarshalAs(UnmanagedType.LPWStr)] string lpText,
        [In][MarshalAs(UnmanagedType.LPWStr)] string lpCaption,
        uint uType);

    public static void Main()
    {
        MessageBoxW(IntPtr.Zero, "Hello", "Interop", 0);
    }
}

稍加解释这个代码。类PInvoke中,有个MessageBoxW的函数声明,它的实现在user32.dll(系统自带)中,入口是MessageBoxW,参数的构成是根据windows API的声明而定的,我们在Codeplex上有一个工具,专门帮助大家声称一个本地代码(c++)编写的函数在托过代码(c#)中的函数声明,之前我们团队的成员也撰文介绍了这个工具的使用。

有了这个声明以后,在Main中调用MessageBox,就和调用其他托管代码一样轻松自如了。

2. Reverse P/Invoke

接着,我们来看看在本地代码中调用.NET方法。本地代码需要拿到一个.NET委托(delegate),然后把这个delegate当作一个函数指针使用,示例如下:

 using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;

public class Program
{
    internal delegate void DelegateMessageBox([MarshalAs(UnmanagedType.LPWStr)]string msg);

    [DllImport("Native.dll", CallingConvention = CallingConvention.Cdecl)]
    static extern void NativeMethod(DelegateMessageBox d);

    public static void ShowMessageBox(string msg)
    {
       MessageBox.Show(msg);
    }

    public static void Main()
    {
        NativeMethod(new DelegateMessageBox(ShowMessageBox));
    }
}

这个例子中,我们希望本地代码能够调用托管函数ShowMessageBox来显示一个对话框。为了让本地代码可以调用这个函数,我们根据它的声明,定了了一个delegate,并且通过P/Invoke把这个委托传给了本地代码。本地代码可以如下调用托管代码:

 #include <stdio.h>
#include <wtypes.h>

extern "C" {
    __declspec(dllexport) void NativeMethod(void (__stdcall *pShowMsgBox)(WCHAR *wChar))
    {
        (*pShowMsgBox)(L"hello reverse interop");
    }
}

注意到托管代码中的委托到了本地代码中,就是一个函数指针,本地代码可以像一个普通的函数指针一般调用托管代码。

大家可能注意到dll的声明用了extern “C”,它指明了调用规范是cdecl,在之前的托过代码的DllImport中,也相应的注明了调用约定,关于调用约定的详细介绍,可以参见我的另一篇博客

今天的介绍就到这里,大家可以把这些示例代码当作一个template,根据实际需求作相应的具体改动。