C#經過PInvoke方式調用Windows API

Tag:

版權聲明:轉載時請以超連接形式標明文章原始出處和做者信息及本聲明
http://www.blogbus.com/rasaba-logs/36050546.html

html

      Api函數是構築Windws應用程序的基石,每一種Windows應用程序開發工具,它提供的底層函數都間接或直接地調用了Windows API函數,同時爲了實現功能擴展,通常也都提供了調用WindowsAPI函數的接口, 也就是說具有調用動態鏈接庫的能力。Visual C#和其它開發工具同樣也可以調用動態連接庫的API函數。.NET框架自己提供了這樣一種服務,容許受管轄的代碼調用動態連接庫中實現的非受管轄函數,包括操做系統提供的Windows API函數。它可以定位和調用輸出函數,根據須要,組織其各個參數(整型、字符串類型、數組、和結構等等)跨越互操做邊界。

下面以C#爲例簡單介紹調用API的基本過程: 

動態連接庫函數的聲明 

  動態連接庫函數使用前必須聲明,相對於VB,C#函數聲明顯得更加羅嗦,前者經過 Api Viewer粘貼之後,能夠直接使用,然後者則須要對參數做些額外的變化工做。

  動態連接庫函數聲明部分通常由下列兩部分組成,一是函數名或索引號,二是動態連接庫的文件名。 
  譬如,你想調用User32.DLL中的MessageBox函數,咱們必須指明函數的名字MessageBoxA或MessageBoxW,以及庫名字User32.dll,咱們知道Win32 API對每個涉及字符串和字符的函數通常都存在兩個版本,單字節字符的ANSI版本和雙字節字符的UNICODE版本。

 下面是一個調用API函數的例子: 
[DllImport("KERNEL32.DLL", EntryPoint="MoveFileW", SetLastError=true, 
CharSet=CharSet.Unicode, ExactSpelling=true, 
CallingConvention=CallingConvention.StdCall)] 
public static extern bool MoveFile(String src, String dst);

    其中入口點EntryPoint標識函數在動態連接庫的入口位置,在一個受管轄的工程中,目標函數的原始名字和序號入口點不只標識一個跨越互操做界限的函數。並且,你還能夠把這個入口點映射爲一個不一樣的名字,也就是對函數進行重命名。重命名能夠給調用函數帶來種種便利,經過重命名,一方面咱們不用爲函數的大小寫傷透腦筋,同時它也能夠保證與已有的命名規則保持一致,容許帶有不一樣參數類型的函數共存,更重要的是它簡化了對ANSI和Unicode版本的調用。CharSet用於標識函數調用所採用的是Unicode或是ANSI版本,ExactSpelling=false將告訴編譯器,讓編譯器決定使用Unicode或者是Ansi版本。其它的參數請參考MSDN在線幫助.

    在C#中,你能夠在EntryPoint域經過名字和序號聲明一個動態連接庫函數,若是在方法定義中使用的函數名與DLL入口點相同,你不須要在EntryPoint域顯示聲明函數。不然,你必須使用下列屬性格式指示一個名字和序號。

[DllImport("dllname", EntryPoint="Functionname")] 
[DllImport("dllname", EntryPoint="#123")] 
值得注意的是,你必須在數字序號前加「#」 
下面是一個用MsgBox替換MessageBox名字的例子: 
[C#] 
using System.Runtime.InteropServices;

public class Win32 { 
[DllImport("user32.dll", EntryPoint="MessageBox")] 
public static extern int MsgBox(int hWnd, String text, String caption, uint type); 

      許多受管轄的動態連接庫函數指望你可以傳遞一個複雜的參數類型給函數,譬如一個用戶定義的結構類型成員或者受管轄代碼定義的一個類成員,這時你必須提供額外的信息格式化這個類型,以保持參數原有的佈局和對齊。

      C#提供了一個StructLayoutAttribute類,經過它你能夠定義本身的格式化類型,在受管轄代碼中,格式化類型是一個用StructLayoutAttribute說明的結構或類成員,經過它可以保證其內部成員預期的佈局信息。佈局的選項共有三種:

佈局選項 
描述 
LayoutKind.Automatic 
爲了提升效率容許運行態對類型成員從新排序。 
注意:永遠不要使用這個選項來調用不受管轄的動態連接庫函數。 
LayoutKind.Explicit 
對每一個域按照FieldOffset屬性對類型成員排序 
LayoutKind.Sequential 
對出如今受管轄類型定義地方的不受管轄內存中的類型成員進行排序。api


傳遞結構成員 
下面的例子說明如何在受管轄代碼中定義一個點和矩形類型,並做爲一個參數傳遞給User32.dll庫中的PtInRect函數, 
函數的不受管轄原型聲明以下: 
BOOL PtInRect(const RECT *lprc, POINT pt); 
注意你必須經過引用傳遞Rect結構參數,由於函數須要一個Rect的結構指針。 
[C#] 
using System.Runtime.InteropServices;

[StructLayout(LayoutKind.Sequential)] 
public struct Point { 
public int x; 
public int y; 
}

[StructLayout(LayoutKind.Explicit] 
public struct Rect { 
[FieldOffset(0)] public int left; 
[FieldOffset(4)] public int top; 
[FieldOffset(8)] public int right; 
[FieldOffset(12)] public int bottom; 
}

class Win32API { 
[DllImport("User32.dll")] 
public static extern Bool PtInRect(ref Rect r, Point p); 

相似你能夠調用GetSystemInfo函數得到系統信息: 
using System.Runtime.InteropServices; 
[StructLayout(LayoutKind.Sequential)] 
public struct SYSTEM_INFO { 
public uint dwOemId; 
public uint dwPageSize; 
public uint lpMinimumApplicationAddress; 
public uint lpMaximumApplicationAddress; 
public uint dwActiveProcessorMask; 
public uint dwNumberOfProcessors; 
public uint dwProcessorType; 
public uint dwAllocationGranularity; 
public uint dwProcessorLevel; 
public uint dwProcessorRevision; 
}數組

[DllImport("kernel32")] 
static extern void GetSystemInfo(ref SYSTEM_INFO pSI);

SYSTEM_INFO pSI = new SYSTEM_INFO(); 
GetSystemInfo(ref pSI);

類成員的傳遞 
      一樣只要類具備一個固定的類成員佈局,你也能夠傳遞一個類成員給一個不受管轄的動態連接庫函數,下面的例子主要說明如何傳遞一個sequential順序定義的MySystemTime類給User32.dll的GetSystemTime函數, 函數用C/C++調用規範以下:

void GetSystemTime(SYSTEMTIME* SystemTime); 
不像傳值類型,類老是經過引用傳遞參數. 
[C#] 
[StructLayout(LayoutKind.Sequential)] 
public class MySystemTime { 
public ushort wYear; 
public ushort wMonth; 
public ushort wDayOfWeek; 
public ushort wDay; 
public ushort wHour; 
public ushort wMinute; 
public ushort wSecond; 
public ushort wMilliseconds; 

class Win32API { 
[DllImport("User32.dll")] 
public static extern void GetSystemTime(MySystemTime st); 
}框架

 

回調函數的傳遞:函數

      從受管轄的代碼中調用大多數動態連接庫函數,你只需建立一個受管轄的函數定義,而後調用它便可,這個過程很是直接。
    若是一個動態連接庫函數須要一個函數指針做爲參數,你還須要作如下幾步: 
     首先,你必須參考有關這個函數的文檔,肯定這個函數是否須要一個回調;第二,你必須在受管轄代碼中建立一個回調函數;最後,你能夠把指向這個函數的指針做爲一個參數創遞給DLL函數.

回調函數及其實現: 
回調函數常常用在任務須要重複執行的場合,譬如用於枚舉函數,譬如Win32 API 中的EnumFontFamilies(字體枚舉), EnumPrinters(打印機), EnumWindows (窗口枚舉)函數. 下面以窗口枚舉爲例,談談如何經過調用EnumWindow 函數遍歷系統中存在的全部窗口

分下面幾個步驟: 
1. 在實現調用前先參考函數的聲明 
BOOL EnumWindows(WNDENUMPROC lpEnumFunc, LPARMAM IParam) 
顯然這個函數須要一個回調函數地址做爲參數. 
2. 建立一個受管轄的回調函數,這個例子聲明爲表明類型(delegate),也就是咱們所說的回調,它帶有兩個參數hwnd和lparam,第一個參數是一個窗口句柄,第二個參數由應用程序定義,兩個參數均爲整形。

  當這個回調函數返回一個非零值時,標示執行成功,零則暗示失敗,這個例子老是返回True值,以便持續枚舉。 
3. 最後建立以表明對象(delegate),並把它做爲一個參數傳遞給EnumWindows 函數,平臺會自動地 把表明轉化成函數可以識別的回調格式。

[C#] 
using System; 
using System.Runtime.InteropServices;

public delegate bool CallBack(int hwnd, int lParam);

public class EnumReportApp {

[DllImport("user32")] 
public static extern int EnumWindows(CallBack x, int y);

public static void Main() 

CallBack myCallBack = new CallBack(EnumReportApp.Report); 
EnumWindows(myCallBack, 0); 
}

public static bool Report(int hwnd, int lParam) { 
Console.Write("窗口句柄爲"); 
Console.WriteLine(hwnd); 
return true; 

}


指針類型參數傳遞: 
  在Windows API函數調用時,大部分函數採用指針傳遞參數,對一個結構變量指針,咱們除了使用上面的類和結構方法傳遞參數以外,咱們有時還能夠採用數組傳遞參數。

 下面這個函數經過調用GetUserName得到用戶名 
BOOL GetUserName( 
LPTSTR lpBuffer, // 用戶名緩衝區 
LPDWORD nSize // 存放緩衝區大小的地址指針 
); 
  
[DllImport("Advapi32.dll", 
EntryPoint="GetComputerName", 
ExactSpelling=false, 
SetLastError=true)] 
static extern bool GetComputerName ( 
[MarshalAs(UnmanagedType.LPArray)] byte[] lpBuffer, 
[MarshalAs(UnmanagedType.LPArray)] Int32[] nSize ); 
  這個函數接受兩個參數,char * 和int *,由於你必須分配一個字符串緩衝區以接受字符串指針,你可使用String類代替這個參數類型,固然你還能夠聲明一個字節數組傳遞ANSI字符串,一樣你也能夠聲明一個只有一個元素的長整型數組,使用數組名做爲第二個參數。上面的函數能夠調用以下:

byte[] str=new byte[20]; 
Int32[] len=new Int32[1]; 
len[0]=20; 
GetComputerName (str,len); 
MessageBox.Show(System.Text.Encoding.ASCII.GetString(str)); 

最後須要提醒的是,每一種方法使用前必須在文件頭加上: 
using System.Runtime.InteropServices;
工具

相關文章
相關標籤/搜索