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 ========