为何使用Ruby Fiddle调用Win32 API时结构体被垃圾数据填充?
解决Ruby Fiddle调用Win32 API获取文件版本信息的结构体问题
我之前也折腾过类似的Win32 API调用,尤其是结构体这块,Fiddle对内存布局和类型匹配的要求真的很严格,你遇到的垃圾数据问题大概率是这里出了岔子,咱们一步步来捋清楚:
1. 先补全你提到的C代码逻辑
先把你没写完的C代码补全,方便对照着转Ruby:
DWORD dwHandle, dwLen; LPVOID lpData; UINT BufLen; VS_FIXEDFILEINFO *pFileInfo; LPTSTR LibName = L"C:\\windows\\notepad.exe"; // 获取版本信息所需内存大小 dwLen = GetFileVersionInfoSize(LibName, &dwHandle); if (dwLen == 0) { /* 错误处理 */ } // 分配内存存储版本数据 lpData = malloc(dwLen); if (!lpData) { /* 错误处理 */ } // 读取文件版本信息到内存 if (!GetFileVersionInfo(LibName, dwHandle, dwLen, lpData)) { free(lpData); /* 错误处理 */ } // 提取VS_FIXEDFILEINFO结构体指针 if (!VerQueryValue(lpData, L"\\", (LPVOID*)&pFileInfo, &BufLen)) { free(lpData); /* 错误处理 */ } // 读取版本信息示例 DWORD fileVerMS = pFileInfo->dwFileVersionMS; DWORD fileVerLS = pFileInfo->dwFileVersionLS; free(lpData);
2. Ruby Fiddle核心问题:结构体定义与指针传递
你遇到的垃圾数据,90%是结构体布局不匹配或者VerQueryValue的指针传递错误。先严格定义VS_FIXEDFILEINFO结构体:
require 'fiddle' require 'fiddle/import' module Win32VersionAPI extend Fiddle::Importer dlload 'version.dll' # 用宽字符版本的API(对应C里的L""字符串) extern 'DWORD GetFileVersionInfoSizeW(LPCWSTR, LPDWORD)' extern 'BOOL GetFileVersionInfoW(LPCWSTR, DWORD, DWORD, LPVOID)' extern 'BOOL VerQueryValueW(LPCVOID, LPCWSTR, LPVOID*, PUINT)' # 严格对应C的VS_FIXEDFILEINFO结构体成员顺序和类型 class VS_FIXEDFILEINFO < Fiddle::Struct layout( :dwSignature, :dword, :dwStrucVersion, :dword, :dwFileVersionMS, :dword, :dwFileVersionLS, :dword, :dwProductVersionMS, :dword, :dwProductVersionLS, :dword, :dwFileFlagsMask, :dword, :dwFileFlags, :dword, :dwFileOS, :dword, :dwFileType, :dword, :dwFileSubtype, :dword, :dwFileDateMS, :dword, :dwFileDateLS, :dword ) end end
3. 完整的Ruby调用实现
按照C代码的逻辑一步步来,重点处理内存分配和指针传递:
target_path = "C:\\windows\\notepad.exe" dw_handle = Fiddle::Pointer.malloc(Fiddle::SIZEOF_DWORD) # 第一步:获取版本信息所需内存大小 dw_len = Win32VersionAPI.GetFileVersionInfoSizeW(target_path, dw_handle) if dw_len == 0 puts "获取版本大小失败,错误码:#{Fiddle.win32_last_error}" exit 1 end # 第二步:分配内存存储版本数据 lp_data = Fiddle::Pointer.malloc(dw_len) success = Win32VersionAPI.GetFileVersionInfoW(target_path, dw_handle.to_int, dw_len, lp_data) unless success puts "读取版本信息失败,错误码:#{Fiddle.win32_last_error}" lp_data.free exit 1 end # 第三步:提取VS_FIXEDFILEINFO结构体指针 # 注意:VerQueryValue需要传递指针的指针,所以先分配存储指针的内存 p_file_info_ptr = Fiddle::Pointer.malloc(Fiddle::SIZEOF_VOIDP) buf_len = Fiddle::Pointer.malloc(Fiddle::SIZEOF_UINT) success = Win32VersionAPI.VerQueryValueW(lp_data, "\\", p_file_info_ptr, buf_len) unless success puts "提取结构体失败,错误码:#{Fiddle.win32_last_error}" lp_data.free exit 1 end # 将指针转换为结构体实例 file_info = Win32VersionAPI::VS_FIXEDFILEINFO.new(p_file_info_ptr.to_int) # 解析版本号(高16位是主版本,低16位是次版本) file_major = (file_info[:dwFileVersionMS] >> 16) & 0xFFFF file_minor = file_info[:dwFileVersionMS] & 0xFFFF file_build = (file_info[:dwFileVersionLS] >> 16) & 0xFFFF file_revision = file_info[:dwFileVersionLS] & 0xFFFF puts "Notepad.exe版本:#{file_major}.#{file_minor}.#{file_build}.#{file_revision}" # 手动释放内存,避免泄漏 lp_data.free p_file_info_ptr.free buf_len.free dw_handle.free
4. 避坑提醒
- 必须用宽字符API:你的C代码用了
L""宽字符串,Ruby里要对应调用W后缀的API,否则会出现字符串编码不匹配的问题。 - 结构体布局丝毫不差:
VS_FIXEDFILEINFO的成员顺序、类型必须和C定义完全一致,差一个成员或者类型错了都会读出垃圾数据。 - 指针传递要准确:
VerQueryValue需要的是指针的指针,所以得先分配一块存指针的内存,不能直接传结构体指针的地址。
内容的提问来源于stack exchange,提问作者cjones26
相关产品推荐
相关产品推荐

