Win32 API能夠直接控制Microsoft Windows的核心,由於API(Application Programming Interface)原本就是微軟留給咱們直接控制Windows的接口。
一. 基礎知識
Win32 API是C語言(注意,不是C++語言,儘管C語言是C++語言的子集)函數集。
1. Win32 API函數放在哪裏?
Win32 API函數是Windows的核心,好比咱們看到的窗體、按鈕、對話框什麼的,都是依靠Win32函數「畫」在屏幕上的,因爲這些控件(有時也稱組件)都用於用戶與Windows進行交互,因此控制這些控件的Win32 API函數稱爲「用戶界面」函數(User Interface Win32 API),簡稱UI函數;還有一些函數,並不用於交互,好比管理當前系統正在運行的進程、硬件系統狀態的監視等等……這些函數只有一套,可是能夠被全部的Windows程序調用(只要這個程序的權限足夠高),簡而言之,API是爲程序所共享的。爲了達到全部程序能共享一套API的目的,Windows採用了「動態連接庫」的辦法。之因此叫「動態連接庫」,是由於這樣的函數庫的調用方式是「隨用隨取」而不是像靜態連接庫那樣「用不用都要帶上」。
Win32 API函數是放在Windows系統的核心庫文件中的,這些庫在硬盤裏的存儲形式是.dll文件。咱們經常使用到的dll文件是user32.dll和kernel32.dll兩個文件。
這些dll文件是用C語言寫的,源代碼經C語言編譯器編譯以後,會以二進制可執行代碼形式存放在這些dll文件中。爲了能讓程序使用這些函數,微軟在發佈每一個新的操做系統的時候,也會放出這個系統的SDK,目前最新的是Win2003 SP1 SDK,聽說Vista的立刻就要放出來,並且已經把UI的API從核心庫中分離出去以提升系統的穩定性了。SDK裏有一些C語言的頭文件(.h文件),這些文件裏描述了核心dll文件裏都有哪些Win32 API函數,在寫程序的時候,把這些.h文件用#include"....."指令包含進你的程序裏,你就可使用這些Win32 API了。
C#語言也使用dll動態連接庫,不過這些dll都是.NET版本的,具備「自描述性」,也就是本身肚子裏都有哪些函數都已經寫在本身的metadata裏了,不用再附加一個.h文件來講明。如今,咱們已經找到了問題的關鍵點:如何用.NET平臺上的C#語言來調用Win32平臺上的dll文件。答案很是簡單:使用DllImport特性。
二. 小試
using System;
using System.Runtime.InteropServices;
class Program
{
[DllImport("User32.dll")]
public static extern int MessageBox(int h, string m, string c, int type);
static int Main()
{
MessageBox(0, "Hello Win32 API", "大爺", 4);
Console.ReadLine();
return 0;
}
}
1. 要使用DllImport這個特性(特性也是一種類),必須使用這一句using System.Runtime.InteropServices;
,導入「運行時->交互服務」。喔~~~~運行時的交互服務不就是「動態連接」嗎?感謝Microsoft!
2. 而後咱們就能夠製造一個DllImport類的實例,並把這個實例綁定在咱們要使用的函數上了。「特性類」這種類很是怪——製造類實例的時候不使用MyClass mc = new MyClass();這種形式,而是使用[特性類(參數列表)]這種形式;特性類不能獨立存在,必定要用做修飾其它目標上(本例是修飾後面的一個函數),不一樣的特性能夠用來修飾類、函數、變量等等;特性類實例在被編譯的時候也不產生可執行代碼,而是被放進metadata裏以備檢索。總之,你記住特性類很怪就是了,想了解更多就查查MSDN,懶得查就先這麼記——不懂慣性定律不影響你學騎自行車。[DllImport("User32.dll")]是說咱們要使用的Win32 API函數在User32.dll這個文件裏。問題又來了:我怎麼知道那麼多API函數都在哪一個dll文件裏呢?這個你能夠在MSDN裏查到,位置是Root->Win32 and COM Development->Development Guides->Windows API->Windows API->Windows API Reference->Functions by Category。打開這頁,你會看到有不少API的分類,API全在這裏了。打開一個分類,好比Dialog Box,在Functions段,你會看到不少具體的函數,其中就有上面用到的MessageBox函數,點擊進入。你將打開MessageBox的詳細解釋和具體用法。在這一頁的底部,你能夠看到一個小表格,裏面有一項「Minimum DLL Version user32.dll」就是說這個函數在user32.dll裏。
3. 接下來就是咱們的函數了。在C#裏調用Win32函數有這麼幾個要點。第一:名字要與Win32 API的徹底同樣。第二:函數除了要有相應的DllImport類修飾外,還要聲明成public static extern類型的。第三:函數的返回值和參數類型要與Win32 API徹底一致!
從MSDN裏摘出一張表來,是經常使用Win32數據類型與.NET平臺數據類型的對應表:
Figure 2 Non-Pointer Data Types
Win32 Types Specification CLR Type
char, INT8, SBYTE, CHAR 8-bit signed integer System.SByte
short, short int, INT16, SHORT 16-bit signed integer System.Int16
int, long, long int, INT32, LONG32, BOOL, INT 32-bit signed integer System.Int32
__int64, INT64, LONGLONG 64-bit signed integer System.Int64
unsigned char, UINT8, UCHAR, BYTE 8-bit unsigned integer System.Byte
unsigned short, UINT16, USHORT, WORD, ATOM, WCHAR, __wchar_t 16-bit unsigned integer System.UInt16
unsigned, unsigned int, UINT32, ULONG32, DWORD32, ULONG, DWORD, UINT 32-bit unsigned integer System.UInt32
unsigned __int64, UINT64, DWORDLONG, ULONGLONG 64-bit unsigned integer System.UInt64
float, FLOAT Single-precision floating point System.Single
double, long double, DOUBLE Double-precision floating point System.Double
In Win32 this type is an integer with a specially assigned meaning; in contrast, the CLR provides a specific type devoted to this meaning.
有了這些東西,咱們就能把一個Win32 API函數轉成C#函數了。還拿MessageBox函數爲例(看剛纔給出的函數表),它的Win32原形以下:
int MessageBox( HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType );
函數名:MessageBox將保持不變。
返回值:int 將保持不變(不管是Win32仍是C#,int都是32位整數)
參數表:H開頭意味着是Handle,通常狀況下Handld都是指針類型,Win32平臺的指針類型是用32位來存儲的,因此在C#里正好對應一個int整型。不過,既然是指針,就沒有什麼正負之分,32位都應該用來保存數值——這樣一來,用uint(無符號32位整型)來對應Win32的H類型更合理。不過提醒你們一點,int是受C#和.NET CLR雙重支持的,而uint只受C#支持而不受.NET CLR支持,因此,本例仍是老老實實地使用了int型。
至於LPCTSTR是Long Pointer to Constant String的縮寫,說白了就是——字符串。因此,用C#裏的string類型就對了。
修飾符:要求有相應的DllImport和public static extern
通過上面一番折騰,Win32的MessageBox函數就包裝成C#能夠調用的函數了:
[DllImport("User32.dll")]
public static extern int MessageBox(int h, string m, string c, int type);
第一個:彈出的MessageBox的父窗口是誰。本例中沒有,因此是0,也就是「空指針」。
第二個:MessageBox的內容。本例中是「Hello Win32 API」。
第三個:MessageBox的標題。
第四個:MessageBox上的按鈕是什麼,若是是0,那就只有一個OK;改爲了4,這樣就有兩個按鈕了。
.NET Framework是對Win32 API的良好封裝,大部分Win32 API函數都已經封裝在了.NET Framework類庫的各個類裏了。
一個例子
寫程序用來控制用戶登陸,在登陸以前,用戶不能把鼠標移出登陸窗體,由於要控制鼠標,調用Win32 API中與Cursor相關的函數,調用了Win32 API中的ClipCursor()這個函數,效果還不錯。
.NET Framework類庫,發現System.Windows.Forms.Cursor類的Clip屬性就是專門作這個用的。
這個例子的目的就是要告訴你們:1.對類庫的瞭解程序直接決定了你編程的效率和質量——用類庫裏的組件比咱們「從輪子造起」要快得多、安全得多。2.不到萬不得已,不要去直接調Win32 API函數——那是不安全的。
private void Form1_DoubleClick(object sender, EventArgs e)
{
Rectangle r = new Rectangle(this.Left, this.Top, this.Width, this.Height);
System.Windows.Forms.Cursor.Clip = r;
}
.NET Framework都爲咱們封裝好了哪些Win32 API,OK,MSDN裏有一篇文章,專門列出了這些。文章的名字是《Microsoft Win32 to Microsoft .NET Framework API Map》
========html
之前整理的Win32 API,能夠直接在C#中直接調用,在作WinForm時仍是頗有幫助的。之前用在一個多窗口界面中,當輪詢窗口時,調用API會提升不少效率。
源碼包含三個文件Win32API.cs,Enums.cs,Structs.cs分別以下
Win32API.cs
using System;
using System.Drawing;
using System.Runtime.InteropServices;
using Lordal.Window.Form.Lib.General;
using Lordal.Window.Form.Lib.Win32;
namespace Lordeo.Framework
{
/// <summary>
/// Windows API Functions
/// </summary>
public class Win32API
{
#region .ctor()
// No need to construct this object
private Win32API()
{
}
#endregion
#region Constans values
public const string TOOLBARCLASSNAME = "ToolbarWindow32";
public const string REBARCLASSNAME = "ReBarWindow32";
public const string PROGRESSBARCLASSNAME = "msctls_progress32";
public const string SCROLLBAR = "SCROLLBAR";
#endregion
#region CallBacks
public delegate IntPtr HookProc(int nCode, IntPtr wParam, IntPtr lParam);
#endregion
#region Kernel32.dll functions
[DllImport("kernel32.dll", ExactSpelling = true, CharSet = CharSet.Auto)]
public static extern int GetCurrentThreadId();
#endregion
#region Gdi32.dll functions
[DllImport("gdi32.dll")]
static public extern bool StretchBlt(IntPtr hDCDest, int XOriginDest, int YOriginDest, int WidthDest, int HeightDest,
IntPtr hDCSrc, int XOriginScr, int YOriginSrc, int WidthScr, int HeightScr, uint Rop);
[DllImport("gdi32.dll")]
static public extern IntPtr CreateCompatibleDC(IntPtr hDC);
[DllImport("gdi32.dll")]
static public extern IntPtr CreateCompatibleBitmap(IntPtr hDC, int Width, int Heigth);
[DllImport("gdi32.dll")]
static public extern IntPtr SelectObject(IntPtr hDC, IntPtr hObject);
[DllImport("gdi32.dll")]
static public extern bool BitBlt(IntPtr hDCDest, int XOriginDest, int YOriginDest, int WidthDest, int HeightDest,
IntPtr hDCSrc, int XOriginScr, int YOriginSrc, uint Rop);
[DllImport("gdi32.dll")]
static public extern IntPtr DeleteDC(IntPtr hDC);
[DllImport("gdi32.dll")]
static public extern bool PatBlt(IntPtr hDC, int XLeft, int YLeft, int Width, int Height, uint Rop);
[DllImport("gdi32.dll")]
static public extern bool DeleteObject(IntPtr hObject);
[DllImport("gdi32.dll")]
static public extern uint GetPixel(IntPtr hDC, int XPos, int YPos);
[DllImport("gdi32.dll")]
static public extern int SetMapMode(IntPtr hDC, int fnMapMode);
[DllImport("gdi32.dll")]
static public extern int GetObjectType(IntPtr handle);
[DllImport("gdi32")]
public static extern IntPtr CreateDIBSection(IntPtr hdc, ref BITMAPINFO_FLAT bmi,
int iUsage, ref int ppvBits, IntPtr hSection, int dwOffset);
[DllImport("gdi32")]
public static extern int GetDIBits(IntPtr hDC, IntPtr hbm, int StartScan, int ScanLines, int lpBits, BITMAPINFOHEADER bmi, int usage);
[DllImport("gdi32")]
public static extern int GetDIBits(IntPtr hdc, IntPtr hbm, int StartScan, int ScanLines, int lpBits, ref BITMAPINFO_FLAT bmi, int usage);
[DllImport("gdi32")]
public static extern IntPtr GetPaletteEntries(IntPtr hpal, int iStartIndex, int nEntries, byte[] lppe);
[DllImport("gdi32")]
public static extern IntPtr GetSystemPaletteEntries(IntPtr hdc, int iStartIndex, int nEntries, byte[] lppe);
[DllImport("gdi32")]
public static extern uint SetDCBrushColor(IntPtr hdc, uint crColor);
[DllImport("gdi32")]
public static extern IntPtr CreateSolidBrush(uint crColor);
[DllImport("gdi32")]
public static extern int SetBkMode(IntPtr hDC, BackgroundMode mode);
[DllImport("gdi32")]
public static extern int SetViewportOrgEx(IntPtr hdc, int x, int y, int param);
[DllImport("gdi32")]
public static extern uint SetTextColor(IntPtr hDC, uint colorRef);
[DllImport("gdi32")]
public static extern int SetStretchBltMode(IntPtr hDC, int StrechMode);
#endregion
#region Uxtheme.dll functions
[DllImport("uxtheme.dll")]
static public extern int SetWindowTheme(IntPtr hWnd, string AppID, string ClassID);
#endregion
#region User32.dll functions
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr GetDC(IntPtr hWnd);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static public extern IntPtr GetDesktopWindow();
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static public extern bool ShowWindow(IntPtr hWnd, short State);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static public extern bool UpdateWindow(IntPtr hWnd);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static public extern bool SetForegroundWindow(IntPtr hWnd);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static public extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int Width, int Height, uint flags);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static public extern bool OpenClipboard(IntPtr hWndNewOwner);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static public extern bool CloseClipboard();
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static public extern bool EmptyClipboard();
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static public extern IntPtr SetClipboardData(uint Format, IntPtr hData);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static public extern bool GetMenuItemRect(IntPtr hWnd, IntPtr hMenu, uint Item, ref RECT rc);
[DllImport("user32.dll", ExactSpelling = true, CharSet = CharSet.Auto)]
public static extern IntPtr GetParent(IntPtr hWnd);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern int SendMessage(IntPtr hWnd, int msg, int wParam, int lParam);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr SendMessage(IntPtr hWnd, int msg, int wParam, IntPtr lParam);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern void SendMessage(IntPtr hWnd, int msg, int wParam, ref RECT lParam);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern int SendMessage(IntPtr hWnd, int msg, int wParam, ref POINT lParam);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern void SendMessage(IntPtr hWnd, int msg, int wParam, ref TBBUTTON lParam);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern void SendMessage(IntPtr hWnd, int msg, int wParam, ref TBBUTTONINFO lParam);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern int SendMessage(IntPtr hWnd, int msg, int wParam, ref REBARBANDINFO lParam);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern void SendMessage(IntPtr hWnd, int msg, int wParam, ref TVITEM lParam);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern void SendMessage(IntPtr hWnd, int msg, int wParam, ref LVITEM lParam);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern void SendMessage(IntPtr hWnd, int msg, int wParam, ref HDITEM lParam);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern void SendMessage(IntPtr hWnd, int msg, int wParam, ref HD_HITTESTINFO hti);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr PostMessage(IntPtr hWnd, int msg, int wParam, int lParam);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr SetWindowsHookEx(int hookid, HookProc pfnhook, IntPtr hinst, int threadid);
[DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
public static extern bool UnhookWindowsHookEx(IntPtr hhook);
[DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
public static extern IntPtr CallNextHookEx(IntPtr hhook, int code, IntPtr wparam, IntPtr lparam);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr SetFocus(IntPtr hWnd);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public extern static int DrawText(IntPtr hdc, string lpString, int nCount, ref RECT lpRect, int uFormat);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public extern static IntPtr SetParent(IntPtr hChild, IntPtr hParent);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public extern static IntPtr GetDlgItem(IntPtr hDlg, int nControlID);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public extern static int GetClientRect(IntPtr hWnd, ref RECT rc);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public extern static int InvalidateRect(IntPtr hWnd, IntPtr rect, int bErase);
[DllImport("User32.dll", CharSet = CharSet.Auto)]
public static extern bool WaitMessage();
[DllImport("User32.dll", CharSet = CharSet.Auto)]
public static extern bool PeekMessage(ref MSG msg, int hWnd, uint wFilterMin, uint wFilterMax, uint wFlag);
[DllImport("User32.dll", CharSet = CharSet.Auto)]
public static extern bool GetMessage(ref MSG msg, int hWnd, uint wFilterMin, uint wFilterMax);
[DllImport("User32.dll", CharSet = CharSet.Auto)]
public static extern bool TranslateMessage(ref MSG msg);
[DllImport("User32.dll", CharSet = CharSet.Auto)]
public static extern bool DispatchMessage(ref MSG msg);
[DllImport("User32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr LoadCursor(IntPtr hInstance, uint cursor);
[DllImport("User32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr SetCursor(IntPtr hCursor);
[DllImport("User32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr GetFocus();
[DllImport("User32.dll", CharSet = CharSet.Auto)]
public static extern bool ReleaseCapture();
[DllImport("User32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr BeginPaint(IntPtr hWnd, ref PAINTSTRUCT ps);
[DllImport("User32.dll", CharSet = CharSet.Auto)]
public static extern bool EndPaint(IntPtr hWnd, ref PAINTSTRUCT ps);
[DllImport("User32.dll", CharSet = CharSet.Auto)]
public static extern bool UpdateLayeredWindow(IntPtr hwnd, IntPtr hdcDst, ref POINT pptDst, ref SIZE psize, IntPtr hdcSrc, ref POINT pprSrc, Int32 crKey, ref BLENDFUNCTION pblend, Int32 dwFlags);
[DllImport("User32.dll", CharSet = CharSet.Auto)]
public static extern bool GetWindowRect(IntPtr hWnd, ref RECT rect);
[DllImport("User32.dll", CharSet = CharSet.Auto)]
public static extern bool ClientToScreen(IntPtr hWnd, ref POINT pt);
[DllImport("User32.dll", CharSet = CharSet.Auto)]
public static extern bool TrackMouseEvent(ref TRACKMOUSEEVENTS tme);
[DllImport("User32.dll", CharSet = CharSet.Auto)]
public static extern bool SetWindowRgn(IntPtr hWnd, IntPtr hRgn, bool redraw);
[DllImport("User32.dll", CharSet = CharSet.Auto)]
public static extern ushort GetKeyState(int virtKey);
[DllImport("User32.dll", CharSet = CharSet.Auto)]
public static extern bool MoveWindow(IntPtr hWnd, int x, int y, int width, int height, bool repaint);
[DllImport("User32.dll", CharSet = CharSet.Auto)]
public static extern int GetClassName(IntPtr hWnd, out STRINGBUFFER ClassName, int nMaxCount);
[DllImport("User32.dll", CharSet = CharSet.Auto)]
public static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
[DllImport("User32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr GetDCEx(IntPtr hWnd, IntPtr hRegion, uint flags);
[DllImport("User32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr GetWindowDC(IntPtr hWnd);
[DllImport("User32.dll", CharSet = CharSet.Auto)]
public static extern int FillRect(IntPtr hDC, ref RECT rect, IntPtr hBrush);
[DllImport("User32.dll", CharSet = CharSet.Auto)]
public static extern int GetWindowPlacement(IntPtr hWnd, ref WINDOWPLACEMENT wp);
[DllImport("User32.dll", CharSet = CharSet.Auto)]
public static extern int SetWindowText(IntPtr hWnd, string text);
[DllImport("User32.dll", CharSet = CharSet.Auto)]
public static extern int GetWindowText(IntPtr hWnd, out STRINGBUFFER text, int maxCount);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static public extern int SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static public extern IntPtr SetClipboardViewer(IntPtr hWndNewViewer);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static public extern int ChangeClipboardChain(IntPtr hWndRemove, IntPtr hWndNewNext);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static public extern int GetSystemMetrics(int nIndex);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static public extern int SetScrollInfo(IntPtr hwnd, int bar, ref SCROLLINFO si, int fRedraw);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern int ShowScrollBar(IntPtr hWnd, int bar, int show);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern int EnableScrollBar(IntPtr hWnd, uint flags, uint arrows);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern int BringWindowToTop(IntPtr hWnd);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern int GetScrollInfo(IntPtr hwnd, int bar, ref SCROLLINFO si);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static public extern int ScrollWindowEx(IntPtr hWnd, int dx, int dy,
ref RECT rcScroll, ref RECT rcClip, IntPtr UpdateRegion, ref RECT rcInvalidated, uint flags);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern int IsWindow(IntPtr hWnd);
[DllImport("user32", CharSet = CharSet.Auto)]
public static extern int GetKeyboardState(byte[] pbKeyState);
[DllImport("user32")]
public static extern int ToAscii(int uVirtKey, //[in] Specifies the virtual-key code to be translated.
int uScanCode, // [in] Specifies the hardware scan code of the key to be translated. The high-order bit of this value is set if the key is up (not pressed).
byte[] lpbKeyState, // [in] Pointer to a 256-byte array that contains the current keyboard state. Each element (byte) in the array contains the state of one key. If the high-order bit of a byte is set, the key is down (pressed). The low bit, if set, indicates that the key is toggled on. In this function, only the toggle bit of the CAPS LOCK key is relevant. The toggle state of the NUM LOCK and SCROLL LOCK keys is ignored.
byte[] lpwTransKey, // [out] Pointer to the buffer that receives the translated character or characters.
int fuState); // [in] Specifies whether a menu is active. This parameter must be 1 if a menu is active, or 0 otherwise.
#endregion
#region Common Controls functions
[DllImport("comctl32.dll")]
public static extern bool InitCommonControlsEx(INITCOMMONCONTROLSEX icc);
[DllImport("comctl32.dll")]
public static extern bool InitCommonControls();
[DllImport("comctl32.dll", EntryPoint = "DllGetVersion")]
public extern static int GetCommonControlDLLVersion(ref DLLVERSIONINFO dvi);
[DllImport("comctl32.dll")]
public static extern IntPtr ImageList_Create(int width, int height, uint flags, int count, int grow);
[DllImport("comctl32.dll")]
public static extern bool ImageList_Destroy(IntPtr handle);
[DllImport("comctl32.dll")]
public static extern int ImageList_Add(IntPtr imageHandle, IntPtr hBitmap, IntPtr hMask);
[DllImport("comctl32.dll")]
public static extern bool ImageList_Remove(IntPtr imageHandle, int index);
[DllImport("comctl32.dll")]
public static extern bool ImageList_BeginDrag(IntPtr imageHandle, int imageIndex, int xHotSpot, int yHotSpot);
[DllImport("comctl32.dll")]
public static extern bool ImageList_DragEnter(IntPtr hWndLock, int x, int y);
[DllImport("comctl32.dll")]
public static extern bool ImageList_DragMove(int x, int y);
[DllImport("comctl32.dll")]
public static extern bool ImageList_DragLeave(IntPtr hWndLock);
[DllImport("comctl32.dll")]
public static extern void ImageList_EndDrag();
#endregion
#region Win32 Macro-Like helpers
public static int GET_X_LPARAM(int lParam)
{
return (lParam & 0xffff);
}
public static int GET_Y_LPARAM(int lParam)
{
return (lParam >> 16);
}
public static Point GetPointFromLPARAM(int lParam)
{
return new Point(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
}
public static int LOW_ORDER(int param)
{
return (param & 0xffff);
}
public static int HIGH_ORDER(int param)
{
return (param >> 16);
}
#endregion
}
}
========c++
C# 用戶常常提出兩個問題:「我爲何要另外編寫代碼來使用內置於 Windows 中的功能?在框架中爲何沒有相應的內容能夠爲我完成這一任務?」當框架小組構建他們的 .NET 部分時,他們評估了爲使 .NET 程序員可使用 Win32 而須要完成的工做,結果發現 Win32 API 集很是龐大。他們沒有足夠的資源爲全部 Win32 API 編寫託管接口、加以測試並編寫文檔,所以只能優先處理最重要的部分。許多經常使用操做都有託管接口,可是還有許多完整的 Win32 部分沒有託管接口。
平臺調用 (P/Invoke) 是完成這一任務的最經常使用方法。要使用 P/Invoke,您能夠編寫一個描述如何調用函數的原型,而後運行時將使用此信息進行調用。另外一種方法是使用 Managed Extensions to C++ 來包裝函數,這部份內容將在之後的專欄中介紹。
要理解如何完成這一任務,最好的辦法是經過示例。在某些示例中,我只給出了部分代碼;完整的代碼能夠經過下載得到。
1.簡單示例
在第一個示例中,咱們將調用 Beep() API 來發出聲音。首先,我須要爲 Beep() 編寫適當的定義。查看 MSDN 中的定義,我發現它具備如下原型:
BOOL Beep(
DWORD dwFreq, // 聲音頻率
DWORD dwDuration // 聲音持續時間
);
要用 C# 來編寫這一原型,須要將 Win32 類型轉換成相應的 C# 類型。因爲 DWORD 是 4 字節的整數,所以咱們可使用 int 或 uint 做爲 C# 對應類型。因爲 int 是 CLS 兼容類型(能夠用於全部 .NET 語言),以此比 uint 更經常使用,而且在多數狀況下,它們之間的區別並不重要。bool 類型與 BOOL 對應。如今咱們能夠用 C# 編寫如下原型:
public static extern bool Beep(int frequency, int duration);
這是至關標準的定義,只不過咱們使用了 extern 來指明該函數的實際代碼在別處。此原型將告訴運行時如何調用函數;如今咱們須要告訴它在何處找到該函數。
咱們須要回顧一下 MSDN 中的代碼。在參考信息中,咱們發現 Beep() 是在 kernel32.lib 中定義的。這意味着運行時代碼包含在 kernel32.dll 中。咱們在原型中添加 DllImport 屬性將這一信息告訴運行時:
[DllImport("kernel32.dll")]
這就是咱們要作的所有工做。下面是一個完整的示例,它生成的隨機聲音在二十世紀六十年代的科幻電影中很常見。
using System;
using System.Runtime.InteropServices;
namespace Beep
{
class Class1
{
[DllImport("kernel32.dll")]
public static extern bool Beep(int frequency, int duration);
static void Main(string[] args)
{
Random random = new Random();
for (int i = 0; i < 10000; i++)
{
Beep(random.Next(10000), 100);
}
}
}
}
它的聲響足以刺激任何聽者!因爲 DllImport 容許您調用 Win32 中的任何代碼,所以就有可能調用惡意代碼。因此您必須是徹底受信任的用戶,運行時才能進行 P/Invoke 調用。
2.枚舉和常量
Beep() 可用於發出任意聲音,但有時咱們但願發出特定類型的聲音,所以咱們改用 MessageBeep()。MSDN 給出瞭如下原型:
BOOL MessageBeep(
UINT uType // 聲音類型
);
這看起來很簡單,可是從註釋中能夠發現兩個有趣的事實。
首先,uType 參數實際上接受一組預先定義的常量。
其次,可能的參數值包括 -1,這意味着儘管它被定義爲 uint 類型,但 int 會更加適合。
對於 uType 參數,使用 enum 類型是合乎情理的。MSDN 列出了已命名的常量,但沒有就具體值給出任何提示。因爲這一點,咱們須要查看實際的 API。
若是您安裝了 Visual Studio? 和 C++,則 Platform SDK 位於 \Program Files\Microsoft Visual Studio .NET\Vc7\PlatformSDK\Include 下。
爲查找這些常量,我在該目錄中執行了一個 findstr。
findstr "MB_ICONHAND" *.h
它肯定了常量位於 winuser.h 中,而後我使用這些常量來建立個人 enum 和原型:
public enum BeepType
{
SimpleBeep = -1,
IconAsterisk = 0x00000040,
IconExclamation = 0x00000030,
IconHand = 0x00000010,
IconQuestion = 0x00000020,
Ok = 0x00000000,
}
[DllImport("user32.dll")]
public static extern bool MessageBeep(BeepType beepType);
如今我能夠用下面的語句來調用它: MessageBeep(BeepType.IconQuestion);
處理結構
有時我須要肯定我筆記本的電池情況。Win32 爲此提供了電源管理函數。
搜索 MSDN 能夠找到 GetSystemPowerStatus() 函數。
BOOL GetSystemPowerStatus(
LPSYSTEM_POWER_STATUS lpSystemPowerStatus
);
此函數包含指向某個結構的指針,咱們還沒有對此進行過處理。要處理結構,咱們須要用 C# 定義結構。咱們從非託管的定義開始:
typedef struct _SYSTEM_POWER_STATUS {
BYTE ACLineStatus;
BYTE BatteryFlag;
BYTE BatteryLifePercent;
BYTE Reserved1;
DWORD BatteryLifeTime;
DWORD BatteryFullLifeTime;
} SYSTEM_POWER_STATUS, *LPSYSTEM_POWER_STATUS;
而後,經過用 C# 類型代替 C 類型來獲得 C# 版本。
public struct SystemPowerStatus
{
public byte ACLineStatus;
public byte batteryFlag;
public byte batteryLifePercent;
public byte reserved1;
public int batteryLifeTime;
public int batteryFullLifeTime;
}
private void button1_Click(object sender, EventArgs e)
{
SystemPowerStatus powerStatus = new SystemPowerStatus();
GetSystemPowerStatus(ref powerStatus);
textBox1.Text = "供電情況:" + powerStatus.ACLineStatus.ToString() + "\r\n"+Environment.NewLine+"剩餘時間:" + powerStatus.batteryLifeTime.ToString() + "\r\n"+Environment.NewLine+"電力剩餘"+powerStatus.batteryLifePercent.ToString()+"%";
}
這樣,就能夠方便地編寫出 C# 原型:
[DllImport("kernel32.dll")]
public static extern bool GetSystemPowerStatus(ref SystemPowerStatus systemPowerStatus);
在此原型中,咱們用「ref」指明將傳遞結構指針而不是結構值。這是處理經過指針傳遞的結構的通常方法。
此函數運行良好,可是最好將 ACLineStatus 和 batteryFlag 字段定義爲 enum:
enum ACLineStatus: byte
{
Offline = 0,
Online = 1,
Unknown = 255,
}
enum BatteryFlag: byte
{
High = 1,
Low = 2,
Critical = 4,
Charging = 8,
NoSystemBattery = 128,
Unknown = 255,
}
C# <wbr>win32 <wbr>API編程
請注意,因爲結構的字段是一些字節,所以咱們使用 byte 做爲該 enum 的基本類型。
3.字符串
雖然只有一種 .NET 字符串類型,但這種字符串類型在非託管應用中卻有幾項獨特之處。可使用具備內嵌字符數組的字符指針和結構,其中每一個數組都須要正確的封送處理。
在 Win32 中還有兩種不一樣的字符串表示:
ANSI
Unicode
最初的 Windows 使用單字節字符,這樣能夠節省存儲空間,但在處理不少語言時都須要複雜的多字節編碼。Windows NT? 出現後,它使用雙字節的 Unicode 編碼。爲解決這一差異,Win32 API 採用了很是聰明的作法。它定義了 TCHAR 類型,該類型在 Win9x 平臺上是單字節字符,在 WinNT 平臺上是雙字節 Unicode 字符。對於每一個接受字符串或結構(其中包含字符數據)的函數,Win32 API 均定義了該結構的兩種版本,用 A 後綴指明 Ansi 編碼,用 W 指明 wide 編碼(即 Unicode)。若是您將 C++ 程序編譯爲單字節,會得到 A 變體,若是編譯爲 Unicode,則得到 W 變體。Win9x 平臺包含 Ansi 版本,而 WinNT 平臺則包含 W 版本。
因爲 P/Invoke 的設計者不想讓您爲所在的平臺操心,所以他們提供了內置的支持來自動使用 A 或 W 版本。若是您調用的函數不存在,互操做層將爲您查找並使用 A 或 W 版本。
經過示例可以很好地說明字符串支持的一些精妙之處。
4.簡單字符串
下面是一個接受字符串參數的函數的簡單示例:
BOOL GetDiskFreeSpace(
LPCTSTR lpRootPathName, // 根路徑
LPDWORD lpSectorsPerCluster, // 每一個簇的扇區數
LPDWORD lpBytesPerSector, // 每一個扇區的字節數
LPDWORD lpNumberOfFreeClusters, // 可用的扇區數
LPDWORD lpTotalNumberOfClusters // 扇區總數
);
根路徑定義爲 LPCTSTR。這是獨立於平臺的字符串指針。
因爲不存在名爲 GetDiskFreeSpace() 的函數,封送拆收器將自動查找「A」或「W」變體,並調用相應的函數。咱們使用一個屬性來告訴封送拆收器,API 所要求的字符串類型。
如下是該函數的完整定義,就象我開始定義的那樣:
[DllImport("kernel32.dll")]
static extern bool GetDiskFreeSpace([MarshalAs(UnmanagedType.LPTStr)]
string rootPathName,ref int sectorsPerCluster,ref int bytesPerSector,
ref int numberOfFreeClusters,ref int totalNumberOfClusters);
不幸的是,當我試圖運行時,該函數不能執行。問題在於,不管咱們在哪一個平臺上,封送拆收器在默認狀況下都試圖查找 API 的 Ansi 版本,因爲 LPTStr 意味着在 Windows NT 平臺上會使用 Unicode 字符串,所以試圖用 Unicode 字符串來調用 Ansi 函數就會失敗。
有兩種方法能夠解決這個問題:一種簡單的方法是刪除 MarshalAs 屬性。若是這樣作,將始終調用該函數的 A 版本,若是在您所涉及的全部平臺上都有這種版本,這是個很好的方法。可是,這會下降代碼的執行速度,由於封送拆收器要將 .NET 字符串從 Unicode 轉換爲多字節,而後調用函數的 A 版本(將字符串轉換回 Unicode),最後調用函數的 W 版本。
要避免出現這種狀況,您須要告訴封送拆收器,要它在 Win9x 平臺上時查找 A 版本,而在 NT 平臺上時查找 W 版本。要實現這一目的,能夠將 CharSet 設置爲 DllImport 屬性的一部分:
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
private static extern bool GetDiskFreeSpace(
string rootPathName,
out int sectorsPerCluster,
out int bytesPerSector,
out int numberOfFreeClusters,
out int totalNumberOfClusters);
private void button1_Click(object sender, EventArgs e)
{
string root=@"F:\";
int sectorsPerCluster, bytesPerSector, numberOfFreeClusters, totalNumberOfClusters;
GetDiskFreeSpace(root, out sectorsPerCluster, out bytesPerSector, out numberOfFreeClusters, out totalNumberOfClusters);
textBox1.Text = root+"\r\n"+"每一個簇的扇區數:" + sectorsPerCluster + "\r\n" + "每一個扇區的字節數:" + bytesPerSector + "\r\n" + "可用的扇區數:" + numberOfFreeClusters + "\r\n" + "扇區總數:" + totalNumberOfClusters;
}
C# <wbr>win32 <wbr>API編程
對於大多數 Win32 API,均可以對字符串類型設置 CharSet 屬性並使用 LPTStr。可是,還有一些不採用 A/W 機制的函數,對於這些函數必須採起不一樣的方法。
5.字符串緩衝區
.NET 中的字符串類型是不可改變的類型,這意味着它的值將永遠保持不變。對於要將字符串值複製到字符串緩衝區的函數,字符串將無效。這樣作至少會破壞由封送拆收器在轉換字符串時建立的臨時緩衝區;嚴重時會破壞託管堆,而這一般會致使錯誤的發生。不管哪一種狀況都不可能得到正確的返回值。
要解決此問題,咱們須要使用其餘類型。StringBuilder 類型就是被設計爲用做緩衝區的,咱們將使用它來代替字符串。下面是一個示例:
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
public static extern int GetShortPathName([MarshalAs(UnmanagedType.LPTStr)]string path,
[MarshalAs(UnmanagedType.LPTStr)]StringBuilder shortPath,int shortPathLength);
使用此函數很簡單:
StringBuilder shortPath = new StringBuilder(80);
int result = GetShortPathName(@"d:\test.jpg", shortPath, shortPath.Capacity);
string s = shortPath.ToString();
請注意,StringBuilder 的 Capacity 傳遞的是緩衝區大小。
6.具備內嵌字符數組的結構
某些函數接受具備內嵌字符數組的結構。例如,GetTimeZoneInformation() 函數接受指向如下結構的針:
typedef struct _TIME_ZONE_INFORMATION {
LONG Bias;
WCHAR StandardName[ 32 ];
SYSTEMTIME StandardDate;
LONG StandardBias;
WCHAR DaylightName[ 32 ];
SYSTEMTIME DaylightDate;
LONG DaylightBias;
} TIME_ZONE_INFORMATION, *PTIME_ZONE_INFORMATION;
在 C# 中使用它須要有兩種結構。一種是 SYSTEMTIME,它的設置很簡單:
struct SystemTime
{
public short wYear;
public short wMonth;
public short wDayOfWeek;
public short wDay;
public short wHour;
public short wMinute;
public short wSecond;
public short wMilliseconds;
}
這裏沒有什麼特別之處;另外一種是 TimeZoneInformation,它的定義要複雜一些:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
struct TimeZoneInformation
{
public int bias;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string standardName;
SystemTime standardDate;
public int standardBias;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string daylightName;
SystemTime daylightDate;
public int daylightBias;
}
此定義有兩個重要的細節。第一個是 MarshalAs 屬性:
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
查看 ByValTStr 的文檔,咱們發現該屬性用於內嵌的字符數組;另外一個是 SizeConst,它用於設置數組的大小。
我在第一次編寫這段代碼時,遇到了執行引擎錯誤。一般這意味着部分互操做覆蓋了某些內存,代表結構的大小存在錯誤。我使用 Marshal.SizeOf() 來獲取所使用的封送拆收器的大小,結果是 108 字節。我進一步進行了調查,很快回憶起用於互操做的默認字符類型是 Ansi 或單字節。而函數定義中的字符類型爲 WCHAR,是雙字節,所以致使了這一問題。
我經過添加 StructLayout 屬性進行了更正。結構在默認狀況下按順序佈局,這意味着全部字段都將以它們列出的順序排列。CharSet 的值被設置爲 Unicode,以便始終使用正確的字符類型。
通過這樣處理後,該函數一切正常。您可能想知道我爲何不在此函數中使用 CharSet.Auto。這是由於,它也沒有 A 和 W 變體,而始終使用 Unicode 字符串,所以我採用了上述方法編碼。
7.具備回調的函數
當 Win32 函數須要返回多項數據時,一般都是經過回調機制來實現的。開發人員將函數指針傳遞給函數,而後針對每一項調用開發人員的函數。
在 C# 中沒有函數指針,而是使用「委託」,在調用 Win32 函數時使用委託來代替函數指針。
EnumDesktops() 函數就是這類函數的一個示例:
BOOL EnumDesktops(
HWINSTA hwinsta, // 窗口實例的句柄
DESKTOPENUMPROC lpEnumFunc, // 回調函數
LPARAM lParam // 用於回調函數的值
);
HWINSTA 類型由 IntPtr 代替,而 LPARAM 由 int 代替。DESKTOPENUMPROC 所需的工做要多一些。下面是 MSDN 中的定義:
BOOL CALLBACK EnumDesktopProc(
LPTSTR lpszDesktop, // 桌面名稱
LPARAM lParam // 用戶定義的值
);
咱們能夠將它轉換爲如下委託:
delegate bool EnumDesktopProc([MarshalAs(UnmanagedType.LPTStr)] string desktopName,int lParam);
完成該定義後,咱們能夠爲 EnumDesktops() 編寫如下定義:
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern bool EnumDesktops(IntPtr windowStation,EnumDesktopProc callback,int lParam);
這樣該函數就能夠正常運行了。
在互操做中使用委託時有個很重要的技巧:封送拆收器建立了指向委託的函數指針,該函數指針被傳遞給非託管函數。可是,封送拆收器沒法肯定非託管函數要使用函數指針作些什麼,所以它假定函數指針只需在調用該函數時有效便可。
結果是若是您調用諸如 SetConsoleCtrlHandler() 這樣的函數,其中的函數指針將被保存以便未來使用,您就須要確保在您的代碼中引用委託。若是不這樣作,函數可能表面上能執行,但在未來的內存回收處理中會刪除委託,而且會出現錯誤。
8.其餘高級函數
迄今爲止我列出的示例都比較簡單,可是還有不少更復雜的 Win32 函數。下面是一個示例:
DWORD SetEntriesInAcl(
ULONG cCountOfExplicitEntries, // 項數
PEXPLICIT_ACCESS pListOfExplicitEntries, // 緩衝區
PACL OldAcl, // 原始 ACL
PACL *NewAcl // 新 ACL
);
前兩個參數的處理比較簡單:ulong 很簡單,而且可使用 UnmanagedType.LPArray 來封送緩衝區。
但第三和第四個參數有一些問題。問題在於定義 ACL 的方式。ACL 結構僅定義了 ACL 標頭,而緩衝區的其他部分由 ACE 組成。ACE 能夠具備多種不一樣類型,而且這些不一樣類型的 ACE 的長度也不一樣。
若是您願意爲全部緩衝區分配空間,而且願意使用不太安全的代碼,則能夠用 C# 進行處理。但工做量很大,而且程序很是難調試。而使用 C++ 處理此 API 就容易得多。
9.屬性的其餘選項
DLLImport 和 StructLayout 屬性具備一些很是有用的選項,有助於 P/Invoke 的使用。下面列出了全部這些選項:
DLLImport
CallingConvention
您能夠用它來告訴封送拆收器,函數使用了哪些調用約定。您能夠將它設置爲您的函數的調用約定。一般,若是此設置錯誤,代碼將不能執行。可是,若是您的函數是 Cdecl 函數,而且使用 StdCall(默認)來調用該函數,那麼函數可以執行,但函數參數不會從堆棧中刪除,這會致使堆棧被填滿。
CharSet
控制調用 A 變體仍是調用 W 變體。
EntryPoint
此屬性用於設置封送拆收器在 DLL 中查找的名稱。設置此屬性後,您能夠將 C# 函數從新命名爲任何名稱。
ExactSpelling
將此屬性設置爲 true,封送拆收器將關閉 A 和 W 的查找特性。
PreserveSig
COM 互操做使得具備最終輸出參數的函數看起來是由它返回的該值。此屬性用於關閉這一特性。
SetLastError
確保調用 Win32 API SetLastError(),以便您找出發生的錯誤。
StructLayout
LayoutKind
結構在默認狀況下按順序佈局,而且在多數狀況下都適用。若是須要徹底控制結構成員所放置的位置,可使用 LayoutKind.Explicit,而後爲每一個結構成員添加 FieldOffset 屬性。當您須要建立 union 時,一般須要這樣作。
CharSet
控制 ByValTStr 成員的默認字符類型。
Pack
設置結構的壓縮大小。它控制結構的排列方式。若是 C 結構採用了其餘壓縮方式,您可能須要設置此屬性。
Size
設置結構大小。不經常使用;可是若是須要在結構末尾分配額外的空間,則可能會用到此屬性。
從不一樣位置加載
您沒法指定但願 DLLImport 在運行時從何處查找文件,可是能夠利用一個技巧來達到這一目的。
DllImport 調用 LoadLibrary() 來完成它的工做。若是進程中已經加載了特定的 DLL,那麼即便指定的加載路徑不一樣,LoadLibrary() 也會成功。
這意味着若是直接調用 LoadLibrary(),您就能夠從任何位置加載 DLL,而後 DllImport LoadLibrary() 將使用該 DLL。
因爲這種行爲,咱們能夠提早調用 LoadLibrary(),從而將您的調用指向其餘 DLL。若是您在編寫庫,能夠經過調用 GetModuleHandle() 來防止出現這種狀況,以確保在首次調用 P/Invoke 以前沒有加載該庫。
P/Invoke 疑難解答
若是您的 P/Invoke 調用失敗,一般是由於某些類型的定義不正確。如下是幾個常見問題:
1.long != long。在 C++ 中,long 是 4 字節的整數,但在 C# 中,它是 8 字節的整數。
2.字符串類型設置不正確。
========程序員
1.Win32 API與C#數據結構類型對應關係表
API數據類型
類型描述
C#類型
API數據類型
類型描述
C#類型
WORD
16位無符號整數
ushort
CHAR
字符
char
LONG
32位無符號整數
int
DWORDLONG
64位長整數
long
DWORD
32位無符號整數
uint
HDC
設備描述表句柄
int
HANDLE
句柄,32位整數
int
HGDIOBJ
GDI對象句柄
int
UINT
32位無符號整數
uint
HINSTANCE
實例句柄
int
BOOL
32位布爾型整數
bool
HWM
窗口句柄
int
LPSTR
指向字符的32位指針
string
HPARAM
32位消息參數
int
LPCSTR
指向常字符的32位指針
String
LPARAM
32位消息參數
int
BYTE
字節
byte
WPARAM
32位消息參數
int
2.C# 數據類型
簡單類型
描 述
示 例
sbyte
8-bit 有符號整數
sbyte val = 12;
short
16-bit 有符號整數
short val = 12;
int
32-bit有符號整數
int val = 12;
long
64-bit有符號整數
long val1 = 12; long val2 = 34L;
byte
8-bit無符號整數
byte val1 = 12; byte val2 = 34U;
ushort
16-bit 無符號整數
ushort val1 = 12; ushort val2 = 34U;
uint
32-bit 無符號整數
uint val1 = 12; uint val2 = 34U;
ulong
64-bit 無符號整數
ulong val1 = 12; ulong val2 = 34U; ulong val3 = 56L; ulong val4 = 78UL;
float
32-bit單精度浮點數
float val = 1.23F;
double
64-bit雙精度浮點數
double val1 = 1.23; double val2 = 4.56D;
bool
布爾類型
bool val1 = true; bool val2 = false;
char
字符類型 ,Unicode編碼
char val = 'h';
decimal
28個有效數字的128-bit十進制類型
decimal val = 1.23M;
3.Win32 API與C#數據結構類型對應關係
BOOL=System.Int32
BOOLEAN=System.Int32
BYTE=System.UInt16
CHAR=System.Int16
COLORREF=System.UInt32
DWORD=System.UInt32
DWORD32=System.UInt32
DWORD64=System.UInt64
FLOAT=System.Float
HACCEL=System.IntPtr
HANDLE=System.IntPtr
HBITMAP=System.IntPtr
HBRUSH=System.IntPtr
HCONV=System.IntPtr
HCONVLIST=System.IntPtr
HCURSOR=System.IntPtr
HDC=System.IntPtr
HDDEDATA=System.IntPtr
HDESK=System.IntPtr
HDROP=System.IntPtr
HDWP=System.IntPtr
HENHMETAFILE=System.IntPtr
HFILE=System.IntPtr
HFONT=System.IntPtr
HGDIOBJ=System.IntPtr
HGLOBAL=System.IntPtr
HHOOK=System.IntPtr
HICON=System.IntPtr
HIMAGELIST=System.IntPtr
HIMC=System.IntPtr
HINSTANCE=System.IntPtr
HKEY=System.IntPtr
HLOCAL=System.IntPtr
HMENU=System.IntPtr
HMETAFILE=System.IntPtr
HMODULE=System.IntPtr
HMONITOR=System.IntPtr
HPALETTE=System.IntPtr
HPEN=System.IntPtr
HRGN=System.IntPtr
HRSRC=System.IntPtr
HSZ=System.IntPtr
HWINSTA=System.IntPtr
HWND=System.IntPtr
INT=System.Int32
INT32=System.Int32
INT64=System.Int64
LONG=System.Int32
LONG32=System.Int32
LONG64=System.Int64
LONGLONG=System.Int64
LPARAM=System.IntPtr
LPBOOL=System.Int16[]
LPBYTE=System.UInt16[]
LPCOLORREF=System.UInt32[]
LPCSTR=System.String
LPCTSTR=System.String
LPCVOID=System.UInt32
LPCWSTR=System.String
LPDWORD=System.UInt32[]
LPHANDLE=System.UInt32
LPINT=System.Int32[]
LPLONG=System.Int32[]
LPSTR=System.String
LPTSTR=System.String
LPVOID=System.UInt32
LPWORD=System.Int32[]
LPWSTR=System.String
LRESULT=System.IntPtr
PBOOL=System.Int16[]
PBOOLEAN=System.Int16[]
PBYTE=System.UInt16[]
PCHAR=System.Char[]
PCSTR=System.String
PCTSTR=System.String
PCWCH=System.UInt32
PCWSTR=System.UInt32
PDWORD=System.Int32[]
PFLOAT=System.Float[]
PHANDLE=System.UInt32
PHKEY=System.UInt32
PINT=System.Int32[]
PLCID=System.UInt32
PLONG=System.Int32[]
PLUID=System.UInt32
PSHORT=System.Int16[]
PSTR=System.String
PTBYTE=System.Char[]
PTCHAR=System.Char[]
PTSTR=System.String
PUCHAR=System.Char[]
PUINT=System.UInt32[]
PULONG=System.UInt32[]
PUSHORT=System.UInt16[]
PVOID=System.UInt32
PWCHAR=System.Char[]
PWORD=System.Int16[]
PWSTR=System.String
REGSAM=System.UInt32
SC_HANDLE=System.IntPtr
SC_LOCK=System.IntPtr
SHORT=System.Int16
SIZE_T=System.UInt32
SSIZE_=System.UInt32
TBYTE=System.Char
TCHAR=System.Char
UCHAR=System.Byte
UINT=System.UInt32
UINT32=System.UInt32
UINT64=System.UInt64
ULONG=System.UInt32
ULONG32=System.UInt32
ULONG64=System.UInt64
ULONGLONG=System.UInt64
USHORT=System.UInt16
WORD=System.UInt16
WPARAM=System.IntPtr
========數據庫
using System.Runtime.InteropServices;
步驟2:導入Win32 API函數
[DllImport("user32.dll", EntryPoint = "SendMessage")]
private static extern int SendMessage(IntPtr wnh, int msg, IntPtr wP, IntPtr IP);
步驟3:例子
private void toolStripButton2_Click(object sender, EventArgs e)
{
if (textBox1.Text != "")
{
switch (MessageBox.Show("即將清除數據,是否保存數據!", "信息提示", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Warning))
{
case DialogResult.Yes:
SendMessage(button1.Handle, 0xf5, (IntPtr)0, (IntPtr)0);
count_Ping_Number = 0;
label7.Text = "0米";
break;
case DialogResult.No:
textBox1.Text = "";
count_Ping_Number = 0;
label7.Text = "0米";
break;
case DialogResult.Cancel:
break;
}
}
}
/////////////////////////////////////////////////////////////////////////////////////////////
利用進程間通訊:SendMessage(button1.Handle, 0xf5, (IntPtr)0, (IntPtr)0); 操做button1的_Click事件。
========編程
目前,網上關於C#進程間通訊的方法有不少種,可是總結起來它們不外乎從如下兩個方面進行考慮:
1、在兩個進程之間創建一個共同區域,其中一個進程改變這個區域的內容,而另外一個進程則去讀取它,反之亦 然。好比,可讓兩個進程共享同一塊內存,經過改變和讀取內存中的內容進行通訊;或者,建立一個文件,兩個進程同時佔用,甚至能夠利用註冊表或者剪貼板充當這個「共同區域」。
2、利用API函數去找到進程窗口的句柄,而後用API去控制這個窗口。例如,導入「User32.dll」中的FindWindow、FindWindowEx函數查找窗口,並獲取窗口句柄,也可直接利用C#中的Process類來啓動程序,並獲取這個進程的主窗口的句柄,等等。
在編程時,咱們每每須要選擇一種即方便編寫,效率又高的程序。第一種類型相對比較複雜,並且效率不高,相比來說,第二種類型在不下降程序運行效率的狀況下編寫更簡單。下面我就以一個示例程序來說解如何使用Process類和API實現兩個進程之間的傳輸數據。
第一步:
(1)打開VS2008,新建一個「windows 應用程序」,主窗口爲Form1
(2)在Form1上添加一個標籤label1,併爲Form1添加KeyDown事件,當Form1接收到KewDown消息時,將接收到的數據顯示在label1上。
public Form1()
{
InitializeComponent();
////////////////////添加KeyDown事件///////////////////
KeyDown+=new KeyEventHandler(Form1_KeyDown);
}
/////////////////具體實現/////////////////////////////
private void Form1_KeyDown(object sender, KeyEventArgs e)
{
this.label1.Text = Convert.ToString(e.KeyValue);
}
(3)編譯運行,生成Form1.exe
第二步:
(1)打開VS2008,新建一個「windows 應用程序」,主窗口爲Form2,並在Form2上添加三個按鈕和一個文本 框,分別爲button1,button2,button3,textbox1
(2)在Form2.cs中添加引用:
using System.Diagnostics;
using System.Runtime.InteropServices;
並導入Win32 API函數:
[DllImport("User32.dll", EntryPoint = "SendMessage")]
private static extern int SendMessage(IntPtr wnd,int msg,IntPtr wP,IntPtr lP);
(3)在Form2類中定義如下兩個變量
ProcessStartInfo psInfo = new ProcessStartInfo(@"..\..\..\Form1\bin\Debug\Form1.exe");
Process pro = new Process();
(4)爲Form2添加Load事件響應
private void Form1_Load(object sender, EventArgs e)
{
pro.StartInfo = psInfo ;
}
(5)爲button1,button2,button3分別添加click事件響應,並添加響應內容:
Button1: pro.Start();
點擊該按鈕,啓動Form1.exe程序
Button2: pro.Kill();
點擊該按鈕,退出From1.exe程序
Button3:
IntPtr hWnd = pro.MainWindowHandle; //獲取Form1.exe主窗口句柄
int data = Convert.ToInt32(this.textBox1.Text); //獲取文本框數據
SendMessage(hWnd, 0x0100, (IntPtr)data, (IntPtr)0); //發送WM_KEYDOWN消息
點擊該按鈕,以文本框數據爲參數,向Form1發送WM_KEYDOWN消息
(6)編譯運行,生成Form2.exe
第三步:
將Form1文件夾拷貝到與Form2同一目錄下,啓動Form2.exe:
點擊button1按鈕,則Form1.exe啓動
點擊button2按鈕,則Form1.exe退出
在Form1.exe程序正在運行的狀況下,在Form2窗口的文本框中輸入任意數字並點擊button3按鈕,Form1窗口的label1即顯示該數字。
以上只是簡單的介紹了利用C#的Process類和Win32 API函數實現進程之間的數據傳輸,讀者能夠根據實際狀況觸類旁通,編寫功能更增強大的程序。
========c#
api 函數是構築windws應用程序的基石,每一種windows應用程序開發工具,它提供的底層函數都間接或直接地調用了windows api函數,同時爲了實現功能擴展,通常也都提供了調用windowsapi函數的接口, 也就是說具有調用動態鏈接庫的能力。visual c#和其它開發工具同樣也可以調用動態連接庫的api函數。.net框架自己提供了這樣一種服務,容許受管轄的代碼調用動態連接庫中實現的非受管轄函數, 包括操做系統提供的windows api函數。它可以定位和調用輸出函數,根據須要,組織其各個參數(整型、字符串類型、數組、和結構等等)跨越互操做邊界。
下面以c#爲例簡單介紹調用api的基本過程:
動態連接庫函數的聲明
動態連接庫函數使用前必須聲明,相對於vb,c#函數聲明顯得更加羅嗦,前者經過 api viewer粘貼之後,能夠直接使用,然後者則須要對參數做些額外的變化工做。
動態連接庫函數聲明部分通常由下列兩部分組成,一是函數名或索引號,二是動態連接庫的文件名。
譬如,你想調用user32.dll中的messagebox函數,咱們必須指明函數的名字messageboxa或messageboxw, 以及庫名字 user32.dll,咱們知道win32 api對每個涉及字符串和字符的函數通常都存在兩個版本,單字節字符的ansi版本和雙字節字符的unicode版本。
下面是一個調用api函數的例子:
[dllimport("kernel32.dll", entrypoint="movefilew", setlasterror=true, charset=charset.unicode, exactspelling=true, callingconvention=callingconvention.stdcall)]
public static extern bool movefile(string src, string dst);
其中入口點entrypoint標識函數在動態連接庫的入口位置,在一個受管轄的工程中,目標函數的原始名字和序號入口點不只標識一個跨越互操 做界限的函數。並且,你還能夠把這個入口點映射爲一個不一樣的名字,也就是對函數進行重命名。重命名能夠給調用函數帶來種種便利,經過重命名,一方面咱們不 用爲函數的大小寫傷透腦筋,同時它也能夠保證與已有的命名規則保持一致,容許帶有不一樣參數類型的函數共存,更重要的是它簡化了對ansi和unicode 版本的調用。charset用於標識函數調用所採用的是unicode或是ansi版本,exactspelling=false將告訴編譯器,讓編譯器 決定使用 unicode或者是ansi版本。其它的參數請參考msdn在線幫助.
在c#中,你能夠在entrypoint域經過名字和序號聲明一個動態連接庫函數,若是在方法定義中使用的函數名與dll入口點相同,你不須要在entrypoint域顯示聲明函數。不然,你必須使用下列屬性格式指示一個名字和序號。
[dllimport("dllname", entrypoint="functionname")] [dllimport("dllname", entrypoint="#123")]
值得注意的是,你必須在數字序號前加「#」
下面是一個用msgbox替換messagebox名字的例子:
using system.runtime.interopservices; public class win32
{
[dllimport("user32.dll", entrypoint="messagebox")]
public static extern int msgbox(int hwnd, string text, string caption, uint type);
}
許多受管轄的動態連接庫函數指望你可以傳遞一個複雜的參數類型給函數,譬如一個用戶定義的結構類型成員或者受管轄代碼定義的一個類成員,這時你必須提供額外的信息格式化這個類型,以保持參數原有的佈局和對齊。
c# 提供了一個structlayoutattribute類,經過它你能夠定義本身的格式化類型,在受管轄代碼中,格式化類型是一個用 structlayoutattribute說明的結構或類成員,經過它可以保證其內部成員預期的佈局信息。佈局的選項共有三種:
佈局選項
描述
layoutkind.automatic
爲了提升效率容許運行態對類型成員從新排序。
注意:永遠不要使用這個選項來調用不受管轄的動態連接庫函數。
layoutkind.explicit
對每一個域按照fieldoffset屬性對類型成員排序
layoutkind.sequential
對出如今受管轄類型定義地方的不受管轄內存 <http://product.it168.com/list/b/0205_1.shtml>中的類型成員進行排序。
傳遞結構成員
下面的例子說明如何在受管轄代碼中定義一個點和矩形類型,並做爲一個參數傳遞給user32.dll庫中的ptinrect函數,
函數的不受管轄原型聲明以下:
bool ptinrect(const rect *lprc, point pt);
注意你必須經過引用傳遞rect結構參數,由於函數須要一個rect的結構指針。
[c#] using system.runtime.interopservices;
[structlayout(layoutkind.sequential)]
public struct point { public int x; public int y; }
[structlayout(layoutkind.explicit]
public struct rect
{
[fieldoffset(0)] public int left;
[fieldoffset(4)] public int top;
[fieldoffset(8)] public int right;
[fieldoffset(12)] public int bottom;
}
class win32api
{
[dllimport("user32.dll")]
public static extern bool ptinrect(ref rect r, point p);
}
相似你能夠調用getsysteminfo函數得到系統信息:
using system.runtime.interopservices;
[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;
}
[dllimport("kernel32")]
static extern void getsysteminfo(ref system_info psi);
system_info psi = new system_info(); getsysteminfo(ref psi);
類成員的傳遞
一樣只要類具備一個固定的類成員佈局,你也能夠傳遞一個類成員給一個不受管轄的動態連接庫函數,下面的例子主要說明如何傳遞一個 sequential順序定義的mysystemtime類給user32.dll的getsystemtime函數, 函數用c/c++調用規範以下:
void getsystemtime(systemtime* systemtime);
不像傳值類型,類老是經過引用傳遞參數.
[c#]
[structlayout(layoutkind.sequential)]
public class mysystemtime
{
public ushort wyear;
public ushort wmonth;
public ushort wdayofweek;
public ushort wday;
public ushort whour;
public ushort wminute;
public ushort wsecond;
public ushort wmilliseconds;
}
class win32api
{
[dllimport("user32.dll")]
public static extern void getsystemtime(mysystemtime st);
}
回調函數的傳遞:
從受管轄的代碼中調用大多數動態連接庫函數,你只需建立一個受管轄的函數定義,而後調用它便可,這個過程很是直接。
若是一個動態連接庫函數須要一個函數指針做爲參數,你還須要作如下幾步:
首先,你必須參考有關這個函數的文檔,肯定這個函數是否須要一個回調;第二,你必須在受管轄代碼中建立一個回調函數;最後,你能夠把指向這個函數的指針做爲一個參數創遞給dll函數,.
回調函數及其實現:
回調函數常常用在任務須要重複執行的場合,譬如用於枚舉函數,譬如win32 api 中的enumfontfamilies(字體枚舉), enumprinters(打印機 <http://print.it168.com/>), enumwindows (窗口枚舉)函數. 下面以窗口枚舉爲例,談談如何經過調用 enumwindow 函數遍歷系統中存在的全部窗口
分下面幾個步驟:
1. 在實現調用前先參考函數的聲明
bool enumwindows(wndenumproc lpenumfunc, lparmam iparam)
顯然這個函數須要一個回調函數地址做爲參數.
2. 建立一個受管轄的回調函數,這個例子聲明爲表明類型(delegate),也就是咱們所說的回調,它帶有兩個參數hwnd和lparam,第一個參數是一個窗口句柄,第二個參數由應用程序定義,兩個參數均爲整形。
當這個回調函數返回一個非零值時,標示執行成功,零則暗示失敗,這個例子老是返回true值,以便持續枚舉。
3. 最後建立以表明對象(delegate),並把它做爲一個參數傳遞給enumwindows 函數,平臺會自動地 把表明轉化成函數可以識別的回調格式。
using system;
using system.runtime.interopservices;
public delegate bool callback(int hwnd, int lparam);
public class enumreportapp
{
[dllimport("user32")]
public static extern int enumwindows(callback x, int y);
public static void main()
{
callback mycallback = new callback(enumreportapp.report);
enumwindows(mycallback, 0);
}
public static bool report(int hwnd, int lparam)
{
console.write("窗口句柄爲");
console.writeline(hwnd); return true;
}
}
指針類型參數傳遞:
在windows api函數調用時,大部分函數採用指針傳遞參數,對一個結構變量指針,咱們除了使用上面的類和結構方法傳遞參數以外,咱們有時還能夠採用數組傳遞參數。
下面這個函數經過調用getusername得到用戶名
bool getusername( lptstr lpbuffer, // 用戶名緩衝區 lpdword nsize // 存放緩衝區大小的地址指針 );
[dllimport("advapi32.dll", entrypoint="getcomputername", exactspelling=false, setlasterror=true)]
static extern bool getcomputername ( [marshalas(unmanagedtype.lparray)] byte[] lpbuffer, [marshalas(unmanagedtype.lparray)] int32[] nsize );
這個函數接受兩個參數,char * 和int *,由於你必須分配一個字符串緩衝區以接受字符串指針,你可使用string類代替這個參
========windows
最近在學習C#中的GDI部分,原本嘗試編寫一個字幕控件(其實仍是用label比較合適),可是發現控件中用GDI將整個控件粉刷貌似不行(應該是我水平不行),因此就去搗鼓了下WIN32的DLL,發現用API還真是件幸福的事(僅在WIN32平臺上說)。回到C#,在C#中要在一個窗體(控件也是窗體),只要用
Graphics g=控件名.CreateGraphics();//這樣就能夠用g來在這個控件上畫東西了。
可是若是我想不限範圍,在整個屏幕上畫,那麼.NET就無能爲力了。還好,咱們有WIN32,咱們能夠用GetDC或者CreateDC來得到整個屏幕的設備驅動器句柄。用完以後別忘了用ReleaseDC或DeleteDC釋放。
如下是C#中GetDC()和ReleaseDC()的聲明方法
[System.Runtime.InteropServices.DllImport("User32.dll")]
static extern IntPtr GetDC(IntPtr Hwnd); //其在MSDN中原型爲HDC GetDC(HWND hWnd),HDC和HWND都是驅動器句柄(長指針),在C#中只能用IntPtr代替了
[System.Runtime.InteropServices.DllImport("User32.dll")]
static extern int ReleaseDC( IntPtr hWnd, IntPtr hDC);
而後咱們得到整個屏幕的設備驅動器句柄
Hdc = GetDC(IntPtr.Zero); //MSDN中說當傳入指針爲空時返回整個屏幕的設備驅動器句柄
嘿嘿,接下來咱們就能夠利用這個設備驅動器句柄來亂畫東西了,不過在此以前咱們先把這個C#不常見的東西轉化爲熟悉的Graphics。咱們只要用
Graphics g = Graphics.FromHdc(Hdc);//這樣就從設備驅動器句柄中得到了.NET只能的Graphics類。
獲得了這些東西,剩下的就不用我多說了吧,這些來你們就能夠在這個屏幕上愛怎麼畫就怎麼畫。不過畫完以後記得調用ReleaseDC()來釋放這個句柄(若是畫完程序就結束那倒無所謂)。
ReleaseDC(IntPtr.Zero, Hdc); //這樣這個屏幕的設備驅動器句柄就被釋放了。
========api
0、前言
從VB到C#,被人詬病比較多的就是交互性比較差,又集中表如今調用Win32 API上。若是說C/C++調用API只是調用函數這類輕鬆的活,在C#下卻成了阻擋入門者的技術活。之因此產生這麼大區別在於數據類型的差別,就是由於C#這類採用了「安全」的類型,咱們避免了內存釋放和內存訪問錯誤的一些困擾,可是不得不面對調用API時的繁瑣。有得必有失,關鍵看你選擇了什麼。
在調用API時,對於值類型的數據,不存在什麼轉換問題,只要搞清楚究竟是Byte、Int1六、Int32 仍是Int64就能夠了,比較麻煩的地方是指針,由於C#中沒有辦法顯性的使用指針,有時須要藉助unsafe code達到這個目的。若是都「unsafe」了,那還用C#幹嘛,本文的目的就是總結一下,怎樣用「safe」的方式解決Win32 API中指針類型參數的問題。
一、 基本原則
在咱們在調用API時,若是發現參數中有指針類型的時候,不要簡單的用IntPtr去替換,或者直接就是用*來定義。雖然C#中可以使用指針,可是這樣作就違背了C#設計時的初衷,此外DotNET Framework平臺下使用unsafe代碼多少會影響應用程序的效率。
當咱們拿到一個API,閱讀API的說明時,必定要關注如下幾點:
l 每個參數的數據類型是什麼?若是是指針,指針指向的是一個什麼數據結構,基本數據類型、字符串、結構還就是一塊內存。不一樣的類型在C#下處理的模式是不一樣的。
l 指針所指向的數據結構是誰建立,該由誰釋放?這也很是重要,它兩層含義:一個是咱們怎麼定義接口,而且準備調用參數;另外一個就是資源釋放的問題,某些調用這申請,被調用這釋放的資源,須要約定的方法申請或釋放資源,反之亦然。
只要花點時間分析一下,就會發現即使是在複雜的結構,不用「unsafe code」也可以完成調用,只不過有時候過程有點繁瑣,不如C/C++調用API那麼暢快淋漓。可是我想說的是,若是選擇了C#,那麼就是C#的思想去解決問題,這樣纔可以發揮出C#全部的潛力。
二、實例分析
瞭解了基本原則,下面就逐一分析一下怎樣雅緻而且「安全」地解決不一樣類型指針的調用問題。
2.一、 字符串
字符串應該是咱們接觸到最多的狀況,通常在API定義中被描述爲「LPSTR/LPTSTR/LPCTSTR/LPWSTR」之類,咱們在申明API接口的時候,若是是傳入類型的參數,直接用String類型申明便可,例如:
/**////<summary>
///原型是:HMODULE LoadLibrary(LPCTSTR lpFileName);
///</summary>
///<param name="lpFileName">DLL 文件名</param>
///<returns>函數庫模塊的句柄</returns>
[DllImport("kernel32.dll")]
public static extern IntPtr LoadLibrary(string lpFileName);
可是若是是傳出類型的字符串參數,簡單的這麼寫就不行了。個人理解是String變成LPSTR,是DotNET Framework的交互接口幫咱們作了一次轉換,建立了一個字符數組,將咱們提供的String複製了一次,再傳遞給API,並不是簡單的指針傳遞,因此當咱們要求在咱們設定的一個地址區域去寫數據時,就不可以直接申明爲String,而應該是Byte或者Char數組,能夠參考下面的例子:
函數聲明:
/**////<summary>
/// int GetClassName(HWND hWnd, LPTSTR lpClassName, int nMaxCount);
///</summary>
[DllImport("user32",CharSet=CharSet.Ansi)]
public static extern Int32 GetClassName(IntPtr hwnd, Byte[] lpClassName, Int32 nMaxCount);
調用事例:
String sClassName = null;
Byte[] abClassName = null;
Int32 dwRet = 0;
abClassName = new Byte[100];
dwRet = GetClassName(this.Handle, abClassName, 100);
sClassName = System.Text.ASCIIEncoding.ASCII.GetString(abClassName,0,dwRet);
MessageBox.Show(sClassName);
還須要注意一點的就是Ansi仍是Unicode的字符集了,申明的是什麼就用什麼轉換。
2.二、 句柄—Handle
句柄嚴格意義上來講不能歸在指針這一類,句柄是本宏定義掩蓋了的一種數據結構,不過行爲上和指針有些相似。最多見的有窗口句柄、Socket句柄還有內核對象的句柄等。總之H開頭的一些定義基本都是句柄。
對於句柄來講咱們一般沒法直接訪問句柄所表明的那個數據結構,只要記錄句柄值就能夠了,並且咱們並不關心句柄這個值的內容,只要他有效就好了,因此句柄最容易處理。通常Win32下,句柄就是一個32位的整型,因此用Int32/UInt32或者IntPtr申明便可。仍是上面那個例子,HMODULE就是一個句柄。
2.三、 基本類型的指針
兩種狀況下會出現基本類型的指針:一種是基本類型的地址,表示返回類型的參數;一種是表示傳遞一個基本類型的數組,這兩種狀況須要分別對待。
返回類型,C#中有專門的修飾符ref,表示參數傳遞按地址傳送。缺省狀況下參數都是按值傳遞的,若是但願按照地址傳遞,只要在參數前添加ref的修飾符便可。例如:
/**////<summary>
///原形:BOOL ReadFile(HANDLE hFile, LPVOID lpBuffer, DWORD nNumberOfBytesToRead, LPDWORD lpNumberOfBytesRead, LPOVERLAPPED lpOverlapped);
///</summary>
[DllImport("kernel32.dll")]
public extern static Int32 ReadFile(IntPtr hFile, Byte[] buffer,Int32 nNumberOfBytesToRead, ref Int32 lpNumberOfBytesRead, ref OVERLAPPED lpOverlapped);
對於C/C++中數組參數,就是一塊連續內存的首地址。在C#中的數組默認都是從Array派生的類,雖然結構複雜了,可是內存佈局應該是相同的,因此只要把參數定義爲基本類型的數組就能夠了。例如:
/**////<summary>
/// BOOL GetCharWidth(HDC hdc,UINT iFirstChar,UINT iLastChar,LPINT lpBuffer);
///</summary>
[DllImport("gdi32")]
public static extern Int32 GetCharWidth(HDC hdc, Int32 wFirstChar, Int32 wLastChar, int32[] lpBuffer);
2.四、 結構
說到結構,先要解釋一下C#中數據類型的分類。C#中的數據類型通常有兩種,一種是值類型,就是Byte、Int32之流,出於反射的須要,值類型都是從ValueType派生而得;一種是引用類型,從Object派生出來的類都是引用類型。所謂值類型,就是賦值和傳遞了傳的是數據自己,引用類型傳遞的是數據所對應實例的引用,C#中結構(以struct定義的)是值類型的,類(以class定義的)是引用類型的。
實際調用API時,API參數若是是一個自定義結構指針的話,一般把數據結構定義爲struct,在申明時函數接口時用ref修飾。例如Guid就是DotNET類庫中內建的一個結構,具體用法以下:
///<summary>
///原形:HRESULT WINAPI GetDeviceID(LPCGUID pGuidSrc, LPGUID pGuidDest);
///</summary>
///<param name="pGuidSrc"></param>
///<param name="pGuidDest"></param>
///<returns></returns>
[DllImport("Dsound.dll")]
private static extern Int32 GetDeviceID(ref Guid pGuidSrc, ref Guid pGuidDest);
若是自定義結構的話,結構在內存中佔據的字節數務必要匹配,當結構中包含數組的時候須要用MarshalAsAttribute屬性進行修飾,設定數組長度。具體能夠參考下面的例子:
///<summary>
///原形:
/// typedef struct tagPAINTSTRUCT {
/// HDC hdc;
/// BOOL fErase;
/// RECT rcPaint;
/// BOOL fRestore;
/// BOOL fIncUpdate;
/// BYTE rgbReserved[32];
/// } PAINTSTRUCT;
///</summary>
public struct PAINTSTRUCT
...{
public IntPtr hdc;
public Boolean fErase;
public RECT rcPaint;
public Boolean fRestore;
public Boolean fIncUpdate;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]public Byte[] rgbReserved;
}
/**////<summary>
///原形:HDC BeginPaint( HWND hwnd, LPPAINTSTRUCT lpPaint);
///</summary>
[DllImport("user32")]
public static extern IntPtr BeginPaint(IntPtr hwnd, ref PAINTSTRUCT lpPaint);
2.五、 結構數組
結構數組就比較複雜了,就我我的的經驗,不一樣廠商提供的API,實現不一樣,須要採用的處理方式也不相同的。
通常狀況下,參照基本類型數組的調用方式便可,例如:
///<summary>
///原形:
/// typedef struct tagACCEL {
/// BYTE fVirt;
/// WORD key;
/// WORD cmd;
/// } ACCEL, *LPACCEL;
///</summary>
public struct ACCEL
{
public Byte fVirt;
public UInt16 key;
public UInt16 cmd;
}
///<summary>
///原形:int CopyAcceleratorTable(HACCEL hAccelSrc,LPACCEL lpAccelDst,int cAccelEntries);
///</summary>
///<returns></returns>
[DllImport("user32")]
public static extern Int32 CopyAcceleratorTable(IntPtr hAccelSrc, ACCEL[] lpAccelDst, Int32 cAccelEntries);
可是也有特殊狀況,對些廠商提供的API中,不知是否和內存複製的方式有關,相似的函數,若是採用上面相同的定義方法調用的話,調用正確,可是應該返回的數據沒有被改寫。這個時候就須要另外一種方法來解決了。
衆所周知,在邏輯上結構是一段連續的內存,數組也是一段連續內存,咱們能夠從堆中直接申請一段內存,調用API,而後將返回的數據再轉換成結構便可。具體能夠參看下面的例子。
結構定義以及API聲明:
[StructLayout(LayoutKind.Sequential, Pack = 8)]
private struct CmBoxInfo
{
public static CmBoxInfo Empty = new CmBoxInfo();
public byte MajorVersion;
public byte MinorVersion;
public ushort BoxMask;
public uint SerialNumber;
public ushort BoxKeyId;
public ushort UserKeyId;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = CM_PUBLIC_KEY_LEN)] public byte[] BoxPublicKey;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = CM_PUBLIC_KEY_LEN)] public byte[] SerialPublicKey;
public uint Reserve;
public void Init()
{
BoxPublicKey = new byte[CM_PUBLIC_KEY_LEN];
Debug.Assert(BoxPublicKey != null);
SerialPublicKey = new byte[CM_PUBLIC_KEY_LEN];
Debug.Assert(SerialPublicKey != null);
}
}
///<summary>
///原型:int CMAPIENTRY CmGetBoxes(HCMSysEntry hcmse, unsigned long idPort, CMBOXINFO *pcmBoxInfo, unsigned int cbBoxInfo)
///</summary>
[DllImport("xyz.dll")]
private static extern Int32 CmGetBoxes(IntPtr hcmse, CmGetBoxesOption idPort,IntPtr pcmBoxInfo, Int32 cbBoxInfo);
調用示例
IntPtr hcmBoxes = IntPtr.Zero;
CmAccess cma = new CmAccess();
CmBoxInfo[] aBoxList = null;
Int32 dwBoxNum = 0, dwLoop = 0,dwBoxInfoSize = 0;
IntPtr pBoxInfo = IntPtr.Zero;
dwBoxNum = m_pCmGetBoxes(hcmBoxes, CmGetBoxesOption.AllPorts, IntPtr.Zero, 0);
if (dwBoxNum > 0)
{
aBoxList = new CmBoxInfo[dwBoxNum];
if (aBoxList != null)
{
dwBoxInfoSize = Marshal.SizeOf(aBoxList[0]);
pBoxInfo = Marshal.AllocHGlobal(dwBoxInfoSize * dwBoxNum);
if (pBoxInfo != IntPtr.Zero)
{
dwBoxNum = m_pCmGetBoxes(hcmBoxes, CmGetBoxesOption.AllPorts, pBoxInfo, dwBoxNum);
for (dwLoop = 0; dwLoop < dwBoxNum; dwLoop++)
{
aBoxList[dwLoop] = (CmBoxInfo)Marshal.PtrToStructure((IntPtr)((UInt32)pBoxInfo + dwBoxInfoSize * dwLoop), CmBoxInfo.Empty.GetType());
}
Marshal.FreeHGlobal(pBoxInfo);
pBoxInfo = IntPtr.Zero;
}
else
{
aBoxList = null;
}
}
}
最後提一句,Marshal類很是有用,其中包括了大量內存申請、複製和類型轉換的函數,靈活運用的話,基本上能夠避免unsafe code。
2.六、 函數指針(回調函數)
C#中採用委託(delegate)和函數指針等同的功能,當API函數的參數爲回調函數時,咱們一般使用委託來替代。與C和C++ 中的函數指針相比,委託其實是具體一個Delegate派生類的實例,它還包括了對參數和返回值,類型安全的檢查。
先看一下下面的例子:
///<summary>
///原形:typedef BOOL (CALLBACK *LPDSENUMCALLBACKA)(LPGUID, LPCSTR, LPCSTR, LPVOID);
///</summary>
public delegate Boolean LPDSENUMCALLBACK(IntPtr guid, String sDesc, String sDevName, ref Int32 dwFlag);
///<summary>
///原形:HRESULT WINAPI DirectSoundCaptureEnumerateA(LPDSENUMCALLBACKA pDSEnumCallback, LPVOID pContext);
///</summary>
[DllImport("Dsound.dll")]
public static extern Int32 DirectSoundCaptureEnumerate(LPDSENUMCALLBACK pDSEnumCallBack, ref Int32 dwFlag);
具體調用方法以下:
dwRet = DirectSoundEnumerate(new LPDSENUMCALLBACK(DSoundEnumCallback),ref dwFlag);
這裏須要特別注意的就是委託其實是一個實例,和普通的類實例同樣,是被DotNET Framework垃圾收集機制所管理,有生存週期的。上文例子的定義方式其實函數級別的局部變量,當函數結束時,將被釋放,若是回調仍然在繼續的話,就會產生諸如非法訪問的錯誤。因此在使用回調函數的時候必定要比較清楚的瞭解,回調的做用週期是多大,若是回調是全局的,那麼定義一個全局的委託變量做爲參數。
2.七、 表示多種類型的指針—LPVOID以及其它
指針是C/C++的精髓所在,一個void可以應付全部的問題,咱們遇到最多的可能就是LPVOID這樣的參數。LPVOID最經常使用的有兩種狀況,一種就是表示一個內存塊,另外一種狀況多是根據其它參數的定義指向不一樣的數據結構。
第一種狀況很好處理,若是是一個內存塊,咱們能夠他看成一個Byte數組就能夠了,例如:
/**////<summary>
///原形:BOOL ReadFile(HANDLE hFile, LPVOID lpBuffer, DWORD nNumberOfBytesToRead, LPDWORD lpNumberOfBytesRead, LPOVERLAPPED lpOverlapped);
///</summary>
[DllImport("kernel32.dll")]
public extern static Int32 ReadFile(IntPtr hFile, Byte[] buffer,Int32 nNumberOfBytesToRead, ref Int32 lpNumberOfBytesRead, ref OVERLAPPED lpOverlapped);
第二種狀況比較複雜,C#中類型轉換是有限制的,一個Int32是無法直接轉換成爲Point的,這個時候之可以根據不一樣的參數類型定義不一樣的重載函數了。例如GetProcAddress函數的lpProcName既能夠是一個字符串表示函數名,又能夠是一個高字爲0的Int32類型,表示函數的序號,咱們能夠這樣分別定義:
[c-sharp] view plain copy print?
///<summary>
///原型是: FARPROC GetProcAddress(HMODULE hModule,LPCSTR lpProcName);
///</summary>
[DllImport("kernel32.dll", EntryPoint = "GetProcAddress")]
private extern static IntPtr GetProcAddress(IntPtr hModule, String sFuncName);
[DllImport("kernel32.dll", EntryPoint = "GetProcAddress")]
private extern static IntPtr GetProcAddressByIndex(IntPtr hModule, Int32 dwIndex);
在這裏總結了調用API時有關指針的一些常見問題,你會發現大多數狀況下C#依靠自身的能力就能解決問題,但願對你們有幫助。
========數組
微軟.NET框架的類庫功能強大,內容豐富,能夠處理大部分的編程要求,但在一些場合下,.NET框架類庫不提供支持,此時須要藉助Win32API來實現了。
通常的小弟不推薦在.NET程序中使用Win32API,由於這樣不符合.NET編程的安全框架,並且讓程序綁定到特定的操做系統,由於將來你的.NET程序可能不在Win32操做系統下運行。
推薦歸推薦,有時候要用還得用,在此說說.NET下使用Win32API,本人如今使用C#,所以在此只說說C#語言來使用Win32API。
在C#使用Win32API很方便,只須要從指定的DLL文件中導入函數聲明便可。好比在某個類中寫下
[System.Runtime.InteropServices.DllImport("gdi32.dll")]
public static extern int GetDeviceCaps(int hDC , int index );
此後程序就像調用靜態函數同樣調用這個API函數了。這有點相似VB中聲明API了。
你們知道,Win32API是平面結構,將一個DLL中的API羅列出來,成百上千的API一字排開,好生壯觀,惋惜使用不方便,調用它們須要瞭解各個API入口和出口以及各個API之間的影響,並且一些API比較危險,須要當心調用,若在錯誤的時間使用錯誤的參數調用錯誤的API則可能致使系統資源泄漏,程序忽然退出,甚至會傷害操做系統。
而.NET類庫則是樹狀的立體結構,它使用了面向對象的編程結構,從而掩蓋了不少細節,咱們調用也方便,也很安全。
之前小弟爲了調用API就是在到用的時候就聲明API,或者做個模塊,列出不少API聲明供其餘地方調用。經驗告訴我,這種簡單的用法不夠好。因而小弟就開始使用C#的語法結構來封裝API。
在此使用API函數GetDeviceCaps做例子,這個函數就是得到指定設備上下文的某些信息,參數爲設備上下文句柄(hdc)和類型爲DeviceCapsConst信息編號。這個設備上下文句柄是個比較重的句柄,嚴禁浪費,用完只後須要釋放掉該句柄,但釋放HDC並不簡單,須要知道句柄的來源,當使用CreateDC得到的句柄則必須使用DeleteDC來釋放。
對於比較重要的資源,好比句柄,文件,數據庫鏈接等等,有個原則就是盡晚得到,儘早釋放,所以須要儘可能減小持有句柄的時間。
小弟定義了一個類DeviceCapsClass,用於封裝GetDeviceCaps,在得到一個句柄時就當即屢次調用GetDeviceCaps來得到全部的信息,而後保存到一個緩衝區中,而後當即釋放句柄。並定義了方便的訪問緩衝數據的接口。這樣其餘程序只須要實例化一個DeviceCapsClass,調用它的屬性就可得到設備上下文信息而無需考慮細節。
其實這種作法.NET框架類庫本身就這麼搞,你們看看.NET類庫的反編譯結果,能夠看到.NET類庫中有不少就是Win32API的封裝。相信你們已經這麼作了或未來也這樣作。
現列出DeviceCapsClass全部代碼
[System.Runtime.InteropServices.DllImport("gdi32.dll", CharSet=System.Runtime.InteropServices.CharSet.Auto)]
private static extern int GetDeviceCaps(int hDC , int index );
[System.Runtime.InteropServices.DllImport("User32.dll", CharSet=System.Runtime.InteropServices.CharSet.Auto)]
private static extern int ReleaseDC(int hWnd, int hDC);
[System.Runtime.InteropServices.DllImport("gdi32.dll", CharSet=System.Runtime.InteropServices.CharSet.Auto)]
private static extern int CreateDC( string strDriver , string strDevice , int Output , int InitData );
[System.Runtime.InteropServices.DllImport("gdi32.dll", CharSet=System.Runtime.InteropServices.CharSet.Auto)]
private static extern int DeleteDC( int Hdc );
========安全
http://blog.csdn.net/jiangxinyu/article/details/8088992
C#中調用WIN32API函數
http://blog.csdn.net/jiangxinyu/article/details/8098600
C#調用win32API畫圖函數示例
http://blog.csdn.net/jiangxinyu/article/details/8098291
C# 抽取exe和dll程序圖標
========
C#經過WIN32 API實現嵌入程序窗體 C#利用win32 Api 修改本地系統時間、獲取硬盤序列號 c#使用win32api實現獲取光標位置 C# Win32 API大全(無錯版) C#調用WIN32API系列二列舉局網內共享打印機 C# 使用WIN32API獲取打印機 詳細講解在C#中如何使用鼠標鉤子 分享基於Win32 API的服務操做類 C#封裝好的Win32API ========