API(Application Programming Interface),我想大家不會陌生,它是我們Windows編程的常客,雖然基於.Net平臺的C#有了強大的類庫,但是,我們還是不能否認API在Windows編程中的重要性。大多數的編程語言都支持API編程,而.Net平臺中的MFC(Microsoft Foundation Class Library)構架本身就封裝了大部分的API。
做爲程序員,我們需要了解API從字面上瞭解便是編程接口,因此,做爲開發者,需要了解的只是API的使用方法。
API根據操作系統、處理器及功能性的不同而擁有很多不同的類型。 操作系統特用的API:
每種操作系統都有許多通用的API以及一些特用的API,這些特用的API只能在當前操作系統中執行。
例如:
Windows NT 支持 MS-DOS, Win16, Win32, POSIX (Portable Operating System Interface), OS/2 console API; 而 Windows 95 支持 MS-DOS, Win16 以及 Win32 APIs.
Win16 & Win32 API:
Win16是爲十六位處理器開發的,早期的操作系統均支持。
Win32則是爲32位處理器開發。它可移植性強,被大部分的處理器所支持。
Win32 API在庫名後有一個”32”後綴。比如KERNEL32,USER32等。
所有API在下面3個庫中得以運行:
Kernel
User
GDI
1. KERNEL
他的庫名爲 KERNEL32.DLL, 他主要用於產生與操作系統之間的關聯:
程序加載
上下文選擇.
文件輸入輸出.
內存管理.
例如: GlobalMemoryStatus 函數就包括當前物理內存及虛擬內存的使用信息。
2. USER
這個類庫在Win32中名叫 USER32.DLL。
它允許管理全部的用戶接口,比如:
窗口
菜單
對話框
圖標等.,
例如: DrawIcon 函數將在指定的設備關聯上“畫”出圖標或者鼠標。
3. GDI (Graphical Device Interface)
它在Win32中的庫名爲:GDI32.dll,它是圖形輸出庫。使用GDI Windows“畫”出窗口、菜單以及對話框等:
它能創建圖形輸出.
它也能保存圖形文件.
例如: CreateBitmap 函數就能通過指定的長、寬、顏色創建一個位圖。
C# 中操作API:
作爲初學者來說,在C#中使用API確是一件令人頭疼的問題。在使用API之間你必須知道如何在C#中使用結構、類型轉換、安全/不安全代碼,可控/不可控代碼等許多知識。
一切從簡單開始,複雜的大家一時不能接受。我們就從實現一個簡單的MessageBox開始。首先打開VS.Net ,創建一個新的C#工程,並添加一個Button按鈕。當這個按鈕被點擊,則顯示一個MessageBox對話框。
即然我們需要引用外來庫,所以必須導入一個Namespace:
using System.Runtime.InteropServices;
接着添加下面的代碼來聲明一個API:
[DllImport("User32.dll")]
public static extern int MessageBox(int h, string m, string c, int type);
此處DllImport屬性被用來從不可控代碼中調用一方法。”User32.dll”則設定了類庫名。DllImport屬性指定dll的位置,這個dll中包括調用的外部方法。Static修飾符則聲明一個靜態元素,而這個元素屬於類型本身而不是上面指定的對象。extern則表示這個方法將在工程外部執行,使用DllImport導入的方法必須使用extern修飾符。
MessageBox 則是函數名,擁有4個參數,其返回值爲數字。
大多數的API都能傳遞並返回值。
添中Click點擊事件代碼:
protected void button1_Click(object sender, System.EventArgs e)
{
MessageBox (0,"API Message Box","API Demo",0);
}
編譯並運行這個程序,當你點擊按鈕後,你將會看到對話框,這便是你使用的API函數。
使用結構體
操作帶有結構體的API比使用簡單的API要複雜的多。但是一旦你掌握了API的過程,那個整個API世界將在你的掌握之中。
下面的例子中我們將使用GetSystemInfo API 來獲取整個系統的信息。
第一步還是打開C#建立一個Form工程,同樣的添中一個Button按鈕,在代碼窗中輸入下面的代碼,導入Namespace:
using System.Runtime.InteropServices;
聲明一個結構體,它將做爲GetSystemInfo的一個參數:
[StructLayout(LayoutKind.Sequential)]
public struct SYSTEM_INFO {
public uint dwOemId;
public uint dwPageSize;
public uint lpMinimumApplicationAddress;
public uint lpMaximumApplicationAddress;
public uint dwActiveProcessorMask;
public uint dwNumberOfProcessors;
public uint dwProcessorType;
public uint dwAllocationGranularity;
public uint dwProcessorLevel;
public uint dwProcessorRevision;
}
聲明API函數:
[DllImport("kernel32")]
static extern void GetSystemInfo(ref SYSTEM_INFO pSI);
添加下面的代碼至按鈕的點擊事件處理中:
首先創建一個SYSTEM_INFO結構體,並將其傳遞給GetSystemInfo函數。
protected void button1_Click (object sender, System.EventArgs e)
{
try
{
SYSTEM_INFO pSI = new SYSTEM_INFO();
GetSystemInfo(ref pSI);
//
//
//
一旦你接收到返回的結構體,那麼就可以以返回的參數來執行操作了。
e.g.listBox1.InsertItem (0,pSI.dwActiveProcessorMask.ToString());:
//
//
//
}
catch(Exception er)
{
MessageBox.Show (er.Message);
}
}
view plain
調用API全部代碼
//Created By Ajit Mungale
//程序補充 飛刀
namespace UsingAPI
{
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.WinForms;
using System.Data;
using System.Runtime.InteropServices;
//Struct 收集系統信息
[StructLayout(LayoutKind.Sequential)]
public struct SYSTEM_INFO {
public uint dwOemId;
public uint dwPageSize;
public uint lpMinimumApplicationAddress;
public uint lpMaximumApplicationAddress;
public uint dwActiveProcessorMask;
public uint dwNumberOfProcessors;
public uint dwProcessorType;
public uint dwAllocationGranularity;
public uint dwProcessorLevel;
public uint dwProcessorRevision;
}
//struct 收集內存情況
[StructLayout(LayoutKind.Sequential)]
public struct MEMORYSTATUS
{
public uint dwLength;
public uint dwMemoryLoad;
public uint dwTotalPhys;
public uint dwAvailPhys;
public uint dwTotalPageFile;
public uint dwAvailPageFile;
public uint dwTotalVirtual;
public uint dwAvailVirtual;
}
public class Form1 : System.WinForms.Form
{
private System.ComponentModel.Container components;
private System.WinForms.MenuItem menuAbout;
private System.WinForms.MainMenu mainMenu1;
private System.WinForms.ListBox listBox1;
private System.WinForms.Button button1;
//獲取系統信息
[DllImport("kernel32")]
static extern void GetSystemInfo(ref SYSTEM_INFO pSI);
//獲取內存信息
[DllImport("kernel32")]
static extern void GlobalMemoryStatus(ref MEMORYSTATUS buf);
//處理器類型
public const int PROCESSOR_INTEL_386 = 386;
public const int PROCESSOR_INTEL_486 = 486;
public const int PROCESSOR_INTEL_PENTIUM = 586;
public const int PROCESSOR_MIPS_R4000 = 4000;
public const int PROCESSOR_ALPHA_21064 = 21064;
public Form1()
{
InitializeComponent();
}
public override void Dispose()
{
base.Dispose();
components.Dispose();
}
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container ();
this.mainMenu1 = new System.WinForms.MainMenu ();
this.button1 = new System.WinForms.Button ();
this.listBox1 = new System.WinForms.ListBox ();
this.menuAbout = new System.WinForms.MenuItem ();
mainMenu1.MenuItems.All = new System.WinForms.MenuItem[1] {this.menuAbout};
button1.Location = new System.Drawing.Point (148, 168);
button1.Size = new System.Drawing.Size (112, 32);
button1.TabIndex = 0;
button1.Text = "&Get Info";
button1.Click += new System.EventHandler (this.button1_Click);
listBox1.Location = new System.Drawing.Point (20, 8);
listBox1.Size = new System.Drawing.Size (368, 147);
listBox1.TabIndex = 1;
menuAbout.Text = "&About";
menuAbout.Index = 0;
menuAbout.Click += new System.EventHandler (this.menuAbout_Click);
this.Text = "System Information - Using API";
this.MaximizeBox = false;
this.AutoScaleBaseSize = new System.Drawing.Size (5, 13);
this.MinimizeBox = false;
this.Menu = this.mainMenu1;
this.ClientSize = new System.Drawing.Size (408, 213);
this.Controls.Add (this.listBox1);
this.Controls.Add (this.button1);
}
protected void menuAbout_Click (object sender, System.EventArgs e)
{
Form abt=new about() ;
abt.ShowDialog();
}
protected void button1_Click (object sender, System.EventArgs e)
{
try
{
SYSTEM_INFO pSI = new SYSTEM_INFO();
GetSystemInfo(ref pSI);
string CPUType;
switch (pSI.dwProcessorType)
{
case PROCESSOR_INTEL_386 :
CPUType= "Intel 386";
break;
case PROCESSOR_INTEL_486 :
CPUType = "Intel 486" ;
break;
case PROCESSOR_INTEL_PENTIUM :
CPUType = "Intel Pentium";
break;
case PROCESSOR_MIPS_R4000 :
CPUType = "MIPS R4000";
break;
case PROCESSOR_ALPHA_21064 :
CPUType = "DEC Alpha 21064";
break;
default :
CPUType = "(unknown)";
}
listBox1.InsertItem (0,"Active Processor Mask :"+pSI.dwActiveProcessorMask.ToString());
listBox1.InsertItem (1,"Allocation Granularity :"+pSI.dwAllocationGranularity.ToString());
listBox1.InsertItem (2,"Number Of Processors :"+pSI.dwNumberOfProcessors.ToString());
listBox1.InsertItem (3,"OEM ID :"+pSI.dwOemId.ToString());
listBox1.InsertItem (4,"Page Size:"+pSI.dwPageSize.ToString());
listBox1.InsertItem (5,"Processor Level Value:"+pSI.dwProcessorLevel.ToString());
listBox1.InsertItem (6,"Processor Revision:"+ pSI.dwProcessorRevision.ToString());
listBox1.InsertItem (7,"CPU type:"+CPUType);
listBox1.InsertItem (8,"Maximum Application Address: "+pSI.lpMaximumApplicationAddress.ToString());
listBox1.InsertItem (9,"Minimum Application Address:" +pSI.lpMinimumApplicationAddress.ToString());
/************** 從 GlobalMemoryStatus 獲取返回值****************/
MEMORYSTATUS memSt = new MEMORYSTATUS ();
GlobalMemoryStatus (ref memSt);
listBox1.InsertItem(10,"Available Page File :"+ (memSt.dwAvailPageFile/1024).ToString ());
listBox1.InsertItem(11,"Available Physical Memory : " + (memSt.dwAvailPhys/1024).ToString());
listBox1.InsertItem(12,"Available Virtual Memory:" + (memSt.dwAvailVirtual/1024).ToString ());
listBox1.InsertItem(13,"Size of structur :" + memSt.dwLength.ToString());
listBox1.InsertItem(14,"Memory In Use :"+ memSt.dwMemoryLoad.ToString());
listBox1.InsertItem(15,"Total Page Size :"+ (memSt.dwTotalPageFile/1024).ToString ());
listBox1.InsertItem(16,"Total Physical Memory :" + (memSt.dwTotalPhys/1024).ToString());
listBox1.InsertItem(17,"Total Virtual Memory :" + (memSt.dwTotalVirtual/1024).ToString ());
}
catch(Exception er)
{
MessageBox.Show (er.Message);
}
}
public static void Main(string[] args)
{
try
{
Application.Run(new Form1());
}
catch(Exception er)
{
MessageBox.Show (er.Message );
}
}
}
}
C#中調用Windows API的要點
日期:2003年12月11日 作者:佚名 人氣:586 查看:[大字體 中字體 小字體]
在.Net Framework SDK文檔中,關於調用Windows API的指示比較零散,並且其中稍全面一點的是針對Visual Basic .net講述的。本文將C#中調用API的要點彙集如下,希望給未在C#中使用過API的朋友一點幫助。另外如果安裝了Visual Studio .net的話,在C:/Program Files/Microsoft Visual Studio .NET/FrameworkSDK/Samples/Technologies/Interop/PlatformInvoke/WinAPIs/CS目錄下有大量的調用API的例子。
一、調用格式
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 ends
ReadFile( hr, buffer, 5, out read, 0 );