C#調(diào)用動態(tài)庫
一、引言
“為什么我們需要掌握互操作技術(shù)的呢?” 對于這個問題的解釋就是——掌握了.NET平臺下的互操作性技術(shù)可以幫助我們在.NET中調(diào)用非托管的dll和COM組件。
。.NET 平臺下提供了3種互操作性的技術(shù):
- Platform Invoke(P/Invoke),即平臺調(diào)用,主要用于調(diào)用C庫函數(shù)和Windows API
- C++ Introp, 主要用于Managed C++(托管C++)中調(diào)用C++類庫
- COM Interop, 主要用于在.NET中調(diào)用COM組件和在COM中使用.NET程序集。
二、平臺調(diào)用
使用平臺調(diào)用的技術(shù)可以在托管代碼中調(diào)用動態(tài)鏈接庫(Dll)中實現(xiàn)的非托管函數(shù),如Win32 Dll和C/C++ 創(chuàng)建的dll。
2.1 在托管代碼中通過平臺調(diào)用來調(diào)用非托管代碼的步驟
(1). 獲得非托管函數(shù)的信息,即dll的名稱,需要調(diào)用的非托管函數(shù)名等信息
(2). 在托管代碼中對非托管函數(shù)進(jìn)行聲明,并且附加平臺調(diào)用所需要屬性
(3). 在托管代碼中直接調(diào)用第二步中聲明的托管函數(shù)
平臺調(diào)用的過程可以通過下圖更好地理解:
2.2、如何使用平臺調(diào)用Win32 函數(shù)——從實例開始
第一步就需要知道非托管函數(shù)聲明,為了找到需要需要調(diào)用的非托管函數(shù),可以借助兩個工具——Visual Studio自帶的dumpbin.exe和depends.exe.
- dumpbin.exe 是一個命令行工具,可以用于查看從非托管DLL中導(dǎo)出的函數(shù)等信息,可以通過打開Visual Studio 2010 Command Prompt(中文版為Visual Studio 命令提示(2010)),然后切換到DLL所在的目錄,輸入 dummbin.exe/exports dllName, 如 dummbin.exe/exports User32.dll 來查看User32.dll中的函數(shù)聲明,關(guān)于更多命令的參數(shù)可以參看MSDN;
- 然而 depends.exe是一個可視化界面工具,大家可以從 “VS安裝目錄\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\Tools\Bin\” 這個路徑找到,然后雙擊 depends.exe 就可以出來一個可視化界面(如果某些人安裝的VS沒有附帶這個工具,也可以從官方網(wǎng)站下載:http://www.dependencywalker.com/),如下圖:
上圖中 用紅色標(biāo)示出 MessageBox 有兩個版本,而MessageBoxA 代表的就是ANSI版本,而MessageBoxW 代筆的就是Unicode版本,這也是上面所說的依據(jù)。下面就看看 MessageBox的C++聲明的(更多的函數(shù)的定義大家可以從MSDN中找到,這里提供MessageBox的定義在MSDN中的鏈接:http://msdn.microsoft.com/en-us/library/windows/desktop/ms645505(v=vs.85).aspx ):
int WINAPI MessageBox( _In_opt_ HWND hWnd, _In_opt_ LPCTSTR lpText, _In_opt_ LPCTSTR lpCaption, _In_ UINT uType );
現(xiàn)在已經(jīng)知道了需要調(diào)用的Win32 API 函數(shù)的定義聲明,下面就依據(jù)平臺調(diào)用的步驟,在.NET 中實現(xiàn)對該非托管函數(shù)的調(diào)用,下面就看看.NET中的代碼的:
using System; // 使用平臺調(diào)用技術(shù)進(jìn)行互操作性之前,首先需要添加這個命名空間 using System.Runtime.InteropServices; namespace 平臺調(diào)用Demo { class Program { // 在托管代碼中對非托管函數(shù)進(jìn)行聲明,并且附加平臺調(diào)用所需要屬性 在默認(rèn)情況下,CharSet為CharSet.Ansi // 指定調(diào)用哪個版本的方法有兩種——通過DllImport屬性的CharSet字段和通過EntryPoint字段指定 在托管函數(shù)中聲明注意一定要加上 static 和extern 這兩個關(guān)鍵字 //第一種指定方式,通過CharSet字段指定,在默認(rèn)情況下CharSet為CharSet.Ansi [DllImport("user32.dll")] public static extern int MessageBox(IntPtr hWnd, String text, String caption, uint type); [DllImport("user32.dll")] public static extern int MessageBoxA(IntPtr hWnd, String text, String caption, uint type); // [DllImport("user32.dll", CharSet = CharSet.Unicode)] public static extern int // MessageBox(IntPtr hWnd, String text, String caption, uint type); [DllImport("user32.dll", CharSet = CharSet.Unicode)] public static extern int MessageBoxW(IntPtr hWnd, String text, String caption, uint type); // 通過EntryPoint字段指定 [DllImport("user32.dll", EntryPoint = "MessageBoxA")] public static extern int MessageBox3(IntPtr hWnd, String text, String caption, uint type); [DllImport("user32.dll", EntryPoint = "MessageBoxW", CharSet = CharSet.Unicode)] public static extern int MessageBox4(IntPtr hWnd, String text, String caption, uint type); static void Main(string[] args) { // 在托管代碼中直接調(diào)用聲明的托管函數(shù) 使用CharSet字段指定的方式,要求在托管代碼中聲明的函數(shù)名必須與非托管函數(shù)名一樣 否則就會出現(xiàn)找不到入口點的運行時錯誤 // 下面的調(diào)用都可以運行正確 MessageBox(new IntPtr(0), "Learning Hard", "歡迎", 0); MessageBoxA(new IntPtr(0), "Learning Hard", "歡迎", 0); MessageBoxW(new IntPtr(0), "Learning Hard", "歡迎", 0); //使用指定函數(shù)入口點的方式調(diào)用,OK MessageBox3(new IntPtr(0), "Learning Hard", "歡迎", 0); MessageBox4(new IntPtr(0), "Learning Hard", "歡迎", 0); } } }
2.3使用平臺調(diào)用技術(shù)中,還需要注意下面4點
(1). DllImport屬性的ExactSpelling字段如果設(shè)置為true時,則在托管代碼中聲明的函數(shù)名必須與要調(diào)用的非托管函數(shù)名完全一致,因為從ExactSpelling字面意思可以看出為 "準(zhǔn)確拼寫"的意思,當(dāng)ExactSpelling設(shè)置為true時,此時會改變平臺調(diào)用的行為,此時平臺調(diào)用只會根據(jù)根函數(shù)名進(jìn)行搜索,而找不到的時候不會添加 A或者W來進(jìn)行再搜索,.
[DllImport("user32.dll", ExactSpelling=true)] public static extern int MessageBox(IntPtr hWnd, String text, String caption, uint type);
(2). 如果采用設(shè)置CharSet的值來控制調(diào)用函數(shù)的版本時,則需要在托管代碼中聲明的函數(shù)名必須與根函數(shù)名一致,否則也會調(diào)用出錯
[DllImport("user32.dll")] public static extern int MessageBox1(IntPtr hWnd, String text, String caption, uint type);
(3). 如果通過指定DllImport屬性的EntryPoint字段的方式來調(diào)用函數(shù)版本時,此時必須相應(yīng)地指定與之匹配的CharSet設(shè)置,意思就是——如果指定EntryPoint為 MessageBoxW,那么必須將CharSet指定為CharSet.Unicode,如果指定EntryPoint為 MessageBoxA,那么必須將CharSet指定為CharSet.Ansi或者不指定,因為 CharSet默認(rèn)值就是Ansi。
(4). CharSet還有一個可選字段為——CharSet.Auto, 如果把CharSet字段設(shè)置為CharSet.Auto,則平臺調(diào)用會針對目標(biāo)操作系統(tǒng)適當(dāng)?shù)刈詣臃馑妥址?。?Windows NT、Windows 2000、Windows XP 和 Windows Server 2003 系列上,默認(rèn)值為 Unicode;在 Windows 98 和 Windows Me 上,默認(rèn)值為 Ansi。
2.3、獲得Win32函數(shù)的錯誤信息
捕捉由托管定義導(dǎo)致的異常演示代碼:
try { MessageBox1(new IntPtr(0), "Learning Hard", "歡迎", 0); } catch (DllNotFoundException dllNotFoundExc) { Console.WriteLine("DllNotFoundException 異常發(fā)生,異常信息為: " + dllNotFoundExc.Message); } catch (EntryPointNotFoundException entryPointExc) { Console.WriteLine("EntryPointNotFoundException 異常發(fā)生,異常信息為: " + entryPointExc.Message); }
捕獲由Win32函數(shù)本身返回異常的演示代碼如下:要想獲得在調(diào)用Win32函數(shù)過程中出現(xiàn)的錯誤信息,首先必須將DllImport屬性的SetLastError字段設(shè)置為true,只有這樣,平臺調(diào)用才會將最后一次調(diào)用Win32產(chǎn)生的錯誤碼保存起來,然后會在托管代碼調(diào)用Win32失敗后,通過Marshal類的靜態(tài)方法GetLastWin32Error獲得由平臺調(diào)用保存的錯誤碼,從而對錯誤進(jìn)行相應(yīng)的分析和處理。
class Program { // Win32 API // DWORD WINAPI GetFileAttributes( // _In_ LPCTSTR lpFileName //); // 在托管代碼中對非托管函數(shù)進(jìn)行聲明 [DllImport("Kernel32.dll",SetLastError=true,CharSet=CharSet.Unicode)] public static extern uint GetFileAttributes(string filename); static void Main(string[] args) { // 試圖獲得一個不存在文件的屬性 // 此時調(diào)用Win32函數(shù)會發(fā)生錯誤 GetFileAttributes("FileNotexist.txt"); // 在應(yīng)用程序的Bin目錄下存在一個test.txt文件,此時調(diào)用會成功 //GetFileAttributes("test.txt"); // 獲得最后一次獲得的錯誤 int lastErrorCode = Marshal.GetLastWin32Error(); // 將Win32的錯誤碼轉(zhuǎn)換為托管異常 //Win32Exception win32exception = new Win32Exception(); Win32Exception win32exception = new Win32Exception(lastErrorCode); if (lastErrorCode != 0) { Console.WriteLine("調(diào)用Win32函數(shù)發(fā)生錯誤,錯誤信息為 : {0}", win32exception.Message); } else { Console.WriteLine("調(diào)用Win32函數(shù)成功,返回的信息為: {0}", win32exception.Message); } Console.Read(); } }
2.4 數(shù)據(jù)封送
數(shù)據(jù)封送是——在托管代碼中對非托管函數(shù)進(jìn)行互操作時,需要通過方法的參數(shù)和返回值在托管內(nèi)存和非托管內(nèi)存之間傳遞數(shù)據(jù)的過程,數(shù)據(jù)封送處理的過程是由CLR(公共語言運行時)的封送處理服務(wù)(即封送拆送器)完成的。
封送時需要處理的數(shù)據(jù)類型分為兩種——可直接復(fù)制到本機(jī)結(jié)構(gòu)中的類型(blittable)和非直接復(fù)制到本機(jī)結(jié)構(gòu)中的類型(non-bittable)。
2.4.1 可直接復(fù)制到本機(jī)結(jié)構(gòu)中的類型
把在托管內(nèi)存和非托管內(nèi)存中有相同表現(xiàn)形式的數(shù)據(jù)類型稱為——可直接復(fù)制到本機(jī)結(jié)構(gòu)中的類型,這些數(shù)據(jù)類型不需要封送拆送器進(jìn)行任何特殊的處理就可以在托管和非托管代碼之間傳遞,
下面列出一些課直接復(fù)制到本機(jī)結(jié)構(gòu)中的簡單數(shù)據(jù)類型:
Windows 數(shù)據(jù)類型 | 非托管數(shù)據(jù)類型 | 托管數(shù)據(jù)類型 | 托管數(shù)據(jù)類型解釋 |
BYTE/Uchar/UInt8 | unsigned char | System.Byte | 無符號8位整型 |
Sbyte/Char/Int8 | char | System.SByte | 有符號8位整型 |
Short/Int16 | short | System.Int16 | 有符號16位整型 |
USHORT/WORD/UInt16/WCHAR | unsigned short | System.UInt16 | 無符號16位整型 |
Bool/HResult/Int/Long | long/int | System.Int32 | 有符號32位整型 |
DWORD/ULONG/UINT | unsigned long/unsigned int | System.UInt32 | 無符號32位整型 |
INT64/LONGLONG | _int64 | System.Int64 | 有符號64位整型 |
UINT64/DWORDLONG/ULONGLONG | _uint64 | System.UInt64 | 無符號64位整型 |
INT_PTR/hANDLE/wPARAM | void*/int或_int64 | System.IntPtr | 有符號指針類型 |
HANDLE | void* | System.UIntPtr | 無符號指針類型 |
FLOAT | float | System.Single | 單精度浮點數(shù) |
DOUBLE | double | System.Double | 雙精度浮點數(shù) |
除了上表列出來的簡單類型之外,還有一些復(fù)制類型也屬于可直接復(fù)制到本機(jī)結(jié)構(gòu)中的數(shù)據(jù)類型:
(1) 數(shù)據(jù)元素都是可直接復(fù)制到本機(jī)結(jié)構(gòu)中的一元數(shù)組,如整數(shù)數(shù)組,浮點數(shù)組等
(2)只包含可直接復(fù)制到本機(jī)結(jié)構(gòu)中的格式化值類型
(3)成員變量全部都是可復(fù)制到本機(jī)結(jié)構(gòu)中的類型且作為格式化類型封送的類
上面提到的格式化指的是——在類型定義時,成員的內(nèi)存布局在聲明時就明確指定的類型。在代碼中用StructLayout屬性修飾被指定的類型,并將StructLayout的LayoutKind屬性設(shè)置為Sequential或Explicit,例如:
using System.Runtime.InteropServices; // 下面的結(jié)構(gòu)體也屬于可直接復(fù)制到本機(jī)結(jié)構(gòu)中的類型 [StructLayout(LayoutKind.Sequential)] public struct Point { public int x; public int y; }
2.4.2 非直接復(fù)制到本機(jī)結(jié)構(gòu)中的類型
對于這種類型,封送器需要對它們進(jìn)行相應(yīng)的類型轉(zhuǎn)換之后再復(fù)制到被調(diào)用的函數(shù)中,下面列出一些非直接復(fù)制到本機(jī)結(jié)構(gòu)中的數(shù)據(jù)類型:
Windows 數(shù)據(jù)類型 | 非托管數(shù)據(jù)類型 | 托管數(shù)據(jù)類型 | 托管數(shù)據(jù)類型解釋 |
Bool | bool | System.Boolean | 布爾類型 |
WCHAR/TCHAR | char/ wchar_t | System.Char | ANSI字符/Unicode字符 |
LPCSTR/LPCWSTR/LPCTSTR/LPSTR/LPWSTR/LPTSTR | const char*/const wchar_t*/char*/wchar_t* | System.String | ANSI字符串/Unicode字符串,如果非托管代碼不需要更新此字符串時,此時用String類型在托管代碼中聲明字符串類型 |
LPSTR/LPWSTR/LPTSTR | Char*/wchar_t* | System.StringBuilder | ANSI字符串/Unicode字符串,如果非托管代碼需要更新此字符串,然后把更新的字符串傳回托管代碼中,此時用StringBuilder類型在托管代碼中聲明字符串 |
除了上表中列出的類型之外,還有很多其他類型屬于非直接復(fù)制到本機(jī)結(jié)構(gòu)中的類型,例如其他指針類型和句柄類型等。
2.4.3、封送字符串的處理
封送作為返回值的字符串,下面是一段演示代碼,代碼中主要是調(diào)用Win32 GetTempPath函數(shù)來獲得返回臨時路徑,此時拆送器就需要把返回的字符串封送回托管代碼中。使用System.StringBuilder托管數(shù)據(jù)類型。
// 托管函數(shù)中的返回值封送回托管函數(shù)的例子 class Program { // Win32 GetTempPath函數(shù)的定義如下: //DWORD WINAPI GetTempPath( // _In_ DWORD nBufferLength, // _Out_ LPTSTR lpBuffer //); // 主要是注意如何在托管代碼中定義該函數(shù)原型 [DllImport("Kernel32.dll", CharSet = CharSet.Unicode, SetLastError=true)] public static extern uint GetTempPath(int bufferLength, StringBuilder buffer); static void Main(string[] args) { StringBuilder buffer = new StringBuilder(300); uint tempPath=GetTempPath(300, buffer); string path = buffer.ToString(); if (tempPath == 0) { int errorcode =Marshal.GetLastWin32Error(); Win32Exception win32expection = new Win32Exception(errorcode); Console.WriteLine("調(diào)用非托管函數(shù)發(fā)生異常,異常信息為:" +win32expection.Message); } Console.WriteLine("調(diào)用非托管函數(shù)成功。"); Console.WriteLine("Temp 路徑為:" + buffer); Console.Read(); } }
2.4.4、封送結(jié)構(gòu)體的處理
在我們實際調(diào)用Win32 API函數(shù)時,經(jīng)常需要封送結(jié)構(gòu)體和類等復(fù)制類型,下面就以Win32 函數(shù)GetVersionEx為例子來演示如何對作為參數(shù)的結(jié)構(gòu)體進(jìn)行封送處理。
下面是GetVersionEx非托管定義(更多關(guān)于該函數(shù)的信息可以參看MSDN鏈接:http://msdn.microsoft.com/en-us/library/ms885648.aspx ):
BOOL GetVersionEx( LPOSVERSIONINFO lpVersionInformation );
參數(shù)lpVersionInformation是一個指向 OSVERSIONINFO結(jié)構(gòu)體的指針類型,所以我們在托管代碼中為函數(shù)GetVersionEx函數(shù)之前,必須知道 OSVERSIONINFO結(jié)構(gòu)體的非托管定義,然后再在托管代碼中定義一個等價的結(jié)構(gòu)體類型作為參數(shù)。以下是OSVERSIONINFO結(jié)構(gòu)體的非托管定義:
typedef struct _OSVERSIONINFO{ DWORD dwOSVersionInfoSize; //在使用GetVersionEx之前要將此初始化為結(jié)構(gòu)的大小 DWORD dwMajorVersion; //系統(tǒng)主版本號 DWORD dwMinorVersion; //系統(tǒng)次版本號 DWORD dwBuildNumber; //系統(tǒng)構(gòu)建號 DWORD dwPlatformId; //系統(tǒng)支持的平臺 TCHAR szCSDVersion[128]; //系統(tǒng)補丁包的名稱 WORD wServicePackMajor; //系統(tǒng)補丁包的主版本 WORD wServicePackMinor; //系統(tǒng)補丁包的次版本 WORD wSuiteMask; //標(biāo)識系統(tǒng)上的程序組 BYTE wProductType; //標(biāo)識系統(tǒng)類型 BYTE wReserved; //保留,未使用 } OSVERSIONINFO;
知道了OSVERSIONINFO結(jié)構(gòu)體在非托管代碼中的定義之后, 現(xiàn)在我們就需要在托管代碼中定義一個等價的結(jié)構(gòu),并且要保證兩個結(jié)構(gòu)體在內(nèi)存中的布局相同。托管代碼中的結(jié)構(gòu)體定義如下:
// 因為Win32 GetVersionEx函數(shù)參數(shù)lpVersionInformation是一個指向 OSVERSIONINFO的數(shù)據(jù)結(jié)構(gòu) // 所以托管代碼中定義個結(jié)構(gòu)體,把結(jié)構(gòu)體對象作為非托管函數(shù)參數(shù) [StructLayout(LayoutKind.Sequential,CharSet=CharSet.Unicode)] public struct OSVersionInfo { public UInt32 OSVersionInfoSize; // 結(jié)構(gòu)的大小,在調(diào)用方法前要初始化該字段 public UInt32 MajorVersion; // 系統(tǒng)主版本號 public UInt32 MinorVersion; // 系統(tǒng)此版本號 public UInt32 BuildNumber; // 系統(tǒng)構(gòu)建號 public UInt32 PlatformId; // 系統(tǒng)支持的平臺 // 此屬性用于表示將其封送成內(nèi)聯(lián)數(shù)組 [MarshalAs(UnmanagedType.ByValTStr,SizeConst=128)] public string CSDVersion; // 系統(tǒng)補丁包的名稱 public UInt16 ServicePackMajor; // 系統(tǒng)補丁包的主版本 public UInt16 ServicePackMinor; // 系統(tǒng)補丁包的次版本 public UInt16 SuiteMask; //標(biāo)識系統(tǒng)上的程序組 public Byte ProductType; //標(biāo)識系統(tǒng)類型 public Byte Reserved; //保留,未使用 }
從上面的定義可以看出, 托管代碼中定義的結(jié)構(gòu)體有以下三個方面與非托管代碼中的結(jié)構(gòu)體是相同的:
- 字段聲明的順序
- 字段的類型
- 字段在內(nèi)存中的大小
并且在上面結(jié)構(gòu)體的定義中,我們使用到了 StructLayout 屬性,該屬性屬于System.Runtime.InteropServices命名空間(所以在使用平臺調(diào)用技術(shù)必須添加這個額外的命名空間)。這個類的作用就是允許開發(fā)人員顯式指定結(jié)構(gòu)體或類中數(shù)據(jù)字段的內(nèi)存布局,為了保證結(jié)構(gòu)體中的數(shù)據(jù)字段在內(nèi)存中的順序與定義時一致,所以指定為 LayoutKind.Sequential(該枚舉也是默認(rèn)值)。
下面就具體看看在托管代碼中調(diào)用的代碼:
using System; using System.ComponentModel; using System.Runtime.InteropServices; namespace 封送結(jié)構(gòu)體的處理 { class Program { // 對GetVersionEx進(jìn)行托管定義 // 為了傳遞指向結(jié)構(gòu)體的指針并將初始化的信息傳遞給非托管代碼,需要用ref關(guān)鍵字修飾參數(shù) // 這里不能使用out關(guān)鍵字,如果使用了out關(guān)鍵字,CLR就不會對參數(shù)進(jìn)行初始化操作,這樣就會導(dǎo)致調(diào)用失敗 [DllImport("Kernel32",CharSet=CharSet.Unicode,EntryPoint="GetVersionEx")] private static extern Boolean GetVersionEx_Struct(ref OSVersionInfo osVersionInfo); // 因為Win32 GetVersionEx函數(shù)參數(shù)lpVersionInformation是一個指向 OSVERSIONINFO的數(shù)據(jù)結(jié)構(gòu) // 所以托管代碼中定義個結(jié)構(gòu)體,把結(jié)構(gòu)體對象作為非托管函數(shù)參數(shù) [StructLayout(LayoutKind.Sequential,CharSet=CharSet.Unicode)] public struct OSVersionInfo { public UInt32 OSVersionInfoSize; // 結(jié)構(gòu)的大小,在調(diào)用方法前要初始化該字段 public UInt32 MajorVersion; // 系統(tǒng)主版本號 public UInt32 MinorVersion; // 系統(tǒng)此版本號 public UInt32 BuildNumber; // 系統(tǒng)構(gòu)建號 public UInt32 PlatformId; // 系統(tǒng)支持的平臺 // 此屬性用于表示將其封送成內(nèi)聯(lián)數(shù)組 [MarshalAs(UnmanagedType.ByValTStr,SizeConst=128)] public string CSDVersion; // 系統(tǒng)補丁包的名稱 public UInt16 ServicePackMajor; // 系統(tǒng)補丁包的主版本 public UInt16 ServicePackMinor; // 系統(tǒng)補丁包的次版本 public UInt16 SuiteMask; //標(biāo)識系統(tǒng)上的程序組 public Byte ProductType; //標(biāo)識系統(tǒng)類型 public Byte Reserved; //保留,未使用 } // 獲得操作系統(tǒng)信息 private static string GetOSVersion() { // 定義一個字符串存儲版本信息 string versionName = string.Empty; // 初始化一個結(jié)構(gòu)體對象 OSVersionInfo osVersionInformation = new OSVersionInfo(); // 調(diào)用GetVersionEx 方法前,必須用SizeOf方法設(shè)置結(jié)構(gòu)體中OSVersionInfoSize 成員 osVersionInformation.OSVersionInfoSize = (UInt32)Marshal.SizeOf(typeof(OSVersionInfo)); // 調(diào)用Win32函數(shù) Boolean result = GetVersionEx_Struct(ref osVersionInformation); if (!result) { // 如果調(diào)用失敗,獲得最后的錯誤碼 int errorcode = Marshal.GetLastWin32Error(); Win32Exception win32Exc = new Win32Exception(errorcode); Console.WriteLine("調(diào)用失敗的錯誤信息為: " + win32Exc.Message); // 調(diào)用失敗時返回為空字符串 return string.Empty; } else { Console.WriteLine("調(diào)用成功"); switch (osVersionInformation.MajorVersion) { // 這里僅僅討論 主版本號為6的情況,其他情況是一樣討論的 case 6: switch (osVersionInformation.MinorVersion) { case 0: if (osVersionInformation.ProductType == (Byte)0) { versionName = " Microsoft Windows Vista"; } else { versionName = "Microsoft Windows Server 2008"; // 服務(wù)器版本 } break; case 1: if (osVersionInformation.ProductType == (Byte)0) { versionName = " Microsoft Windows 7"; } else { versionName = "Microsoft Windows Server 2008 R2"; } break; case 2: versionName = "Microsoft Windows 8"; break; } break; default: versionName = "未知的操作系統(tǒng)"; break; } return versionName; } } static void Main(string[] args) { string OS=GetOSVersion(); Console.WriteLine("當(dāng)前電腦安裝的操作系統(tǒng)為:{0}", OS); Console.Read(); } } }
2.4.5、封送類的處理
下面直接通過GetVersionEx函數(shù)進(jìn)行封送類的處理的例子,具體代碼如下:
using System; using System.ComponentModel; using System.Runtime.InteropServices; namespace 封送類的處理 { class Program { // 對GetVersionEx進(jìn)行托管定義 // 由于類的定義中CSDVersion為String類型,String是非直接復(fù)制到本機(jī)結(jié)構(gòu)類型, // 所以封送拆送器需要進(jìn)行復(fù)制操作。 // 為了是非托管代碼能夠獲得在托管代碼中對象設(shè)置的初始值(指的是OSVersionInfoSize字段,調(diào)用函數(shù)前首先初始化該值), // 所以必須加上[In]屬性;函數(shù)返回時,為了將結(jié)果復(fù)制到托管對象中,必須同時加上 [Out]屬性 // 這里不能是用ref關(guān)鍵字,因為 OsVersionInfo是類類型,本來就是引用類型,如果加ref 關(guān)鍵字就是傳入的為指針的指針了,這樣就會導(dǎo)致調(diào)用失敗 [DllImport("Kernel32", CharSet = CharSet.Unicode, EntryPoint = "GetVersionEx")] private static extern Boolean GetVersionEx_Struct([In, Out] OSVersionInfo osVersionInfo); // 獲得操作系統(tǒng)信息 private static string GetOSVersion() { // 定義一個字符串存儲操作系統(tǒng)信息 string versionName = string.Empty; // 初始化一個類對象 OSVersionInfo osVersionInformation = new OSVersionInfo(); // 調(diào)用Win32函數(shù) Boolean result = GetVersionEx_Struct(osVersionInformation); if (!result) { // 如果調(diào)用失敗,獲得最后的錯誤碼 int errorcode = Marshal.GetLastWin32Error(); Win32Exception win32Exc = new Win32Exception(errorcode); Console.WriteLine("調(diào)用失敗的錯誤信息為: " + win32Exc.Message); // 調(diào)用失敗時返回為空字符串 return string.Empty; } else { Console.WriteLine("調(diào)用成功"); switch (osVersionInformation.MajorVersion) { // 這里僅僅討論 主版本號為6的情況,其他情況是一樣討論的 case 6: switch (osVersionInformation.MinorVersion) { case 0: if (osVersionInformation.ProductType == (Byte)0) { versionName = " Microsoft Windows Vista"; } else { versionName = "Microsoft Windows Server 2008"; // 服務(wù)器版本 } break; case 1: if (osVersionInformation.ProductType == (Byte)0) { versionName = " Microsoft Windows 7"; } else { versionName = "Microsoft Windows Server 2008 R2"; } break; case 2: versionName = "Microsoft Windows 8"; break; } break; default: versionName = "未知的操作系統(tǒng)"; break; } return versionName; } } static void Main(string[] args) { string OS = GetOSVersion(); Console.WriteLine("當(dāng)前電腦安裝的操作系統(tǒng)為:{0}", OS); Console.Read(); } } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] public class OSVersionInfo { public UInt32 OSVersionInfoSize = (UInt32)Marshal.SizeOf(typeof(OSVersionInfo)); public UInt32 MajorVersion = 0; public UInt32 MinorVersion = 0; public UInt32 BuildNumber = 0; public UInt32 PlatformId = 0; // 此屬性用于表示將其封送成內(nèi)聯(lián)數(shù)組 [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] public string CSDVersion = null; public UInt16 ServicePackMajor = 0; public UInt16 ServicePackMinor = 0; public UInt16 SuiteMask = 0; public Byte ProductType = 0; public Byte Reserved; } }
三、COM Interop
為了解決在.NET中的托管代碼能夠調(diào)用COM組件的問題,.NET 平臺下提供了COM Interop,即COM互操作技術(shù)。
在.NET中使用COM對象,主要方法:使用TlbImp工具為COM組件創(chuàng)建一個互操作程序集來綁定早期的COM對象,這樣就可以在程序中添加互操作程序集來調(diào)用COM對象。
在.NET 中使用COM對象的步驟:
- 找到要使用的COM 組件并注冊它。使用 regsvr32.exe 注冊或注銷 COM DLL。
- 在項目中添加對 COM 組件或類型庫的引用。
添加引用時,Visual Studio 會用到Tlbimp.exe(類型庫導(dǎo)入程序),Tlbimp.exe程序?qū)⑸梢粋€ .NET Framework 互操作程序集。該程序集又稱為運行時可調(diào)用包裝 (RCW),其中包含了包裝COM組件中的類和接口。Visual Studio 將生成組件的引用添加至項目。
- 創(chuàng)建RCW中類的實例,這樣就可以使用托管對象一樣來使用COM對象。
在.NET中使用COM組件的過程:
如何在C#中調(diào)用COM組件——訪問Office 互操作對象
在新建的控制臺程序里添加”Microsoft.Office.Interop.Word 14.0.0.0 “ 這個引用
Microsoft.Office.Interop.Word.dll 確實是一個.NET程序集,并且它也叫做COM組件的互操作程序集,這個程序集中包含了COM組件中定義的類型的元數(shù)據(jù), 托管代碼通過調(diào)用互操作程序集中公開的接口或?qū)ο髞黹g接地調(diào)用COM對象和接口的。
關(guān)于通過Tlblmp.exe工具來生成互操作程序集步驟,這里我就不多詳細(xì)訴說了,大家可以參考MSDN中這個工具詳細(xì)使用說明 :http://msdn.microsoft.com/zh-cn/library/tt0cf3sx(v=VS.80).aspx 。
然而我們也可以使用Visual Studio中內(nèi)置的支持來完成為COM類型庫創(chuàng)建互操作程序集的工作,我們只需要在VS中為.NET 項目添加對應(yīng)的COM組件的引用,此時VS就會自動將COM類型庫中的COM類型庫轉(zhuǎn)化為程序集中的元數(shù)據(jù),并在項目的Bin目錄下生成對于的互操作程序集,所以在VS中添加COM引用,其實最后程序中引用的是互操作程序集,然后通過RCW來對COM組件進(jìn)行調(diào)用。 然而對于Office中的Microsoft.Office.Interop.Wordd.dll,這個程序集也是互操作程序集,但是它又是主互操作程序集,即PIA(Primary Interop Assemblies)。主互操作程序集是一個由供應(yīng)商提供的唯一的程序集,為了生成主互操作程序集,可以在使用TlbImp命令是打開 /primary 選項。
using System; using System.Collections.Generic; using System.Linq; using Excel = Microsoft.Office.Interop.Excel; using Word = Microsoft.Office.Interop.Word; namespace OfficeProgramminWalkthruComplete { class Walkthrough { static void Main(string[] args) { // Create a list of accounts. var bankAccounts = new List<Account> { new Account { ID = 345678, Balance = 541.27 }, new Account { ID = 1230221, Balance = -127.44 } }; // Display the list in an Excel spreadsheet. DisplayInExcel(bankAccounts); // Create a Word document that contains an icon that links to // the spreadsheet. CreateIconInWordDoc(); } static void DisplayInExcel(IEnumerable<Account> accounts) { var excelApp = new Excel.Application(); // Make the object visible. excelApp.Visible = true; // Create a new, empty workbook and add it to the collection returned // by property Workbooks. The new workbook becomes the active workbook. // Add has an optional parameter for specifying a praticular template. // Because no argument is sent in this example, Add creates a new workbook. excelApp.Workbooks.Add(); // This example uses a single workSheet. Excel._Worksheet workSheet = excelApp.ActiveSheet; // Earlier versions of C# require explicit casting. //Excel._Worksheet workSheet = (Excel.Worksheet)excelApp.ActiveSheet; // Establish column headings in cells A1 and B1. workSheet.Cells[1, "A"] = "ID Number"; workSheet.Cells[1, "B"] = "Current Balance"; var row = 1; foreach (var acct in accounts) { row++; workSheet.Cells[row, "A"] = acct.ID; workSheet.Cells[row, "B"] = acct.Balance; } workSheet.Columns[1].AutoFit(); workSheet.Columns[2].AutoFit(); // Call to AutoFormat in Visual C#. This statement replaces the // two calls to AutoFit. workSheet.Range["A1", "B3"].AutoFormat( Excel.XlRangeAutoFormat.xlRangeAutoFormatClassic2); // Put the spreadsheet contents on the clipboard. The Copy method has one // optional parameter for specifying a destination. Because no argument // is sent, the destination is the Clipboard. workSheet.Range["A1:B3"].Copy(); } static void CreateIconInWordDoc() { var wordApp = new Word.Application(); wordApp.Visible = true; // The Add method has four reference parameters, all of which are // optional. Visual C# allows you to omit arguments for them if // the default values are what you want. wordApp.Documents.Add(); // PasteSpecial has seven reference parameters, all of which are // optional. This example uses named arguments to specify values // for two of the parameters. Although these are reference // parameters, you do not need to use the ref keyword, or to create // variables to send in as arguments. You can send the values directly. wordApp.Selection.PasteSpecial(Link: true, DisplayAsIcon: true); } } public class Account { public int ID { get; set; } public double Balance { get; set; } } }
錯誤處理
try { // 如果文檔不存在時,就會出現(xiàn)調(diào)用COM對象失敗的情況 // 打開Word文檔 wordDoc = wordApp.Documents.Open(wordPath); // 向Word中插入文本 Range wordRange = wordDoc.Range(0, 0); wordRange.Text = "這是插入的文本"; // 保存文檔 wordDoc.Save(); } catch(Exception ex) { // 獲得異常相對應(yīng)的HRESULT值 // 因為COM中根據(jù)方法返回的HRESULT來判斷調(diào)用是否成功的 int HResult = Marshal.GetHRForException(ex); // 設(shè)置控制臺的前景色,即輸出文本的顏色 Console.ForegroundColor = ConsoleColor.Red; // 下面把HRESULT值以16進(jìn)制輸出 Console.WriteLine("調(diào)用拋出異常,異常類型為:{0}, HRESULT= 0x{1:x}", ex.GetType().Name, HResult); Console.WriteLine("異常信息為:" + ex.Message.Replace('\r', ' ')); } finally { // 關(guān)閉文檔并 if (wordDoc != null) { wordDoc.Close(); } // 退出Word程序 wordApp.Quit(); }
從上面的結(jié)果我們看到了一個 HRESULT值,這個值真是COM代碼中返回返回的。在COM中,COM方法通過返回 HRESULT 來報告錯誤;.NET 方法則通過引發(fā)異常來報告錯誤,為了方便地在托管代碼中獲得COM代碼中出現(xiàn)的錯誤和異常信息,CLR提供了兩者之間的轉(zhuǎn)換,每一個代表錯誤發(fā)生的HRESULT都會被映射到.NET Framework中的一個異常類,對于具體的映射關(guān)系可以參考MSDN中 的文章: http://msdn.microsoft.com/zh-cn/library/9ztbc5s1(VS.80).aspx。
到此這篇關(guān)于C#調(diào)用動態(tài)庫的文章就介紹到這了。希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
結(jié)合.net框架在C#派生類中觸發(fā)基類事件及實現(xiàn)接口事件
這篇文章主要介紹了結(jié)合.net框架在C#派生類中觸發(fā)基類事件及實現(xiàn)接口事件,示例的事件編程中包括接口和類的繼承等面向?qū)ο蟮幕A(chǔ)知識,需要的朋友可以參考下2016-02-02C#根據(jù)反射和特性實現(xiàn)ORM映射實例分析
這篇文章主要介紹了C#根據(jù)反射和特性實現(xiàn)ORM映射的方法,實例分析了反射的原理、特性與ORM的實現(xiàn)技巧,具有一定參考借鑒價值,需要的朋友可以參考下2015-04-04c#解析jobject的數(shù)據(jù)結(jié)構(gòu)
這篇文章介紹了c#解析jobject數(shù)據(jù)結(jié)構(gòu)的方法,文中通過示例代碼介紹的非常詳細(xì)。對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-07-07