C# 调用 Win32 API 实现系统时间修改与磁盘序列号获取
在 C# 开发中,有时需要直接与 Windows 底层系统进行交互,例如同步服务器时间与客户端时间,或者获取硬盘的唯一标识以用于软件授权验证。本文将展示如何通过 DllImport 调用 Kernel32.dll 中的关键函数,分别实现修改本地系统时间和获取磁盘卷序列号。
核心原理:P/Invoke 调用机制
C# 作为 .NET 语言,默认通过托管代码运行。要访问非托管的 Windows API(如修改系统时间的 SetLocalTime 和查询文件系统信息的 GetVolumeInformation),必须使用平台调用服务(Platform Invoke)。这需要在类中使用 [DllImport] 特性声明外部 DLL 路径,并定义对应的函数签名和数据结构。
准备工作:定义数据结构
在调用 API 之前,首先需要定义 Windows API 所需的数据结构。修改时间需要 SYSTEMTIME 结构,而获取卷信息需要特定的整型变量来接收返回值。
- 定义 SYSTEMTIME 结构:该结构包含年、月、日、时、分、秒等字段,对应 Windows API 的时间格式。必须确保字段类型与原生 C 结构体一致,使用
ushort表示无符号短整型。 - 引入命名空间:需要引用
System.Runtime.InteropServices以支持DllImport和StructLayout特性。
using System;
using System.Runtime.InteropServices;
namespace Fengyun
{
public class Win32
{
// 定义 SYSTEMTIME 结构,布局紧凑以匹配原生结构体
[StructLayout(LayoutKind.Sequential)]
private struct SYSTEMTIME
{
public ushort wYear;
public ushort wMonth;
public ushort wDayOfWeek;
public ushort wDay;
public ushort wHour;
public ushort wMinute;
public ushort wSecond;
public ushort wMilliseconds;
}
}
}
第一步:修改本地系统时间
此步骤的目的是将程序指定的 DateTime 对象转换为 Windows 本地时间并应用。操作涉及声明 SetLocalTime 函数并封装调用逻辑。
第1步:声明 SetLocalTime API
- 目的:告诉运行时去哪里找到修改时间的函数。
- 动作:使用
DllImport("Kernel32.dll")声明SetLocalTime,参数为引用类型的SYSTEMTIME结构。
[DllImport("Kernel32.dll")] private static extern uint SetLocalTime(ref SYSTEMTIME lpSystemTime);
第2步:封装设置方法
- 入口:输入一个
DateTime类型的目标时间。 - 动作:实例化
SYSTEMTIME结构,将输入时间的各个部分赋值给结构体字段,然后调用SetLocalTime。 - 注意:修改系统时间通常需要管理员权限。
/// <summary>
/// 将本地时间与指定时间同步
/// </summary>
public static void SetTime(DateTime targetTime)
{
SYSTEMTIME st = new SYSTEMTIME();
st.wYear = Convert.ToUInt16(targetTime.Year);
st.wMonth = Convert.ToUInt16(targetTime.Month);
st.wDay = Convert.ToUInt16(targetTime.Day);
st.wHour = Convert.ToUInt16(targetTime.Hour);
st.wMinute = Convert.ToUInt16(targetTime.Minute);
st.wSecond = Convert.ToUInt16(targetTime.Second);
st.wMilliseconds = Convert.ToUInt16(targetTime.Millisecond);
// 执行修改
uint result = SetLocalTime(ref st);
if (result == 0)
{
throw new Exception("设置系统时间失败,请检查权限。");
}
}
第二步:获取硬盘序列号
获取硬盘序列号通常指的是获取文件系统的卷序列号(Volume Serial Number)。虽然这与物理硬盘的物理序列号不同,但在软件注册场景中常被用作设备指纹。我们使用 GetVolumeInformation API。
第1步:声明 GetVolumeInformation API
- 目的:获取指定路径的文件系统信息。
- 参数:包括根路径、卷名称缓冲区、最大组件长度、卷序列号引用、文件系统标志、文件系统名称缓冲区和缓冲区大小。
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
private static extern int GetVolumeInformation(
string lpRootPathName,
string lpVolumeNameBuffer,
int nVolumeNameSize,
ref int lpVolumeSerialNumber,
int lpMaximumComponentLength,
int lpFileSystemFlags,
string lpFileSystemNameBuffer,
int nFileSystemNameSize
);
第2步:封装获取方法
- 入口:传入盘符字符串,如 "C"。
- 动作:构造根路径
C:\,初始化缓冲区大小和输出变量,调用 API 并将返回的序列号转换为十六进制字符串。
/// <summary>
/// 获取指定盘符的卷序列号
/// </summary>
public static string GetDiskVolume(string drvID)
{
const int MAX_FILENAME_LEN = 256;
int serialNumber = 0;
string volumeName = new string(' ', MAX_FILENAME_LEN);
string fileSystemName = new string(' ', MAX_FILENAME_LEN);
int result = GetVolumeInformation(
drvID + @":\",
volumeName,
MAX_FILENAME_LEN,
ref serialNumber,
0,
0,
fileSystemName,
MAX_FILENAME_LEN
);
if (result != 0)
{
// 返回十六进制格式,常用于唯一性识别
return serialNumber.ToString("X8");
}
throw new Exception($"无法获取 {drvID} 盘的序列号。");
}
完成判断与安全提示
当 SetLocalTime 返回非零值,或 GetVolumeInformation 成功填充序列号变量时,表明操作成功。
风险提醒:修改系统时间会直接影响日志记录、证书验证及软件计时功能,建议在调试或非生产环境中谨慎使用,并在操作前后进行备份或通知用户。获取卷序列号仅依赖于文件系统,更换分区格式可能改变序列号,但不随硬件更换而改变(除非格式化)。
简要总结
本文通过 C# 示例代码展示了如何使用 Win32 API 进行系统级操作。通过定义 SYSTEMTIME 结构并调用 SetLocalTime,可实现时间同步;通过 GetVolumeInformation 可获取磁盘卷信息。这些技术点在实际软件开发中,特别是在需要绑定硬件特征或同步环境的场景中非常实用。以上就是 C# 利用 Win32 API 修改本地系统时间、获取硬盘序列号的详细内容,更多关于 C# 系统编程的资料请关注本站其它相关文章!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













