天天看點

手把手教你PInvoke

當你寫久了應用層代碼,是不是需要來玩一下硬體呀?

這個時候你就會接觸到一些美妙的dll,比如

user32.dll

,

kernal32.dll

當然這些是非托管的代碼,我們在.net中無法直接使用,是以我們會需要使用PInvoke進行調用

于是你會使用

DllImport

特性标記一個方法,引入非托管函數

比如 我們希望彈出一個消息框,就會使用下面這個函數,添加

DllImport

特性,表明從哪個dll引入方法

public class Win32
{
    [DllImport("user32.dll")]
    public static extern IntPtr MessageBox(int hWnd, String text, String caption, uint type);
}
class Program
{
    static void Main(string[] args)
    {
        Win32.MessageBox(0, "這是我的部落格:https://xinyuehtx.github.io/", "Hello 黃騰霄", 0);

        Console.ReadLine();
    }
}
           
手把手教你PInvoke

不要被這些表象給騙了,哪有說的這麼簡單。

我們看一下MessageBox 的原始簽名

手把手教你PInvoke

如果你像我這樣沒怎麼寫過c++,第一感覺一定是一臉懵逼,除了int和uint其他啥也沒看懂。

是以我們一步步來看如何将c++的MessageBox轉化為我們C#中的簽名

手把手PInvoke

  1. 首先打開Programming reference for Windows API -Microsoft Docs,找到目标函數

    MessageBox

    的介紹
  2. 我們可以在

    Requirements

    的DLL欄中看到

    User32.dll

    ,這個就是我們在

    DllImport

    中所需要的dll的名稱
手把手教你PInvoke

這樣為我們就可以寫出(其中?代表還未填寫的内容)

public class Win32
{
    [DllImport("user32.dll")]
    public static extern ? MessageBox(?);
}
           
  1. 接着我們從

    Syntax

    中找到函數簽名
手把手教你PInvoke
  1. 這裡比較麻煩的是4個參數的需要轉換為對應的托管類型,有時候還會涉及一些結構體和指針。

這裡我們先看一下

Parameters

手把手教你PInvoke

第一個是一個

HWND

類型,表示一個視窗句柄,

可以通過

HWND

=Handle to A Window來記憶

那麼在c#中我們可以使用

Intptr

類型,表示一個指針或者句柄

手把手教你PInvoke

第2,3個參數都是

LPCTSTR

LPCTSTR

= L‌ong P‌ointer to a C‌onst T‌CHAR String 是以這是一個字元串,我們此處使用

string

手把手教你PInvoke

最後一個是

UINT

,我們直接在c#中有對應的

uint

這麼一看是不是就更加能夠了解了呢。

實操

再來一個簡單的例子,我們期望擷取HID裝置的接口GUID

方法給到你們,是HidD_GetHidGuid

先看requirements.txt,發現DLL 是

Hid.dll

手把手教你PInvoke

接着是簽名和參數,

LPGUID

我們沒有提過,看解釋這邊是指向GUID的一個指針,是以我們使用

Guid

這個類型

手把手教你PInvoke

是以我們現在得到的結果如下

[DllImport("hid.dll")]
public static extern void HidD_GetHidGuid(Guid hidGuid);
           

不過注意,我們WindowsApi中簽名的參數類型是一個指針,現在我們傳遞的

Guid

隻是一個結構體

是以我們還需要将其以

引用

方式傳遞,通過添加

ref

是以最終形式就是

[DllImport("hid.dll")]
public static extern void HidD_GetHidGuid(ref Guid hidGuid);
           
var guid = Guid.Empty;

Win32.HidD_GetHidGuid(ref guid);

Console.WriteLine(guid);
           

運作可以看到結果如下

手把手教你PInvoke

Tip

有同學說,這麼說完了,我還是擔心會寫錯怎麼辦

沒關系,這裡給大家推薦一個網站pinvoke.net: the interop wiki!,裡面聚集了各種pinvoke的寫法,如果不清楚怎麼使用,可以去其中檢視

另外vs也帶有pinvoke的插件,使用方法可以參見呂毅同學的部落格使用 PInvoke.net Visual Studio Extension 輔助編寫 Win32 函數簽名 - walterlv

參考連結:

  • pinvoke.net: the interop wiki!
  • Platform Invoke Examples - Microsoft Docs
  • Passing Structures -Microsoft Docs
  • 使用 PInvoke.net Visual Studio Extension 輔助編寫 Win32 函數簽名 - walterlv
  • MessageBox function - Microsoft Docs
  • Programming reference for Windows API - Microsoft Docs
  • HidD_GetHidGuid function (hidsdi.h) - Windows drivers 0 - Microsoft Docs

本文會經常更新,請閱讀個人部落格原文: https://xinyuehtx.github.io/ ,以避免陳舊錯誤知識的誤導,同時有更好的閱讀體驗。

手把手教你PInvoke

本作品采用 知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協定 進行許可。歡迎轉載、使用、重新釋出,但務必保留文章署名黃騰霄(包含連結: https://xinyuehtx.github.io/ ),不得用于商業目的,基于本文修改後的作品務必以相同的許可釋出。如有任何疑問,請 與我聯系 。