程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> c# 調用API

c# 調用API

編輯:C#入門知識

1      DLLImport的使用
using System;
using System.Runtime.InteropServices; //命名空間
class Example
{
//用DllImport 導入Win32的MessageBox函數
    [DllImport("user32.dll", CharSet = CharSet.Unicode)]
public static extern int MessageBox(IntPtr hWnd, String text, String caption, uint type);
//方法被聲明為 static。這是 P/Invoke 方法所要求的,因為在該 Windows API 中沒有//一致的實例概念。接下來,還要注意該方法被標記為 extern。這是提示編譯器該方法是通//過一個從 DLL 導出的函數實現的,因此不需要提供方法體。
    static void Main()
    {
        // Call the MessageBox function using platform invoke.
        MessageBox(new IntPtr(0), "Hello World!", "Hello Dialog", 0);
    }
}
使用非托管DLL函數並不困難,下面我們可以詳細的了解上面的代碼的含義。首先介紹什麼是托管代碼,什麼是非托管代碼。然後再詳細介紹DLLImport的使用方法和各字段的意義。
2      托管代碼 (managed code)
.NET Framework的核心是其運行庫的執行環境,稱為公共語言運行庫(CLR)或.NET運行庫。通常將在CLR的控制下運行的代碼稱為托管代碼(managed code)。
運行庫環境(而不是直接由操作系統)執行的代碼。托管代碼應用程序可以獲得公共語言運行庫服務,例如自動垃圾回收、運行庫類型檢查和安全支持等。這些服務幫助提供獨立於平台和語言的、統一的托管代碼應用程序行為。
  托管代碼是可以使用20多種支持Microsoft .NET Framework的高級語言編寫的代碼,它們包括:C#, J#, Microsoft Visual Basic .NET, Microsoft JScript .NET, 以及C++。所有的語言共享統一的類庫集合,並能被編碼成為中間語言(IL)。運行庫編譯器(runtime-aware ompiler)在托管執行環境下編譯中間語言(IL)使之成為本地可執行的代碼,並使用數組邊界和索引檢查,異常處理,垃圾回收等手段確保類型的安全。
  在托管執行環境中使用托管代碼及其編譯,可以避免許多典型的導致安全黑洞和不穩定程序的編程錯誤。同樣,許多不可靠的設計也自動的被增強了安全 性,例如類型安全檢查,內存管理和釋放無效對象。程序員可以花更多的精力關注程序的應用邏輯設計並可以減少代碼的編寫量。這就意味著更短的開發時間和更健 壯的程序。
簡單點說,托管代碼是microsoft的中間語言,他主要的作用是在.NET FRAMEWORK的CLR執行代碼前去編譯源代碼,也就是說托管代碼充當著翻譯的作用,源代碼在運行時分為兩個階段:
  1.源代碼編譯為托管代碼;(所以源代碼可以有很多種,如VB,C#,J#)
  2.托管代碼編譯為microsoft系統的.net平台專用文件(如類庫、可執行文件等)。
2.1     非托管代碼 (unmanaged code)
  在公共語言運行庫環境的外部,由操作系統直接執行的代碼。非托管代碼必須提供自己的垃圾回收、類型檢查、安全支持等服務;它與托管代碼不同,後者從公共語言運行庫中獲得這些服務。
.net中托管代碼的含義
2.2     什麼是托管?托管是什麼意思?
托管代碼就是基於.net元數據格式的代碼,運行於.net平台之上,所有的與操作系統的交換有.net來完成,就像是把這些功能委托給.net,所以稱之為托管代碼。非托管代碼則反之。
舉個例子l
Vc.net還可以使用mfc,atl來編寫程序,他們基於MFC或者ATL,而不是.NET,所以是非托管代碼,如果基於.net比如C#,VB.net則是托管代碼
非托管代碼是指.NET解釋不了的
簡單的說,托管代碼的話,.net可以自動釋放資料,非托管代碼需要手動釋放資料.
什麼是托管C++
托管是.NET的一個專門概念,它倡導一種新的編程理念,因此我們完全可以把“托管”視為“.NET”。由托管概念所引發的C++應用程序包括托管代碼、托管數據和托管類三個組成部分。
托管代碼
.Net環境提供了許多核心的運行(RUNTIME)服務,比如異常處理和安全策略。為了能使用這些服務,必須要給運行環境提供一些信息代碼(元數 據),這種代碼就是托管代碼。所有的C#、VB.NET、JScript.NET默認時都是托管的,但Visual C++默認時不是托管的,必須在編譯器中使用命令行選項(/CLR)才能產生托管代碼。
托管數據
與托管代碼密切相關的是托管數據。托管數據是由公共語言運行的垃圾回收器進行分配和釋放的數據。默認情況下,C#、Visual Basic 和 JScript.NET 數據是托管數據。不過,通過使用特殊的關鍵字,C# 數據可以被標記為非托管數據。Visual C++數據在默認情況下是非托管數據,即使在使用 /CLR 開關時也不是托管的。
托管類
盡管Visual C++數據在默認情況下是非托管數據,但是在使用C++的托管擴展時,可以使用“__gc”關鍵字將類標記為托管類。就像該名稱所顯示的那樣,它表示類實 例的內存由垃圾回收器管理。另外,一個托管類也完全可以成為 .NET 框架的成員,由此可以帶來的好處是,它可以與其他語言編寫的類正確地進行相互操作,如托管的C++類可以從Visual Basic類繼承等。但同時也有一些限制,如托管類只能從一個基類繼承等。
2.3     托管代碼如何調用非托管代碼(c sharp如何調用c++代碼)?
 1. COM interop
具體操作:
a. 用atl寫com服務程序
b. 使用Tlbimp將atl寫的com程序轉換成 COM DLL
   用如下命令:
   tlbimp 你寫的com.dll
   tlbimp是 .NET Framework SDK中附帶的類型庫導入程序。用這個命令即是把生成一個非托管com dll的托管包裝。
c. 托管客戶端非常簡單
   直接new一下,然後調用對應的方法即可。
2. P/Invoke
a. 在托管客戶端增加一條 DllImport語句和一個方法的調用。
介紹一個P/Invoke網站, 這個網站主要是一個wiki,允許開發者發現,編輯,增加PInvoke的簽名,用戶自定義類型和從托管代碼(指c#和VB.net開發語言)訪問win32和其他非托管api的信息。
世界各地的.Net開發者可以很容易分享自己有價值的東西給社區,
2.4     托管代碼和非托管代碼效率的對比
 3      DllImportAttribute 的字段
在對托管代碼進行 P/Invoke 調用時,DllImportAttribute 類型扮演著重要的角色。DllImportAttribute 的主要作用是給 CLR 指示哪個 DLL 導出您想要調用的函數。相關 DLL 的名稱被作為一個構造函數參數傳遞給 DllImportAttribute。
下表列出了所有與平台調用相關的特性字段。 對於每個字段,下表都將包含其默認值,並且會提供一個鏈接,用於獲取有關如何使用這些字段定義非托管 DLL 函數的信息。 字段
說明
BestFitMapping
啟用或禁用最佳匹配映射。
CallingConvention
指定用於傳遞方法參數的調用約定。 默認值為 WinAPI,該值對應於基於 32 位 Intel 的平台的 __stdcall。
CharSet
控制名稱重整以及將字符串參數封送到函數中的方式。 默認值為 CharSet.Ansi。
EntryPoint
指定要調用的 DLL 入口點。
ExactSpelling
控制是否應修改入口點以對應於字符集。 對於不同的編程語言,默認值將有所不同。
PreserveSig
控制托管方法簽名是否應轉換成返回 HRESULT 並且返回值有一個附加的 [out, retval] 參數的非托管簽名。
默認值為 true(不應轉換簽名)。
SetLastError
允許調用方使用 Marshal.GetLastWin32Error API 函數來確定執行該方法時是否發生了錯誤。 在 Visual Basic 中,默認值為 true;在 C# 和 C++ 中,默認值為 false。
ThrowOnUnmappableChar
控件引發的異常,將無法映射的 Unicode 字符轉換成一個 ANSI"?"字符。
除了指出宿主 DLL 外,DllImportAttribute 還包含了一些可選屬性,其中四個特別有趣:EntryPoint、CharSet、SetLastError 和 CallingConvention。http://www.360doc.com/content/11/0105/09 /3877783_84071078.shtml
3.1     entrypoint
入口點用於標識函數在 DLL 中的位置。在托管對象中,目標函數的原名或序號入口點將標識跨越交互操作邊界的函數。此外,您可以將入口點映射到一個不同的名稱,這實際上是將函數重命名。
以下列出了重命名 DLL 函數的可能原因:
·        避免使用區分大小寫的 API 函數名
·        符合現行的命名標准
·        提供采用不同數據類型的函數(通過聲明同一 DLL 函數的多個版本)
·        簡化對包含 ANSI 和 Unicode 版本的 API 的使用
您可以使用 DllImportAttribute.EntryPoint 字段按名稱或序號指定 DLL 函數。如果函數在方法定義中的名稱與入口點在 DLL 的名稱相同,則不必用 EntryPoint 字段來顯式地標識函數。否則,使用以下屬性形式之一來指示名稱或序號:
    [DllImport("dllname", EntryPoint="Functionname")]
    [DllImport("dllname", EntryPoint="#123")]
   指定入口點名稱時,您可以提供一個字符串來指示包含入口點的 DLL 的名稱,或者也可以按序號來標識入口點。序號以 # 符號為前綴,如 #1。如果省略此字段,則公共語言運行庫將使用以DllImportAttribute 標記的 .NET 方法的名稱。
下面的示例演示如何使用 EntryPoint 字段將代碼中的 MessageBoxA 替換為 MsgBox
using System.Runtime.InteropServices;
public class Win32 {
    [DllImport("user32.dll", EntryPoint="MessageBoxA")]
    public static extern int MsgBox(int hWnd, String text, String caption, uint type);
}   
3.2     CharSet
 DllImportAttribute.CharSet 字段控制字符串封送處理並確定平台調用在 DLL 中查找函數名的方式。本主題將介紹這兩種行為。
對於采用字符串參數的函數,有些 API 將導出它們的兩個版本:窄版本 (ANSI) 和寬版本 (Unicode)。例如,Win32 API 包含 MessageBox 函數的以下入口點名稱:
·        MessageBoxA
提供單字節字符 ANSI 格式,其特征是在入口點名稱後附加一個“A”。對 MessageBoxA 的調用始終會以 ANSI 格式封送字符串,它常見於 Windows 95 和 Windows 98 平台。
·        MessageBoxW
提供雙字節字符 Unicode 格式,其特征是在入口點名稱後附加一個“W”。對 MessageBoxW 的調用始終會以 Unicode 格式封送字符串,它常見於 Windows NT、Windows 2000 和 Windows XP 平台。
CharSet 字段接受以下值:
CharSet.Ansi(默認值)
·        字符串封送處理
平台調用將字符串從托管格式 (Unicode) 封送為 ANSI 格式。
·        名稱匹配
在 DllImportAttribute.ExactSpelling 字段為 true(它是 Visual Basic 2005 中的默認值)時,平台調用將只搜索您指定的名稱。例如,如果指定MessageBox,則平台調用將搜索 MessageBox,如果它找不到完全相同的拼寫則失敗。
當 ExactSpelling 字段為 false(它是 C++ 和 C# 中的默認值)時,平台調用將首先搜索未處理的別名 (MessageBox),如果找不到未處理的別名,則將搜索已處理的名稱 (MessageBoxA)。請注意,ANSI 名稱匹配行為與 Unicode 名稱匹配行為不同。
CharSet.Unicode
·        字符串封送處理
平台調用會將字符串從托管格式 (Unicode) 復制為 Unicode 格式。
·        名稱匹配
當 ExactSpelling 字段為 true(它是 Visual Basic 2005 中的默認值)時,平台調用將只搜索您指定的名稱。例如,如果指定MessageBox,則平台調用將搜索 MessageBox,如果它找不到完全相同的拼寫則失敗。
當 ExactSpelling 字段為 false(它是 C++ 和 C# 中的默認值)時,平台調用將首先搜索已處理的名稱 (MessageBoxW),如果找不到已處理的名稱,則將搜索未處理的別名 (MessageBox)。請注意,Unicode 名稱匹配行為與 ANSI 名稱匹配行為不同。
CharSet.Auto
·        平台調用在運行時根據目標平台在 ANSI 和 Unicode 格式之間進行選擇。
下面的示例演示用於指定字符集的 MessageBox 函數的三個托管定義。在第一個定義中,通過省略,使CharSet 字段默認為 ANSI 字符集。
[DllImport("user32.dll")]
public static extern int MessageBoxA(int hWnd, String text, String caption, uint type);
[DllImport("user32.dll", CharSet=CharSet.Unicode)]
public static extern int MessageBoxW(int hWnd, String text, String caption, uint type);
[DllImport("user32.dll", CharSet=CharSet.Auto)]
public static extern int MessageBox(int hWnd, String text, String caption, uint type);
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”。
如果 DLL 函數不以任何方式處理文本,則可以忽略 DllImportAttribute 的 CharSet 屬性。然而,當 Char 或 String 數據是等式的一部分時,應該將 CharSet 屬性設置為 CharSet.Auto。這樣可以使 CLR 根據宿主 OS 使用適當的字符集。如果沒有顯式地設置 CharSet 屬性,則其默認值為 CharSet.Ansi。這個默認值是有缺點的,因為對於在 Windows 2000、Windows XP 和 Windows NT® 上進行的 interop 調用,它會消極地影響文本參數封送處理的性能。
應該顯式地選擇 CharSet.Ansi 或 CharSet.Unicode 的 CharSet 值而不是使用 CharSet.Auto 的唯一情況是:您顯式地指定了一個導出函數,而該函數特定於這兩種 Win32 OS 中的某一種。ReadDirectoryChangesW API 函數就是這樣的一個例子,它只存在於基於 Windows NT 的操作系統中,並且只支持 Unicode;在這種情況下,您應該顯式地使用 CharSet.Unicode。
有時,Windows API 是否有字符集關系並不明顯。一種決不會有錯的確認方法是在 Platform SDK 中檢查該函數的 C 語言頭文件。(如果您無法肯定要看哪個頭文件,則可以查看 Platform SDK 文檔中列出的每個 API 函數的頭文件。)如果您發現該 API 函數確實定義為一個映射到以 A 或 W 結尾的函數名的宏,則字符集與您嘗試調用的函數有關系。Windows API 函數的一個例子是在 WinUser.h 中聲明的 GetMessage API,您也許會驚訝地發現它有 A 和 W 兩種版本。
3.3    SetLastError
SetLastError 錯誤處理非常重要,但在編程時經常被遺忘。當您進行 P/Invoke 調用時,也會面臨其他的挑戰 — 處理托管代碼中 Windows API 錯誤處理和異常之間的區別。我可以給您一點建議。
如果您正在使用 P/Invoke 調用 Windows API 函數,而對於該函數,您使用 GetLastError 來查找擴展的錯誤信息,則應該在外部方法的 DllImportAttribute 中將 SetLastError 屬性設置為 true。這適用於大多數外部方法。
這會導致 CLR 在每次調用外部方法之後緩存由 API 函數設置的錯誤。然後,在包裝方法中,可以通過調用類庫的 System.Runtime.InteropServices.Marshal 類型中定義的 Marshal.GetLastWin32Error 方法來獲取緩存的錯誤值。我的建議是檢查這些期望來自 API 函數的錯誤值,並為這些值引發一個可感知的異常。對於其他所有失敗情況(包括根本就沒意料到的失敗情況),則引發在 System.ComponentModel 命名空間中定義的 Win32Exception,並將 Marshal.GetLastWin32Error 返回的值傳遞給它。
3.4    CallingConvention
CallingConvention.Cdecl : 調用方清理堆棧。它使您能夠調用具有 varargs 的函數。
CallingConvention.StdCall : 被調用方清理堆棧。它是從托管代碼調用非托管函數的默認約定。
CallingConvention 字段的默認值為 Winapi,而後者又默認為 StdCall 約定。
可能是最不重要的一個 DllImportAttribute 屬性是 CallingConvention。通過此屬性,可以給 CLR 指示應該將哪種函數調用約定用於堆棧中的參數。CallingConvention.Winapi 的默認值是最好的選擇,它在大多數情況下都可行。然而,如果該調用不起作用,則可以檢查 Platform SDK 中的聲明頭文件,看看您調用的 API 函數是否是一個不符合調用約定標准的異常 API。
通常,本機函數(例如 Windows API 函數或 C- 運行時 DLL 函數)的調用約定描述了如何將參數推入線程堆棧或從線程堆棧中清除。大多數 Windows API 函數都是首先將函數的最後一個參數推入堆棧,然後由被調用的函數負責清理該堆棧。相反,許多 C-運行時 DLL 函數都被定義為按照方法參數在方法簽名中出現的順序將其推入堆棧,將堆棧清理工作交給調用者。
幸運的是,要讓 P/Invoke 調用工作只需要讓外圍設備理解調用約定即可。通常,從默認值 CallingConvention.Winapi 開始是最好的選擇。然後,在 C 運行時 DLL 函數和少數函數中,可能需要將約定更改為 CallingConvention.Cdecl。
3.5    ExactSpelling
ExactSpelling 指示是否應修改非托管 DLL 中的入口點的名稱,以與 CharSet 字段中指定的 CharSet 值相對應。如果為 true,則當 DllImportAttribute.CharSet 字段設置為 CharSet 的 Ansi 值時,向方法名稱中追加字母 A,當 DllImportAttribute.CharSet 字段設置為 CharSet 的 Unicode 值時,向方法的名稱中追加字母 W。此字段的默認值是 false。www.2cto.com
3.6    PreserveSig
    PreserveSig指示托管方法簽名不應轉換成返回 HRESULT、並且可能有一個對應於返回值的附加 [out, retval] 參數的非托管簽名。
4      參數類型
    數值型直接用對應的就可。(DWORD -> int , WORD -> Int16)
    API中字符串指針類型 -> .net中string
    API中句柄 (dWord) -> .net中IntPtr
    API中結構 -> .net中結構或者類。注意這種情況下,要先用StructLayout特性限定聲明結構或類
5      Win32 API 中幾個常用的 DLL6      wince 中的DLL

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved