C#【高级篇】 .NET平台调用Win32 API
前言
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采用了“动态链接库”的办法。之所以叫“动态链接库”,是因为这样的函数库的调用方式是“随用随取”而不是像静态链接库那样“用不用都要带上”。
2、Win32 API放在哪?
Win32 API函数是放在Windows系统的核心库文件中的,这些库在硬盘里的存储形式是.dll文件。我们常用到的dll文件User32.dll
和kernel32.dll
两个文件。
这些dll文件是用C语言写的,源代码经C语言编译器编译之后,会以二进制可执行代码形式存放在这些dll文件中。为了能让程序使用这些函数,微软在发布每个新的操作系统的时候,也会放出这个系统的SDK。
SDK里有一些C语言的头文件(.h文件),这些文件里描述了核心dll文件里都有哪些Win32 API函数,在写程序的时候,把这些.h文件用#include"…"指令包含进你的程序(C/C++程序)里,你就可以使用这些Win32 API了。
3、C#如何调用Win32 API函数?
C#语言也使用dll动态链接库,不过这些dll都是.NET版本的,具有“自描述性”,也就是自己肚子里都有哪些函数都已经写在自己的metadata里了,不用再附加一个.h文件来说明。
现在,我们已经找到了问题的关键点:如何用.NET平台上的C#语言来调用Win32平台上的dll文件。答案非常简单:使用DllImport特性。
【注意:】
1.对类库的了解,直接决定了你编程的效率和质量——用类库里的组件比我们“从轮子造起”要快得多、安全得多。
2.不到万不得已,不要去直接调Win32 API函数——那是不安全的。
4、.NET框架为何不包括所有的Win32 API?
.NET Framework是对Win32 API的良好封装,大部分Win32 API函数都已经封装在了.NET Framework类库的各个类里了。
C# 用户经常提出两个问题:“我为什么要另外编写代码来使用内置于 Windows 中的功能?在框架中为什么没有相应的内容可以为我完成这一任务?”
当框架小组构建他们的 .NET 部分时,他们评估了为使 .NET 程序员可以使用 Win32 而需要完成的工作,结果发现 Win32 API 集非常庞大。他们没有足够的资源为所有 Win32 API 编写托管接口、加以测试并编写文档,因此只能优先处理最重要的部分。许多常用操作都有托管接口,但是还有许多的 Win32 部分没有托管接口。
补充:
.NET Framework都为我们封装好了哪些Win32 API?
MSDN里有一篇文章,专门列出了这些。文章的名字是《Microsoft Win32 to Microsoft .NET Framework API Map》
5、如何在MSDN中查询Win32 API函数?
MSDN官网:https://msdn.microsoft.com/zh-cn/
(1)MSDN在线查看
例如我们查找MessageBox函数:
-
搜索“Windows API Index”,一步步查找
-
直接查找“MessageBox”
(2)MSDN离线查看
输入的关键字是MSDN Library。以下是下载MSDN Library的链接:
微软官网下载:https://www.microsoft.com/en-us/download/details.aspx?id=20955
其他链接:http://download.microsoft.com/download/1/f/0/1f07c259-7ff2-4902-9205-ad1dfb87ccab/VS2008SP1MSDNENUX1506188.iso
下载之后解压iso文件,找到setup文件夹下的setup.exe双击安装,也可以使用虚拟光驱安装。
安装后,在点击 开始->所有应用—>找到MSDN Library for Visual Studio 2008 SP1 点击就可以使用了。
二、C#调用Win32 API函数的要点
- public static extern后边的函数名字要与Win32 API的完全一样(DllImport中没有EntryPoint时。如果有EntryPoint,名字可以由用户自定义。
详见示例1
)。 - 函数除了要有相应的DllImport类修饰外,还要声明成public static extern类型的。
- 函数的返回值和参数类型要与Win32 API完全一致!
三、常用 Win32数据类型与.NET平台数据类型的对应表
附加:Win32类型和.net类型的对应表
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
四、几个示例
1、弹出一个MessageBox对话框
(1)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的内容。
第三个:MessageBox的标题。
第四个:MessageBox上的按钮是什么,如果是0,那就只有一个OK;改成了4,这样就有两个按钮了。
(2)C#源代码:
using System;
using System.Runtime.InteropServices;//必须引入的【运行时.交互服务】(运行时的交互服务不就是“动态链接”吗?感谢Microsoft!)
class Program
{
[DllImport("User32.dll")]//制造一个DllImport类的实例,并把这个实例绑定在我们要使用的函数上!
public static extern int MessageBox(int h, string m, string c, int type);
static int Main()
{
MessageBox(0, "内容:Hello Win32 API", "标题:风格0", 0);
MessageBox(0, "内容:Hello Win32 API", "标题:风格1", 1);
MessageBox(0, "内容:Hello Win32 API", "标题:风格2", 2);
MessageBox(0, "内容:Hello Win32 API", "标题:风格3", 3);
MessageBox(0, "内容:Hello Win32 API", "标题:风格4", 4);
Console.ReadLine();
return 0;
}
}
运行结果【依次弹出如下对话框】:
进一步测试:
加上入口函数名称后,进一步测试:
2、调用 Beep() API 来发出声音
(1)函数原型:
BOOL Beep(
DWORD dwFreq, // 声音频率
DWORD dwDuration // 声音持续时间
);
MSDN中的查询结果:
(2)分析:
由于 DWORD 是 4 字节的整数,因此我们可以使用 int 或 uint 作为 C# 对应类型。由于 int 是 CLS 兼容类型(可以用于所有 .NET 语言),以此比 uint 更常用,并且在多数情况下,它们之间的区别并不重要。bool 类型与 BOOL 对应。
(3)编写C#原型:
[DllImport("kernel32.dll")]
public static extern bool Beep(int frequency, int duration);
(4)C#源代码【生成的随机声音在二十世纪六十年代的科幻电影中很常见】:
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);
}
Console.WriteLine("End");
}
}
}
参考:
https://blog.csdn.net/bcbobo21cn/article/details/50930221
C#调用Win32常见API函数汇总: https://blog.csdn.net/zhengzhe1937/article/details/8528363