標籤:
在Win32 Api中有很多添加/設定函數在參數上支援多種不同類型的結構體。這些參數定義為LPVOID* 或者LPBYTE,LPVOID*一般由Win32 分配記憶體空間,在C#從通過System.IntPtr進行遍曆[1]。LPBYTE空間在函數調用的外部進行分配,之所以定義為LPBYTE,是因為這些結構體可能並不相同——屬性、大小不同。那麼在C#中如何處理這種LPBYTE接收多種結構體引用的Win32 Api?
先看一個Win 32 API的定義:
//The NetUserSetInfo function sets the parameters of a user account.//Syntax [2]NET_API_STATUS NetUserSetInfo( _In_ LPCWSTR servername, _In_ LPCWSTR username, _In_ DWORD level, _In_ LPBYTE buf, _Out_ LPDWORD parm_err);//LPBYTE支援的結構體有二十多種/**buf部分類型說明0Specifies the user account name. The buf parameter points to a USER_INFO_0 structure. Use this structure to specify a new group name. For more information, see the following Remarks section.1Specifies detailed information about the user account. The buf parameter points to a USER_INFO_1 structure.
....
1005Specifies a user privilege level. The buf parameter points to a USER_INFO_1005 structure.
.....**/
LPBYTE buf支援的類型多達二十多個,面對這種情況,一般可以有兩種解決辦法。LPBYTE是一個位元組數組(棧或者堆)指標,在C#中對應的類型為System.UInt[]。但在此函數中,LPBYTE buf是指向結構體首地址的地址指標。所以第一種方法是直接傳入IntPtr。IntPtr是一個神奇的類型,MSDN是這樣介紹的[3]:
A platform-specific type that is used to represent a pointer or a handle.用於表示指標或控制代碼的平台特定類型。在Remark是這樣說的:The IntPtr type can be used by languages that support pointers, and as a common means of referring to data between languages that do and do not support pointers.MSDN機器翻譯:IntPtr 語言中所支援的指標,並作為一種常見的方式來指代語言,並不支援指標之間的資料,則可以使用類型。
MSDN機器翻譯我個人認為是錯誤的,我個人翻譯為:
IntPtr可以被支援指標,或者那些不支援指標,而使用通常意義上的資料的引用做指標所做的事的各種(編程)語言使用。(.NET支援多種語言,有些語言支援指標。)
基於以上,第一種方法這是使用System.IntPtr來類型替換LPBYTE,支援多種變化的結構體Buf。那麼該函數在C#中的定義即為:
[DllImport("Netapi32.dll", CharSet = CharSet.Unicode)] internal static extern uint NetUserSetInfo( [MarshalAs(UnmanagedType.LPWStr)] string servername, [MarshalAs(UnmanagedType.LPWStr)] string username, uint level, IntPtr buf, uint parm_err);
第二種方法基於Win32 Api函數的“重載”——類似於通過參數實現的重載。如果在使用某一個函數時,不考慮其通用性而考慮具體性,可以直接將函數定義為具體的結構體類型。比如如下定義:
[DllImport("Netapi32.dll", CharSet = CharSet.Unicode)] internal static extern uint NetUserSetInfo( [MarshalAs(UnmanagedType.LPWStr)] string servername, [MarshalAs(UnmanagedType.LPWStr)] string username, uint level, ref USER_INFO_1003 buf, uint parm_err); [DllImport("Netapi32.dll", CharSet = CharSet.Unicode)] internal static extern uint NetUserSetInfo( [MarshalAs(UnmanagedType.LPWStr)] string servername, [MarshalAs(UnmanagedType.LPWStr)] string username, uint level, ref USER_INFO_1017 buf, uint parm_err);
這兩個函數在C#中屬於重載函數,支援USER_INFO_1003 和 USER_INFO_1017兩個類型。當需要較多的重載函數時,這種方法稍顯冗餘,使用IntPtr更加有彈性。
畢竟,Win32 只是想得到結構體的地址指標。使用具體類型的引用(ref)或者IntPtr都可以做到。
參考:
[1] 遍曆LPVOID*, http://www.cnblogs.com/jjseen/p/win32_api.html
[2] NetSetUserInfo, https://msdn.microsoft.com/zh-cn/data/aa370659(v=vs.100)
[3] IntPtr結構, https://msdn.microsoft.com/zh-cn/library/system.intptr(v=vs.110).aspx
C#面對“重載”的Win 32 函數