平台調用服務

平台調用服務

平台調用服務通常指微軟公共語言運行時提供的跨平台調用方式。平台調用服務是公共語言基礎設施相關實現的一個特性。這一特性與微軟的公共語言運行時提供的較為類似,因此一般提到P/Invoke多數指微軟的.NET實現方案。這一方案能夠實現通過託管代碼訪問原生代碼。使用P/Invoke可以通過CLR來控制DLL的載入,以及將非託管代碼的數據類型轉換為託管數據類型

基本介紹

  • 中文名:平台調用服務
  • 外文名:Platform Invocation Service
  • 簡稱:P/Invoke
簡介,C#中的平台調用,調用格式,參數類型,如何保證使用託管對象的平台調用成功,

簡介

在Microsoft Windows作業系統中,Native API有時也是以COM接口方式來推出,像是ADSI,FSRM(File Server Resource Manager)等,通常是新的服務或是接口才會廣泛使用COM原生接口方式。因為.NET Framework的推行,Windows的應用程式接口被分為兩種,一種是遵循原本Windows API方式的,稱為Native API,另一種則是以.NET Framework為基礎開發的,稱為Managed API,例如Managed DirectX或是IIS Admin APIs等。
在Microsoft Windows作業系統中,若是通過VB或是.NET Framework訪問直接開放C函式的Native API時,則必須要利用平台調用服務方式訪問;若是訪問以COM方式開放的Native API時,若該API支持COM Automation規格時,即可利用COM Interop Services來訪問。
在受控代碼與非受控代碼進行互動時會產生一個事務(transition),這通常發生在使用平台調用服務(Platform Invocation Services),即P/Invoke平台調用是一種服務,它使託管代碼能夠調用 DLL 中實現的非託管函式。如調用系統的 API 或與 COM 對象打交道,通過 System.Runtime.InteropServices 命名空間。為了從託管代碼中調用非託管的DLL中函式,你要創建一個P/Invoke包裝(Wrapper)。一個P/Invoke包裝是一個.net兼 容的方法聲明,用來創建P/Invoke包裝的語法與創建託管方法的聲明語法本質上是一樣的。一個不同點是P/Invoke包裝不包含函式體,而只有方法 名、返回值類型和參數信息。並且,P/Invoke包裝使用了DllImport屬性。這個屬性是用來定位包含有目標函式的非託管的DLL。
例如:MessageBox在win32的頭檔案中的聲明:int WINAPI MessageBoxA(HWnd,LPCWSTR lpText,LPCWSTR lpCaption,UINT uType);那么要在C#中調用這個MessageBox時要這樣聲明:
using System.Runtime.InteropServices; public class Win32 { [DllImport("user32.dll")] public static extern int MessageBox(int hWnd, String text, String caption, uint type); } 
然後可以用常規的.net方法去調用這個MessageBox:
public class HelloWorld { public static void Main() { Win32.MessageBox(0, "Hello World", "Platform Invoke Sample", 0); } }

C#中的平台調用

調用格式

using System.Runtime.InteropServices; //引用此名稱空間,簡化後面的代碼...//使用DllImportAttribute特性來引入api函式,注意聲明的是空方法,即方法體為空。[DllImport("user32.dll")]public static extern ReturnType FunctionName(type arg1,type arg2,...);//調用時與調用其他方法並無區別 
可以使用欄位進一步說明特性,用逗號隔開,如:
[ DllImport( "kernel32", EntryPoint="GetVersionEx" )] 
DllImportAttribute特性的公共欄位如下:
1、CallingConvention 指示向非託管實現傳遞方法參數時所用的 CallingConvention 值。
CallingConvention.Cdecl : 調用方清理堆疊。它使您能夠調用具有 varargs 的函式。
CallingConvention.StdCall : 被調用方清理堆疊。它是從託管代碼調用非託管函式的默認約定。
2、CharSet 控制調用函式的名稱版本及指示如何向方法封送 String 參數。
此欄位被設定為 CharSet 值之一。如果 CharSet 欄位設定為 Unicode,則所有字元串參數在傳遞到非託管實現之前都轉換成 Unicode 字元。這還導致向 DLL EntryPoint 的名稱中追加字母“W”。如果此欄位設定為 Ansi,則字元串將轉換成 ANSI 字元串,同時向 DLL EntryPoint 的名稱中追加字母“A”。大多數 Win32 API 使用這種追加“W”或“A”的約定。如果 CharSet 設定為 Auto,則這種轉換就是與平台有關的(在 Windows NT 上為 Unicode,在 Windows 98 上為 Ansi)。CharSet 的默認值為 Ansi。CharSet 欄位也用於確定將從指定的 DLL 導入哪個版本的函式。CharSet.Ansi 和 CharSet.Unicode 的名稱匹配規則大不相同。
對於 Ansi 來說,如果將 EntryPoint 設定為“MyMethod”且它存在的話,則返回“MyMethod”。如果 DLL 中沒有“MyMethod”,但存在“MyMethodA”,則返回“MyMethodA”。對於 Unicode 來說則正好相反。如果將 EntryPoint 設定為“MyMethod”且它存在的話,則返回“MyMethodW”。如果 DLL 中不存在“MyMethodW”,但存在“MyMethod”,則返回“MyMethod”。如果使用的是 Auto,則匹配規則與平台有關(在 Windows NT 上為 Unicode,在 Windows 98 上為 Ansi)。如果 ExactSpelling 設定為 true,則只有當 DLL 中存在“MyMethod”時才返回“MyMethod”。
3、EntryPoint 指示要調用的 DLL 入口點的名稱或序號。
如果你的方法名不想與api函式同名的話,一定要指定此參數,例如:
[DllImport("user32.dll",CharSet="CharSet.Auto",EntryPoint="MessageBox")]public static extern int MsgBox(IntPtr hWnd,string txt,string caption, int type); 
4、ExactSpelling 指示是否應修改非託管 DLL 中的入口點的名稱,以與 CharSet 欄位中指定的 CharSet 值相對應。如果為 true,則當 DllImportAttribute.CharSet 欄位設定為 CharSet 的 Ansi 值時,向方法名稱中追加字母 A,當 DllImportAttribute.CharSet 欄位設定為 CharSet 的 Unicode 值時,向方法的名稱中追加字母 W。此欄位的默認值是 false。
5、PreserveSig 指示託管方法簽名不應轉換成返回 HRESULT、並且可能有一個對應於返回值的附加 [out, retval] 參數的非託管簽名。
6、SetLastError 指示被調用方在從屬性化方法返回之前將調用 Win32 API SetLastError。 true 指示調用方將調用 SetLastError,默認為 false。運行時封送拆收器將調用 GetLastError 並快取返回的值,以防其被其他 API 調用重寫。用戶可通過調用 GetLastWin32Error 來檢索錯誤代碼。

參數類型

1、數值型直接用對應的就可。(DWORD -> int , WORD -> Int16)
2、API中字元串指針類型 -> .net中string
3、API中句柄 (dWord) -> .net中IntPtr
4、API中結構 -> .net中結構或者類。注意這種情況下,要先用StructLayout特性限定聲明結構或類
公共語言運行庫利用StructLayoutAttribute控制類或結構的數據欄位在託管記憶體中的物理布局,即類或結構需要按某種方式排列。如果要將類傳遞給需要指定布局的非託管代碼,則顯式控制類布局是重要的。它的構造函式中用LayoutKind值初始化 StructLayoutAttribute 類的新實例。 LayoutKind.Sequential 用於強制將成員按其出現的順序進行順序布局。
LayoutKind.Explicit 用於控制每個數據成員的精確位置。利用 Explicit, 每個成員必須使用 FieldOffsetAttribute 指示此欄位在類型中的位置。如:
[StructLayout(LayoutKind.Explicit, Size=16, CharSet=CharSet.Ansi)]public class MySystemTime {[FieldOffset(0)]public ushort wYear; [FieldOffset(2)]public ushort wMonth;[FieldOffset(4)]public ushort wDayOfWeek; [FieldOffset(6)]public ushort wDay; [FieldOffset(8)]public ushort wHour; [FieldOffset(10)]public ushort wMinute; [FieldOffset(12)]public ushort wSecond; [FieldOffset(14)]public ushort wMilliseconds; } 
下面是針對API中OSVERSIONINFO結構,在.net中定義對應類或結構的例子:
/*********************************************** API中定義原結構聲明* OSVERSIONINFOA STRUCT* dwOSVersionInfoSize DWORD ?* dwMajorVersion DWORD ?* dwMinorVersion DWORD ?* dwBuildNumber DWORD ?* dwPlatformId DWORD ?* szCSDVersion BYTE 128 dup (?)* OSVERSIONINFOA ENDS** OSVERSIONINFO equ <OSVERSIONINFOA>*********************************************///.net中聲明為類[ StructLayout( LayoutKind.Sequential )] public class OSVersionInfo { public int OSVersionInfoSize;public int majorVersion; public int minorVersion;public int buildNumber;public int platformId;[ MarshalAs( UnmanagedType.ByValTStr, SizeConst=128 )] public String versionString;}//或者//.net中聲明為結構[ StructLayout( LayoutKind.Sequential )] public struct OSVersionInfo2 {public int OSVersionInfoSize;public int majorVersion; public int minorVersion;public int buildNumber;public int platformId;[ MarshalAs( UnmanagedType.ByValTStr, SizeConst=128 )] public String versionString;}
此例中用到MashalAs特性,它用於描述欄位、方法或參數的封送處理格式。用它作為參數前綴並指定目標需要的數據類型。例如,以下代碼將兩個參數作為數據類型長指針封送給 Windows API 函式的字元串 (LPStr):
[MarshalAs(UnmanagedType.LPStr)]String existingfile;[MarshalAs(UnmanagedType.LPStr)]String newfile; 
注意結構作為參數時候,一般前面要加上ref修飾符,否則會出現錯誤:對象的引用沒有指定對象的實例。
[ DllImport( "kernel32", EntryPoint="GetVersionEx" )] public static extern bool GetVersionEx2( ref OSVersionInfo2 osvi ); 

如何保證使用託管對象的平台調用成功

如果在調用平台 invoke 後的任何位置都未引用託管對象,則垃圾回收器可能將完成該託管對象。這將釋放資源並使句柄無效,從而導致平台invoke 調用失敗。用 HandleRef 包裝句柄可保證在平台 invoke 調用完成前,不對託管對象進行垃圾回收。
例如下面:
FileStream fs = new FileStream( "a.txt", FileMode.Open );StringBuilder buffer = new StringBuilder( 5 );int read = 0;ReadFile(fs.Handle, buffer, 5, out read, 0 ); //調用Win API中的ReadFile函式  
由於fs是託管對象,所以有可能在平台調用還未完成時候被垃圾資源回收筒回收。將檔案流的句柄用HandleRef包裝後,就能避免被垃圾站回收:
[ DllImport( "Kernel32.dll" )]public static extern bool ReadFile( HandleRef hndRef, StringBuilder buffer, int numberOfBytesToRead, out int numberOfBytesRead, ref Overlapped flag );............FileStream fs = new FileStream( "HandleRef.txt", FileMode.Open );HandleRef hr = new HandleRef( fs, fs.Handle );StringBuilder buffer = new StringBuilder( 5 );int read = 0;// platform invoke will hold reference to HandleRef until call endsReadFile( hr, buffer, 5, out read, 0 );

相關詞條

熱門詞條

聯絡我們