VB.NET中GetFirmwareEnvironmentVariable函数调用异常排查
解决GetFirmwareEnvironmentVariable读取乱码及空数组问题
核心问题分析
你遇到的乱码、空数组问题本质是两个原因:
- P/Invoke定义不规范:错误的字符集、缓冲区处理方式导致字符串类变量解析异常;
- UEFI变量数据类型误解:并非所有变量都是字符串类型,像
SecureBoot是单字节布尔值、TimeOut是4字节无符号整数,直接按字符串读取必然出现乱码或空数组。
修正后的P/Invoke实现及辅助方法
以下是适配UEFI变量类型的VB.NET代码,包含不同类型变量的读取逻辑:
Imports System.Runtime.InteropServices Public Class UefiVariableHelper ' 全局UEFI变量通用GUID:{8BE4DF61-93CA-11D2-AA0D-00E098032B8C} Public Shared ReadOnly EFI_GLOBAL_VARIABLE_GUID As New Guid("8BE4DF61-93CA-11D2-AA0D-00E098032B8C") ' 标准P/Invoke定义:使用Unicode字符集,正确处理缓冲区大小 <DllImport("kernel32.dll", SetLastError:=True, CharSet:=CharSet.Unicode)> Public Shared Function GetFirmwareEnvironmentVariable( ByVal lpName As String, ByVal lpGuid As String, ByVal lpBuffer As IntPtr, ByRef nSize As UInteger ) As UInteger End Function ' 读取UTF-16LE字符串类型的UEFI变量(如PlatformLang、PlatformLangCodes) Public Shared Function ReadStringVariable(variableName As String, targetGuid As Guid) As String Dim guidStr As String = targetGuid.ToString("B").ToUpper() Dim requiredSize As UInteger = 0 ' 先获取变量所需的缓冲区大小 GetFirmwareEnvironmentVariable(variableName, guidStr, IntPtr.Zero, requiredSize) If requiredSize = 0 Then Return Nothing Dim buffer As IntPtr = Marshal.AllocHGlobal(CInt(requiredSize)) Try Dim readBytes As UInteger = GetFirmwareEnvironmentVariable(variableName, guidStr, buffer, requiredSize) If readBytes = 0 Then Return Nothing ' UEFI字符串以UTF-16LE编码,直接转换为.NET字符串 Return Marshal.PtrToStringUni(buffer, CInt((requiredSize - 2) / 2)) ' 去除末尾的空终止符 Finally Marshal.FreeHGlobal(buffer) End Try End Function ' 读取布尔类型的UEFI变量(如SecureBoot,1字节UINT8) Public Shared Function ReadBooleanVariable(variableName As String, targetGuid As Guid) As Boolean? Dim guidStr As String = targetGuid.ToString("B").ToUpper() Const bufferSize As UInteger = 1 ' UINT8占用1字节 Dim buffer As IntPtr = Marshal.AllocHGlobal(CInt(bufferSize)) Try Dim readBytes As UInteger = GetFirmwareEnvironmentVariable(variableName, guidStr, buffer, bufferSize) If readBytes = 0 Then Return Nothing Return Marshal.ReadByte(buffer) <> 0 Finally Marshal.FreeHGlobal(buffer) End Try End Function ' 读取无符号32位整数类型的UEFI变量(如TimeOut,4字节UINT32) Public Shared Function ReadUInt32Variable(variableName As String, targetGuid As Guid) As UInteger? Dim guidStr As String = targetGuid.ToString("B").ToUpper() Const bufferSize As UInteger = 4 ' UINT32占用4字节 Dim buffer As IntPtr = Marshal.AllocHGlobal(CInt(bufferSize)) Try Dim readBytes As UInteger = GetFirmwareEnvironmentVariable(variableName, guidStr, buffer, bufferSize) If readBytes = 0 Then Return Nothing Return Marshal.ReadUInt32(buffer) Finally Marshal.FreeHGlobal(buffer) End Try End Function End Class
使用示例
' 读取字符串类型变量 Dim platformLang = UefiVariableHelper.ReadStringVariable("PlatformLang", UefiVariableHelper.EFI_GLOBAL_VARIABLE_GUID) Console.WriteLine($"PlatformLang: {platformLang}") ' 读取SecureBoot状态 Dim secureBootStatus = UefiVariableHelper.ReadBooleanVariable("SecureBoot", UefiVariableHelper.EFI_GLOBAL_VARIABLE_GUID) Console.WriteLine($"SecureBoot: {If(secureBootStatus.HasValue, secureBootStatus.Value, "读取失败")}") ' 读取启动超时时间 Dim bootTimeout = UefiVariableHelper.ReadUInt32Variable("TimeOut", UefiVariableHelper.EFI_GLOBAL_VARIABLE_GUID) Console.WriteLine($"启动超时: {If(bootTimeout.HasValue, $"{bootTimeout.Value}秒", "读取失败")}")
关键注意事项
- GUID正确性:不同UEFI变量属于不同的GUID命名空间,大部分全局变量使用
EFI_GLOBAL_VARIABLE_GUID,启动项相关变量需使用对应专属GUID; - 二进制变量处理:像
ErrOut这类二进制数据变量,需要根据UEFI规范定义对应的结构体进行解析,不能直接按字符串读取; - 权限要求:读取UEFI变量需要管理员权限,否则函数会返回0并设置错误码
ERROR_ACCESS_DENIED。
内容的提问来源于stack exchange,提问作者ElektroStudios
相关产品推荐
相关产品推荐

