在C#中經常需要調用一些API函數,那麼怎樣才能正確的調用API函數呢,如下:
一、調用API格式
//引用此名稱空間,簡化後面的代碼
usingSystem.Runtime.InteropServices;
...
//使用DllImportAttribute特性來引入api函數,注意聲明的是空方法,即方法體為空。
[DllImport("user32.dll")]
publicstaticexternReturnTypeFunctionName(typearg1,typearg2,...);
可以使用字段進一步說明特性,用逗號隔開,如:
[DllImport("kernel32",EntryPoint="GetVersionEx",SetLastError=true)]
DllImportAttribute特性的幾個公共字段如下:
1、CallingConvention:指示向非托管實現傳遞方法參數時所用的CallingConvention值。
CallingConvention.Cdecl:調用方清理堆棧。它使您能夠調用具有varargs的函數。
CallingConvention.StdCall:被調用方清理堆棧。它是從托管代碼調用非托管函數的默認約定。
2、CharSet:控制調用函數的名稱版本及指示如何向方法封送String參數。
此字段被設置為CharSet值之一。如果CharSet字段設置為Unicode,則所有字符串參數在傳遞到非托管實現之前都轉換成Unicode字符。這還導致向DLLEntryPoint的名稱中追加字母“W”。如果此字段設置為Ansi,則字符串將轉換成ANSI字符串,同時向DLLEntryPoint的名稱中追加字母“A”。大多數Win32API使用這種追加“W”或“A”的約定。如果CharSet設置為Auto,則這種轉換就是與平台有關的(在WindowsNT上為Unicode,在Windows98上為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,則匹配規則與平台有關(在WindowsNT上為Unicode,在Windows98上為Ansi)。如果ExactSpelling設置為true,則只有當DLL中存在“MyMethod”時才返回“MyMethod”。
3、EntryPoint:指示要調用的DLL入口點的名稱或序號。
如果你的方法名不想與api函數同名的話,一定要指定此參數,例如:
[DllImport("user32.dll",CharSet="CharSet.Auto",EntryPoint="MessageBox")]
publicstaticexternintMsgBox(IntPtrhWnd,stringtxt,stringcaption,inttype);
4、ExactSpelling:指示是否應修改非托管DLL中的入口點的名稱,以與CharSet字段中指定的CharSet值相對應。如果為true,則當DllImportAttribute.CharSet字段設置為CharSet的Ansi值時,向方法名稱中追加字母A,當DllImportAttribute.CharSet字段設置為CharSet的Unicode值時,向方法的名稱中追加字母W。此字段的默認值是false。
5、PreserveSig:指示托管方法簽名不應轉換成返回HRESULT、並且可能有一個對應於返回值的附加[out,retval]參數的非托管簽名。
6、SetLastError:指示被調用方在從屬性化方法返回之前將調用Win32APISetLastError。true指示調用方將調用SetLastError,默認為false。運行時封送拆收器將調用GetLastError並緩存返回的值,以防其被其他API調用重寫。用戶可通過調用GetLastWin32Error來檢索錯誤代碼。
二、參數類型:
1、數值型直接用對應的就可。(DWORD->int,或uint,WORD->Int16)
2、API中字符串指針類型->.net中string,或者數組,如byte[]
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)]
publicclassMySystemTime
{
[FieldOffset(0)]publicushortwYear;
[FieldOffset(2)]publicushortwMonth;
[FieldOffset(4)]publicushortwDayOfWeek;
[FieldOffset(6)]publicushortwDay;
[FieldOffset(8)]publicushortwHour;
[FieldOffset(10)]publicushortwMinute;
[FieldOffset(12)]publicushortwSecond;
[FieldOffset(14)]publicushortwMilliseconds;
}
例如:下面是API函數CreateNamedPipe的原函數:
WINBASEAPI
HANDLE
WINAPI
CreateNamedPipeW(
LPCWSTRlpName,
DWORDdwOpenMode,
DWORDdwPipeMode,
DWORDnMaxInstances,
DWORDnOutBufferSize,
DWORDnInBufferSize,
DWORDnDefaultTimeOut,
LPSECURITY_ATTRIBUTESlpSecurityAttributes
);
在C#中可這麼調用(封裝在類NamedPipeNative當中):
[DllImport("kernel32.dll",SetLastError=true)]
publicstaticexternIntPtrCreateNamedPipe(
StringlpName,
uintdwOpenMode,
uintdwPipeMode,
uintnMaxInstances,
uintnOutBufferSize,
uintnInBufferSize,
uintnDefaultTimeOut,
IntPtrpipeSecurityDescriptor
);
使用實例:
IntPtrm_HPipe=NamedPipeNative.CreateNamedPipe(m_PipeName,
NamedPipeNative.PIPE_ACCESS_DUPLEX,//數據雙工通信
NamedPipeNative.PIPE_TYPE_MESSAGE|NamedPipeNative.PIPE_WAIT,//字節流,並且阻塞
100,//最大實例個數
128,//流出數據緩沖大小
128,//流入數據緩沖大小
150,//超時,毫秒
IntPtr.Zero//安全信息
);
其中CreateNamedPipe函數,還有PIPE_ACCESS_DUPLEX,PIPE_TYPE_MESSAGE,PIPE_WAIT等字段都封裝在類NamedPipeNative當中,以便於調用。