深入探讨P/Invoke与跨平台编程实践
发布时间: 2025-08-17 02:15:13 阅读量: 1 订阅数: 3 

# 深入探讨P/Invoke与跨平台编程实践
## 1. 了解P/Invoke
在Windows 8应用开发中,有时会遇到想要使用Win32函数的情况。例如,有一个函数`GetNativeSystemInfo`,其在文档中的定义如下:
```plaintext
void WINAPI GetNativeSystemInfo(__out LPSYSTEM_INFO lpSystemInfo);
```
对于不熟悉Win32 API的人来说,可能很难理解这个定义。其中,像`WINAPI`这样的标识符通常是通过C的`#define`和`typedef`指令在Windows的各种头文件中定义的,这些头文件一般位于`C:/Program Files (x86)/Windows Kits/8.0`目录下,重要的头文件有`Windows.h`、`WinDef.h`、`WinBase.h`和`winnt.h`。`WINAPI`等价于`__stdcall`,这是从C代码调用Win32函数的标准调用约定。`LPSYSTEM_INFO`是指向`SYSTEM_INFO`结构的“长”指针。
`SYSTEM_INFO`结构的定义如下:
```c
typedef struct _SYSTEM_INFO {
union {
DWORD dwOemId;
struct {
WORD wProcessorArchitecture;
WORD wReserved;
};
};
DWORD dwPageSize;
LPVOID lpMinimumApplicationAddress;
LPVOID lpMaximumApplicationAddress;
DWORD_PTR dwActiveProcessorMask;
DWORD dwNumberOfProcessors;
DWORD dwProcessorType;
DWORD dwAllocationGranularity;
WORD wProcessorLevel;
WORD wProcessorRevision;
} SYSTEM_INFO;
```
这里字段名前的后缀是简单匈牙利命名法对数据类型的缩写,不过现在在应用编程中这种命名法已很少见。在Windows术语中,`WORD`表示16位无符号值,对应C#中的`ushort`;`DWORD`是双字,即32位无符号值(`uint`);要注意`long`,这里指的是C++中的`long`,其大小与`int`相同(32位);`LPVOID`表示“指向void的长指针”(C中为`void *`),`DWORD_PTR`是32或64位无符号数,取决于Windows运行的处理器,在C#中对应`IntPtr`。
### 1.1 在C#中重新定义结构
为了在C#程序中使用`SYSTEM_INFO`结构,需要在C#中重新定义它。由于文档中提到`dwOemId`字段已过时,所以可以忽略`union`关键字,直接创建一个带有公共字段的C#结构:
```csharp
[StructLayout(LayoutKind.Sequential)]
struct SystemInfo
{
public ushort wProcessorArchitecture;
public ushort wReserved;
public uint dwPageSize;
public IntPtr lpMinimumApplicationAddress;
public IntPtr lpMaximumApplicationAddress;
public IntPtr dwActiveProcessorMask;
public uint dwNumberOfProcessors;
public uint dwProcessorType;
public uint dwAllocationGranularity;
public ushort wProcessorLevel;
public ushort wProcessorRevision;
}
```
在C#中,如果想在结构外部访问字段,字段必须定义为`public`。也可以给字段赋予新的名称,或者使用相同大小的其他数据类型,但要确保实际值不会导致有符号类型溢出。这个结构在32位Windows中占用36字节,在64位Windows中占用48字节。
`[StructLayout(LayoutKind.Sequential)]`属性明确表示字段应被视为相邻的,并按字节边界对齐。
### 1.2 声明外部函数
定义好结构后,需要声明`GetNativeSystemInfo`函数。可以使用`DllImportAttribute`,该属性也在`System.Runtime.InteropServices`命名空间中。至少要指定包含该函数的DLL文件,文档中表明`GetNativeSystemInfo`函数定义在`kernel32.dll`中,函数声明如下:
```csharp
[DllImport("kernel32.dll")]
static extern void GetNativeSystemInfo(out SystemInfo systemInfo);
```
这个声明应该放在C#类的定义中,与其他方法处于同一级别。函数必须用`static`关键字声明,这在普通C#类中很常见,同时使用`extern`关键字表示函数的实际实现位于类外部。如果希望函数在类外部可见,还需用`public`关键字声明。
### 1.3 调用函数
在类的其他方法中,可以定义`SystemInfo`类型的值并调用该函数,就像调用普通静态方法一样:
```csharp
SystemInfo systemInfo;
GetNativeSystemInfo(out systemInfo);
```
### 1.4 完整应用示例
以下是一个完整的应用示例,展示了如何使用上述结构和函数:
```csharp
// Project: SystemInfoPInvoke | File: MainPage.xaml.cs
using System;
using System.Runtime.InteropServices;
using Windows.UI.Xaml.Controls;
namespace SystemInfoPInvoke
{
public sealed partial class MainPage : Page
{
[StructLayout(LayoutKind.Sequential)]
struct SystemInfo
{
public ushort wProcessorArchitecture;
public byte wReserved;
public uint dwPageSize;
public IntPtr lpMinimumApplicationAddress;
public IntPtr lpMaximumApplicationAddress;
public IntPtr dwActiveProcessorMask;
public uint dwNumberOfProcessors;
public uint dwProcessorType;
public uint dwAllocationGranularity;
public ushort wProcessorLevel;
public ushort wProcessorRevision;
}
[DllImport("kernel32.dll")]
static extern void GetNativeSystemInfo(out SystemInfo systemInfo);
enum ProcessorType
{
x86 = 0,
ARM = 5,
ia64 = 6,
x64 = 9,
Unknown = 65535
};
public MainPage()
{
this.InitializeComponent();
SystemInfo systemInfo = new SystemInfo();
GetNativeSystemInfo(out systemInfo);
processorArchitecture.Text =
((ProcessorType)systemInfo.wProcessorArchitecture).ToString();
pageSize.Text = systemInfo.dwPageSize.ToString();
minAppAddr.Text =
((ulong)systemInfo.lpMinimumApplicationAddress).ToString("X");
maxAppAddr.Text =
((ulong)systemInfo.lpMaximumApplicationAddress).ToString("X");
activeProcessorMask.Text =
((ulong)systemInfo.dwActiveProcessorMask).ToString("X");
numberProcessors.Text = systemInfo.dwNumberOfProcessors.ToString("X");
allocationGranularity.Text =
systemInfo.dwAllocationGranularity.ToString();
processorLevel.Text = systemInfo.wProcessorLevel.ToString();
processorRevision.Text = systemInfo.wProcessorRevision.ToString("X");
}
}
}
```
在这个示例中,定义了`ProcessorType`枚举来简化`wProcessorArchitecture`值的格式化。`IntPtr`字段被转换为`ulong`并以十六进制格式显示。
## 2. 时区信息处理
假设要编写一个Windows Store应用,用于显示世界不同城市的时钟,类似于`ClockRack`程序。在Windows 8应用中,`TimeZoneInfo.GetSystemTimeZones`方法不可用,但可以使用Win32函数来获取大部分所需信息。
### 2.1 相关Win32结构
Win32的`EnumDynamicTimeZoneInformation`函数可以遍历世界上所有时区,其使用的结构如下:
```c
typedef struct _TIME_DYNAMIC_ZONE_INFORMATION {
LONG Bias;
WCHAR StandardName[32];
SYSTEMTIME StandardDate;
LONG StandardBias;
WCHAR DaylightName[32];
SYSTEMTIME DaylightDate;
LONG DaylightBias;
WCHAR TimeZoneKeyName[128];
BOOLEAN DynamicDaylightTimeDisabled;
} DYNAMIC_TIME_ZONE_INFORMATION, *PDYNAMIC_TIME_ZONE_INFORMATION;
```
这是`TIME_ZONE_INFORMATION`结构的扩展版本:
```c
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;
```
`WCHAR`表示16位Unicode字符,这些字符数组通常表示以空字符结尾的字符串。`Bias`字段包含从UTC时间减去的分钟数,以获得当地时间;`StandardBias`通常为0,`DaylightBias`包含从标准时间减去的分钟数,用于转换为夏令时。`DaylightDate`和`StandardDate`字段定义了何时切换到夏令时和切换回标准时间,信息以`SYSTEMTIME`格式存储:
```c
typedef struct _SYSTEMTIME {
WORD wYear;
WORD wMonth;
WORD wDayOfWeek;
WORD wDay;
WORD wHour;
WORD wMinute;
WORD wSecond;
WORD wMilliseconds;
} SYSTEMTIME, *PSYSTEMTIME;
```
### 2.2 在C#中定义结构和声明函数
在C#中,需要重新定义这些结构并声明相关函数:
```csharp
// Project: ClockRack | File: TimeZoneManager.cs (fragment)
public partial class TimeZoneManager
{
[StructLayout(LayoutKind.Sequential)]
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;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
struct TIME_ZONE_INFORMATION
{
public int Bias;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string StandardName;
public SYSTEMTIME StandardDate;
public int StandardBias;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string DaylightName;
public SYSTEMTIME DaylightDate;
public int DaylightBias;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
struct DYNAMIC_TIME_ZONE_INFORMATION
{
public int Bias;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string StandardName;
public SYSTEMTIME StandardDate;
public int StandardBias;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string DaylightName;
public SYSTEMTIME DaylightDate;
public int DaylightBias;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
public string TimeZoneKeyName;
public byte DynamicDaylightTimeDisabled;
}
[DllImport("Advapi32.dll")]
static extern uint EnumDynamicTimeZoneInformation(uint index,
ref DYNAMIC_TIME_ZONE_INFORMATION dynamicTzi);
[DllImport("kernel32.dll")]
static extern byte GetTimeZoneInformationForYear(ushort year,
ref DYNAMIC_TIME_ZONE_INFORMATION dtzi,
out TIME_ZONE_INFORMATION tzi);
[DllImport("kernel32.dll")]
static extern byte SystemTimeToTzSpecificLocalTime(
ref TIME_ZONE_INFORMATION tzi,
ref SYSTEMTIME utc, out SYSTEMTIME local);
...
}
```
注意,对于`DYNAMIC_TIME_ZONE_INFORMATION`和`TIME_ZONE_INFORMATION`结构中的`WCHAR`数组字段,使用`MarshalAs`属性将其解释为具有特定最大长度的C#字符串。
### 2.3 时区信息处理逻辑
`TimeZoneManager`类的构造函数会多次调用`EnumDynamicTimeZoneInformation`函数,直到返回非零值,表示列表结束。每个值都会转换为`TimeZoneDisplayInfo`类型并添加到`DisplayInformation`集合中:
```csharp
// Project: ClockRack | File: TimeZoneManager.cs (fragment)
public partial class TimeZoneManager
{
...
// 内部字典,用于按键选择DYNAMIC_TIME_ZONE_INFORMATION值
Dictionary<string, DYNAMIC_TIME_ZONE_INFORMATION> dynamicTzis =
new Dictionary<string, DYNAMIC_TIME_ZONE_INFORMATION>();
public TimeZoneManager()
{
uint index = 0;
DYNAMIC_TIME_ZONE_INFORMATION tzi = new DYNAMIC_TIME_ZONE_INFORMATION();
List<TimeZoneDisplayInfo> displayInformation =
new List<TimeZoneDisplayInfo>();
// 遍历所有时区
while (0 == EnumDynamicTimeZoneInformation(index, ref tzi))
{
dynamicTzis.Add(tzi.TimeZoneKeyName, tzi);
// 创建TimeZoneDisplayInfo对象
TimeZoneDisplayInfo displayInfo = new TimeZoneDisplayInfo
{
Bias = tzi.Bias,
TimeZoneKey = tzi.TimeZoneKeyName
};
// 查找显示字符串
if (displayStrings.ContainsKey(tzi.TimeZoneKeyName))
{
displayInfo.Display = displayStrings[tzi.TimeZoneKeyName];
}
else if (displayStrings.ContainsKey(tzi.StandardName))
{
displayInfo.Display = displayStrings[tzi.Standard
```
0
0
相关推荐










