通过P/Invoke向NVML传递含固定数组的C#结构体返回异常值
P/Invoke调用NVML库结构体数据损坏问题解决
问题描述
通过P/Invoke向NVML库函数传递结构体时出现数据损坏:结构体包含固定大小数组与unsigned long long类型数据,代码编译无错误,但返回的isRunning字段(本应仅为0或1)出现30、3等异常值,gpuUtilization值也存在异常。
NVML文档中结构体声明
NVML的nvmlAccountingStats_t结构体文档声明如下:
unsigned int gpuUtilization unsigned int isRunning // 进程运行状态标记:1表示运行,0表示终止 unsigned long long maxMemoryUsage unsigned int memoryUtilization unsigned int reserved[5] // 预留字段,供未来使用 unsigned long long startTime // 进程启动时间(微秒级CPU时间戳) unsigned long long time
初始C#实现代码
以下是最小可复现的C#控制台应用代码:
// 初始C#版本的结构体定义 public struct nvmlAccountingStats_t { public uint gpuUtilization; public uint isRunning; public ulong maxMemoryUsage; public uint memoryUtilization; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)] public uint[] reserved; public ulong startTime; public ulong time; } public enum nvmlReturn_t { NVML_SUCCESS = 0, // 操作成功 NVML_ERROR_UNINITIALIZED = 1, // 未调用nvmlInit()初始化NVML NVML_ERROR_INVALID_ARGUMENT = 2, // 参数无效 NVML_ERROR_NOT_SUPPORTED = 3, // 目标设备不支持该操作 NVML_ERROR_NO_PERMISSION = 4, // 当前用户无操作权限 NVML_ERROR_ALREADY_INITIALIZED = 5, // 已初始化(已废弃:现在允许多次初始化并引用计数) NVML_ERROR_NOT_FOUND = 6, // 查询对象失败 NVML_ERROR_INSUFFICIENT_SIZE = 7, // 输入参数大小不足 NVML_ERROR_INSUFFICIENT_POWER = 8, // 设备外部电源线未正确连接 NVML_ERROR_DRIVER_NOT_LOADED = 9, // NVIDIA驱动未加载 NVML_ERROR_TIMEOUT = 10, // 用户提供的超时时间已过 NVML_ERROR_IRQ_ISSUE = 11, // NVIDIA内核检测到GPU中断问题 NVML_ERROR_LIBRARY_NOT_FOUND = 12, // 无法找到或加载NVML共享库 NVML_ERROR_FUNCTION_NOT_FOUND = 13, // 本地NVML版本未实现该函数 NVML_ERROR_CORRUPTED_INFOROM = 14, // infoROM损坏 NVML_ERROR_GPU_IS_LOST = 15, // GPU已脱离总线或无法访问 NVML_ERROR_RESET_REQUIRED = 16, // GPU需要重置才能再次使用 NVML_ERROR_OPERATING_SYSTEM = 17, // GPU控制设备被操作系统/cgroups阻止 NVML_ERROR_LIB_RM_VERSION_MISMATCH = 18, // RM检测到驱动/库版本不匹配 NVML_ERROR_IN_USE = 19, // GPU正忙,无法执行操作 NVML_ERROR_MEMORY = 20, // 内存不足 NVML_ERROR_NO_DATA = 21, // 无数据 NVML_ERROR_VGPU_ECC_NOT_SUPPORTED = 22, // 目标设备不支持该vgpu操作(因ECC已启用) NVML_ERROR_INSUFFICIENT_RESOURCES = 23, // 关键资源耗尽(非内存) NVML_ERROR_FREQ_NOT_SUPPORTED = 24, // 不支持该频率 NVML_ERROR_UNKNOWN = 999 // 内部驱动错误 } [DllImport(NVMLDLL)] private static extern nvmlReturn_t nvmlInit_v2(); [DllImport(NVMLDLL)] private static extern nvmlReturn_t nvmlShutdown(); [DllImport(NVMLDLL)] private static extern nvmlReturn_t nvmlDeviceGetCount_v2(out uint deviceCount); [DllImport(NVMLDLL)] private static extern nvmlReturn_t nvmlDeviceGetHandleByIndex_v2(int index, out IntPtr handle); [DllImport(NVMLDLL)] private static extern nvmlReturn_t nvmlDeviceGetAccountingPids(IntPtr device, ref uint count, uint[]? pids); [DllImport(NVMLDLL)] private static extern nvmlReturn_t nvmlDeviceGetAccountingStats(IntPtr device, uint pid, out nvmlAccountingStats_t stats); static void Main(string[] args) { if (nvmlInit_v2() == nvmlReturn_t.NVML_SUCCESS) { nvmlDeviceGetCount_v2(out uint deviceCount); for (int i = 0; i < deviceCount; i++) { nvmlDeviceGetHandleByIndex_v2(i, out IntPtr _device); // 获取进程PID列表 uint count = 0; nvmlDeviceGetAccountingPids(_device, ref count, null); uint[] pids = new uint[count]; if (nvmlDeviceGetAccountingPids(_device, ref count, pids) == nvmlReturn_t.NVML_SUCCESS) { foreach (uint pid in pids) { nvmlDeviceGetAccountingStats(_device, pid, out nvmlAccountingStats_t stats); Console.WriteLine($"pid: {pid}, isRunning: {stats.isRunning}"); } } } nvmlShutdown(); } }
异常输出结果
运行后isRunning字段出现异常值,示例输出如下:
pid: 6560, isRunning: 2 pid: 31636, isRunning: 0 pid: 30000, isRunning: 0 pid: 4164, isRunning: 0 pid: 3368, isRunning: 0 pid: 30420, isRunning: 0 pid: 732, isRunning: 0 pid: 14576, isRunning: 0 pid: 27352, isRunning: 0 pid: 25632, isRunning: 0 pid: 7352, isRunning: 0 pid: 32960, isRunning: 0 pid: 25380, isRunning: 30 pid: 21376, isRunning: 0 pid: 21396, isRunning: 0 pid: 7488, isRunning: 0 pid: 39200, isRunning: 0 pid: 15536, isRunning: 3
修正后的C#结构体
调整字段顺序并添加[StructLayout(LayoutKind.Sequential)]属性后,数据恢复正常(isRunning仅返回0或1):
[StructLayout(LayoutKind.Sequential)] public struct nvmlAccountingStats_t { public uint gpuUtilization; public uint memoryUtilization; public ulong maxMemoryUsage; public ulong time; public ulong startTime; public uint isRunning; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)] public uint[] reserved; }
总结
- 问题根源:C#结构体字段顺序与NVML库实际实现的C++结构体顺序不匹配,导致内存偏移错误,数据解析损坏。
- 解决方案:确保C#结构体的字段顺序、类型与库的实际头文件定义完全一致,同时使用
[StructLayout(LayoutKind.Sequential)]强制按顺序布局结构体。 - 重要提示:不要仅依赖官方文档的结构体字段顺序,优先查看库的实际实现头文件,保证跨语言结构体布局完全对齐。
内容的提问来源于stack exchange,提问作者codingNewb
相关产品推荐
相关产品推荐

