今天有人找我解决一些软件相关的问题,然后发现会牵扯到一些Win32的API
一般调用Win32API都是用C++方便的,而且微软给的文档也是C++的
我虽然对C++不是很熟,但对C#熟啊
在C#中,我们可以通过P/Invoke去做动态库的互操作
但对于Win32API,如果自己写P/Invoke,里面就有很多不确定的东西
光是那个DllImport特性里面的参数就不是很好填
那C#开发者是不是很难做Win32开发呢,那也不是
在Nuget上有很多别的大佬包装好的Win32库,安装完可以直接调用
但Win32中有超级多的API,这会导致代码提示里面会多出非常多东西,确实是有点干扰了
但好在微软也提供了一个解决方案,CsWin32
这个是基于C#源生成器的Win32API封装器
就是需要什么API,它就去生成那些API的P/Invoke代码
并且生成的质量还挺高的,保留原本的调用风格的同时,尽可能去迎合C#的开发体验
<0x00> 安装CsWin32
因为会用到C#的源生成器,所以最好使用VS2022并且相对新的版本,不然代码提示之类的会出问题
VSCode的源生成器体验也是不错的,这个直接装C# kit插件就可以了
直接在Nuget中搜索CsWin32,包名是Microsoft.Windows.CsWin32
这个包最低支持到.net framework 4.5也即.NET Standard 2.0
对于这个版本,还需要安装System.Memory
如果是.NET Standard 2.1及以上,也即.net 5.0及以上的版本,那么就不需要安装
(如果是新项目的话应该会用新版本的框架吧)
<0x01> 如何使用
添加需要的API
首先是要告诉源生成器要生成哪些API的封装
在项目根目录新建叫NativeMethods.txt的文件
在里面可以添加你需要添加的Win32函数或者结构的名字
PssCaptureSnapshot
PssQuerySnapshot
PSS_PROCESS_INFORMATION
PSS_HANDLE_INFORMATION
PSS_VA_CLONE_INFORMATION
这里的示例是Windows进程快照相关的API,使用的API也不多,仅做演示作用
简单解释下使用的API
PssCaptureSnapshot
捕获目标进程的快照
DWORD PssCaptureSnapshot(
[in] HANDLE ProcessHandle,
[in] PSS_CAPTURE_FLAGS CaptureFlags,
[in, optional] DWORD ThreadContextFlags,
[out] HPSS *SnapshotHandle
);
ProcessHandle目标进程的句柄CaptureFlags指定要捕获的标志ThreadContextFlags如果 CaptureFlags 指定线程上下文,则要捕获的 CONTEXT 记录标志SnapshotHandle返回此函数捕获的快照的句柄
函数返回的是winerror.h中定义的错误代码,无错误是ERROR_SUCCESS
PssQuerySnapshot
查询捕获的快照的信息
DWORD PssQuerySnapshot(
[in] HPSS SnapshotHandle,
[in] PSS_QUERY_INFORMATION_CLASS InformationClass,
[out] void *Buffer,
[in] DWORD BufferLength
);
SnapshotHandle要查询的快照的句柄InformationClass用于选择要查询的信息Buffer此函数提供的信息,类型由InformationClass决定BufferLength缓冲区的大小(以字节为单位)
函数返回的是winerror.h中定义的错误代码,无错误是ERROR_SUCCESS
剩下的是一些结构,东西多就不细讲了,跟本文关系不大,具体就看文档吧
在代码中使用Win32API
这里做一个使用Win32API查询进程PID的示例
(虽然C#的Process类对象本身就可以直接查询)
using System.Diagnostics;
using Windows.Win32;
using Windows.Win32.System.Diagnostics.ProcessSnapshotting;
public class Program
{
public static void Main()
{
// 使用C#自带的Process类型获取记事本进程
Process test = Process.GetProcessesByName("notepad")[0];
// 声明一个查询flags
PSS_CAPTURE_FLAGS flags = PSS_CAPTURE_FLAGS.PSS_CAPTURE_THREADS;
// 使用Win32API来捕获进程快照
PInvoke.PssCaptureSnapshot(
test.SafeHandle,
flags,
0,
out HPSS snapshotHandle);
// 声明查询进程基本信息的变量
PSS_PROCESS_INFORMATION info;
// 涉及到指针操作,所以要用unsafe块包装
unsafe
{
// 使用Win32API来查询进程快照信息
PInvoke.PssQuerySnapshot(
snapshotHandle,
PSS_QUERY_INFORMATION_CLASS.PSS_QUERY_PROCESS_INFORMATION,
&info,
(uint)sizeof(PSS_PROCESS_INFORMATION));
}
Console.WriteLine(info.ProcessId);
}
}

确实是获取到了记事本的PID
而且观察代码,不难发现,CsWin32会非常智能地使用C#自带的类型
比如说Process类型里面的SafeHandle属性,这个返回的是SafeProcessHandle
这个虽然它的命名空间是Microsoft.Win32.SafeHandles,但确实是C#本身就有的
对于C#不带的类型,只有使用的API需要这些类型传参,CsWin32才会去生成对应的代码
<0x02> 一些不知道类型的枚举
如果你需要使用某个枚举,但不知道是什么类型,CsWin32可以自动指出具体类型
比如前面提到那两个API的返回值是winerror.h中定义的错误代码,但我们不知道这是什么类
这时候可以在NativeMethods.txt里面直接加上ERROR_SUCCESS
CsWin32会抛出警告:应该使用正确的声明![]()
最后也指出正确的声明是WIN32_ERROR,文件里改好就行
这里对上面的代码稍作修改作为示例
using System.Diagnostics;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.System.Diagnostics.ProcessSnapshotting;
public class Program
{
public static void Main()
{
Process test = Process.GetProcessesByName("notepad")[0];
PSS_CAPTURE_FLAGS flags = PSS_CAPTURE_FLAGS.PSS_CAPTURE_THREADS;
PInvoke.PssCaptureSnapshot(
test.SafeHandle,
flags,
0,
out HPSS snapshotHandle);
PSS_PROCESS_INFORMATION info;
// 添加错误码的声明
uint errorCode;
unsafe
{
// 获取错误码返回值
errorCode = PInvoke.PssQuerySnapshot(
snapshotHandle,
PSS_QUERY_INFORMATION_CLASS.PSS_QUERY_PROCESS_INFORMATION,
&info,
(uint)sizeof(PSS_PROCESS_INFORMATION));
}
Console.WriteLine(info.ProcessId);
// 跟WIN32_ERROR.ERROR_SUCCESS做比较,返回True
Console.WriteLine((WIN32_ERROR)errorCode == WIN32_ERROR.ERROR_SUCCESS);
}
}
当然头铁不改正确的声明也没关系,也是这样使用
对于其他不知道怎么声明的枚举也可以这样让CsWin32去找