C#教程

关注公众号 jb51net

关闭
首页 > 软件编程 > C#教程 > C# 调用 Win32 API

C# 调用 Win32 API的实现示例

作者:工程师007

Win32 API是微软为 Windows 操作系统提供的底层编程接口,包含了操作系统的核心功能,本文就来详细的介绍一下C# 调用 Win32 API的实现示例,感兴趣的可以了解一下

一、核心概念解析

1. 什么是 Win32 API?

Win32 API(Windows 32-bit Application Programming Interface)是微软为 Windows 操作系统提供的底层编程接口,包含了操作系统的核心功能(如窗口管理、文件操作、进程控制、内存管理、系统信息获取等),本质上是一组用 C/C++ 编写的原生函数。

2. C# 为什么能调用 Win32 API?

C# 运行在 .NET 运行时(CLR)中,属于托管代码;而 Win32 API 是非托管代码(直接运行在操作系统层面)。.NET 提供了 P/Invoke(Platform Invocation Services,平台调用服务) 机制,这是 CLR 提供的核心功能,允许托管代码调用非托管的函数(如 Win32 API)。

3. P/Invoke 核心要素

要在 C# 中调用 Win32 API,必须满足以下条件:

二、Win32 API 调用的语法规则

1. 基础声明格式

在 C# 中,通过 DllImport 特性(位于 System.Runtime.InteropServices 命名空间)声明 Win32 API 函数,核心语法如下:

using System.Runtime.InteropServices; // 必须引入此命名空间

class Win32Api
{
    // DllImport 特性指定 API 所在的 DLL
    [DllImport("DLL名称", 
               CharSet = CharSet.ANSI/Unicode, // 字符集(匹配 API 要求)
               SetLastError = true/false,      // 是否捕获系统错误码
               CallingConvention = CallingConvention.StdCall)] // 调用约定(Win32 几乎都是 StdCall)
    // 方法声明:必须是 static extern,返回值+方法名+参数列表(类型要匹配)
    public static extern 返回值类型 方法名(参数类型1 参数1, 参数类型2 参数2, ...);
}

2. 关键参数说明

特性参数作用
DllNameWin32 API 所在的系统 DLL 名称(如 kernel32.dll、user32.dll)
CharSet字符编码:CharSet.Ansi(ANSI)、CharSet.Unicode(UTF-16)、CharSet.Auto(自动)
SetLastError设为 true 时,可通过 Marshal.GetLastWin32Error() 获取系统错误码
CallingConvention调用约定:Win32 API 默认为 StdCall(C# 默认为 Winapi,等价于 StdCall)

3. 常见类型映射(Win32 → C#)

Win32 API 的原生类型和 C# 类型必须严格映射,否则会导致调用失败甚至程序崩溃:

Win32 类型
C# 等效类型说明
DWORDuint32 位无符号整数
HANDLEIntPtr句柄(指针类型,用 IntPtr 兼容 32/64 位)
LPCSTRstringANSI 字符串(常量指针)
LPWSTRstringUnicode 字符串(可变指针)
BOOLbool/intWin32 的 BOOL 是 int(0 / 非 0),C# 可用 bool 兼容
int/long
int/long直接映射
VOIDvoid无返回值

三、控制台实战案例(多个场景)

环境准备

案例 1:获取系统目录(简单无参数 / 返回值)

需求:调用 kernel32.dll 中的 GetSystemDirectory 函数,获取 Windows 系统目录(如 C:\Windows\System32)。

步骤 1:查看 Win32 API 原生签名

// Win32 原生声明(C/C++)
UINT GetSystemDirectoryA(
  LPSTR lpBuffer,  // 接收目录的缓冲区
  UINT uSize       // 缓冲区大小
);

步骤 2:C# 声明并调用

using System;
using System.Runtime.InteropServices; // 核心命名空间

namespace Win32ApiDemo
{
    class Program
    {
        // 1. 声明 Win32 API(使用 Unicode 版本 GetSystemDirectoryW)
        [DllImport("kernel32.dll",           // API 所在 DLL
                   CharSet = CharSet.Unicode, // 匹配 W 后缀的 Unicode 版本
                   SetLastError = true)]      // 启用错误码捕获
        // static extern 是固定写法,返回值 uint 对应 Win32 的 UINT
        private static extern uint GetSystemDirectoryW(
            char[] lpBuffer,  // 字符数组作为缓冲区(替代 C 的 char*)
            uint uSize        // 缓冲区大小
        );

        static void Main(string[] args)
        {
            try
            {
                // 2. 准备缓冲区(系统目录最长不超过 260 字符,预留冗余)
                char[] buffer = new char[256];
                // 3. 调用 Win32 API
                uint result = GetSystemDirectoryW(buffer, (uint)buffer.Length);

                // 4. 处理结果
                if (result == 0)
                {
                    // 调用失败,获取错误码
                    int errorCode = Marshal.GetLastWin32Error();
                    Console.WriteLine($"调用失败,错误码:{errorCode}");
                }
                else
                {
                    // 将字符数组转为字符串(去掉空字符)
                    string systemDir = new string(buffer).TrimEnd('\0');
                    Console.WriteLine($"Windows 系统目录:{systemDir}");
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"异常:{ex.Message}");
            }

            Console.ReadKey();
        }
    }
}

运行结果

Windows 系统目录:C:\Windows\System32

案例 2:弹出系统消息框(调用 user32.dll)

需求:调用 user32.dll 中的 MessageBox 函数,弹出 Windows 原生消息框(控制台程序也能调用 GUI 相关 API)。

using System;
using System.Runtime.InteropServices;

namespace Win32ApiDemo
{
    class Program
    {
        // 1. 声明 MessageBoxW(Unicode 版本)
        [DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
        private static extern int MessageBoxW(
            IntPtr hWnd,          // 父窗口句柄(控制台无窗口,传 IntPtr.Zero)
            string lpText,        // 消息内容
            string lpCaption,     // 标题
            uint uType            // 消息框类型(按钮+图标)
        );

        // 定义消息框类型常量(对应 Win32 的宏)
        private const uint MB_OK = 0x00000000;          // 仅 OK 按钮
        private const uint MB_ICONINFORMATION = 0x00000040; // 信息图标
        private const uint MB_OKCANCEL = 0x00000001;     // OK + 取消按钮

        static void Main(string[] args)
        {
            try
            {
                // 2. 调用 MessageBoxW
                int ret = MessageBoxW(
                    IntPtr.Zero,                  // 无父窗口
                    "这是 C# 调用 Win32 API 弹出的消息框!", // 消息内容
                    "Win32 API 演示",              // 标题
                    MB_OK | MB_ICONINFORMATION    // 组合类型:OK 按钮 + 信息图标
                );

                // 3. 处理返回值(用户点击的按钮)
                switch (ret)
                {
                    case 1:
                        Console.WriteLine("用户点击了【确定】按钮");
                        break;
                    case 2:
                        Console.WriteLine("用户点击了【取消】按钮");
                        break;
                    default:
                        Console.WriteLine($"返回值:{ret}(调用失败)");
                        break;
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"异常:{ex.Message}");
            }

            Console.ReadKey();
        }
    }
}

关键说明

案例 3:获取进程 ID(调用 GetCurrentProcessId)

需求:调用 kernel32.dll 的 GetCurrentProcessId 获取当前控制台程序的进程 ID。

using System;
using System.Runtime.InteropServices;

namespace Win32ApiDemo
{
    class Program
    {
        // 声明 GetCurrentProcessId(无参数,返回 DWORD)
        [DllImport("kernel32.dll", SetLastError = false)] // 此函数不会失败,无需捕获错误码
        private static extern uint GetCurrentProcessId();

        static void Main(string[] args)
        {
            // 调用 API
            uint pid = GetCurrentProcessId();
            Console.WriteLine($"当前控制台程序的进程 ID:{pid}");

            // 验证:可以在任务管理器中查看控制台程序的 PID 是否一致
            Console.WriteLine("按任意键退出...");
            Console.ReadKey();
        }
    }
}

运行结果

当前控制台程序的进程 ID:12345
按任意键退出...

案例 4:读写 INI 文件

需求:调用 kernel32.dll 的 WritePrivateProfileString 和 GetPrivateProfileString 读写 INI 配置文件(Win32 原生 INI 操作)。

using System;
using System.Runtime.InteropServices;

namespace Win32ApiDemo
{
    class Program
    {
        // 1. 声明写 INI 的 API
        [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
        private static extern bool WritePrivateProfileStringW(
            string lpAppName,  // 节名(INI 的 [Section])
            string lpKeyName,  // 键名
            string lpString,   // 键值
            string lpFileName  // INI 文件路径
        );

        // 2. 声明读 INI 的 API
        [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
        private static extern uint GetPrivateProfileStringW(
            string lpAppName,  // 节名
            string lpKeyName,  // 键名
            string lpDefault,  // 默认值(读取失败时返回)
            char[] lpReturnedString, // 接收值的缓冲区
            uint nSize,        // 缓冲区大小
            string lpFileName  // INI 文件路径
        );

        static void Main(string[] args)
        {
            string iniPath = $"{Environment.CurrentDirectory}\\demo.ini";

            try
            {
                // 步骤 1:写入 INI 文件
                bool writeSuccess = WritePrivateProfileStringW(
                    "UserInfo",    // 节名
                    "UserName",    // 键名
                    "张三",        // 键值
                    iniPath        // 文件路径
                );

                if (writeSuccess)
                {
                    Console.WriteLine($"成功写入 INI 文件:{iniPath}");
                }
                else
                {
                    int errorCode = Marshal.GetLastWin32Error();
                    Console.WriteLine($"写入失败,错误码:{errorCode}");
                }

                // 步骤 2:读取 INI 文件
                char[] buffer = new char[1024];
                uint readLen = GetPrivateProfileStringW(
                    "UserInfo",    // 节名
                    "UserName",    // 键名
                    "默认值",      // 默认值
                    buffer,        // 缓冲区
                    (uint)buffer.Length, // 缓冲区大小
                    iniPath        // 文件路径
                );

                if (readLen > 0)
                {
                    string value = new string(buffer).TrimEnd('\0');
                    Console.WriteLine($"读取到的值:{value}");
                }
                else
                {
                    Console.WriteLine("读取失败或键不存在");
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"异常:{ex.Message}");
            }

            Console.ReadKey();
        }
    }
}

运行结果

成功写入 INI 文件:D:\Win32ApiDemo\bin\Debug\net8.0\demo.ini
读取到的值:张三

生成的 INI 文件内容

[UserInfo]
UserName=张三

四、常见问题与避坑指南

1. 调用失败的常见原因

2. 如何调试 Win32 API 调用?

到此这篇关于C# 调用 Win32 API的实现示例的文章就介绍到这了,更多相关C# 调用 Win32 API内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家! 

您可能感兴趣的文章:
阅读全文