如何正确检索电池序列号?

Posted

技术标签:

【中文标题】如何正确检索电池序列号?【英文标题】:How to correctly retrieve battery serial number? 【发布时间】:2012-05-30 23:37:34 【问题描述】:

在 Delphi 7 中,我正在开发一个库,该库实现一个对象,该对象封装有关连接到系统的电池的信息。它运行良好,除了检索电池的序列号。

我用于此调用的代码如下:

function TBattery.GetSerialNumber(hbat: THandle): boolean;
var
  bqi:          TBatteryQueryInformation;
  Serial:       PWideChar;
  SerialSize,
  dwOut:        DWORD;
begin
  Result := False;

  if hbat <> INVALID_HANDLE_VALUE then
  begin
    ZeroMemory(@bqi, SizeOf(bqi));
    dwOut := 0;

    bqi.BatteryTag := FBatteryTag;
    bqi.InformationLevel := BatterySerialNumber;

    SerialSize := 2048;
    GetMem(Serial, SerialSize);
    try
      ZeroMemory(Serial, SerialSize);

      Result := DeviceIoControl(hbat, IOCTL_BATTERY_QUERY_INFORMATION, @bqi,
                                SizeOf(bqi), Serial, SerialSize, @dwOut, nil);

      if Result then
        FSerialNumber := Serial;
    finally
      FreeMem(Serial, SerialSize);
    end;
  end;
end;

不幸的是,DeviceIoControl() 总是返回False,如果我之后检查GetLastError(),则会返回错误 87,“参数不正确。”

这没有多大意义,因为如果我简单地将InformationLevelBatterySerialNumber 更改为BatteryUniqueID,代码就可以完美运行。另外,我在GetSerialNumber之前的代码中的其他调用中使用了电池手柄(hbat),它们都工作正常,我也可以在这个失败后调用其他人,所以这不是问题。

有什么想法吗?我真的很茫然。

【问题讨论】:

您系统中的其他程序是否能够检索序列号?它可能归结为一个狡猾的 DSDT 实现,这就是我认为在 _BIF 部分中读取序列号的地方。 为什么将 dwOut 变量作为@dwOut 传递?尝试使用此代码而不是 Result := DeviceIoControl(hbat, IOCTL_BATTERY_QUERY_INFORMATION, @bqi, SizeOf(bqi), Serial, SerialSize, dwOut, nil); @RRUZ 是正确的(它是一个 var 参数),并且仔细检查 BatterySerialNumber = 8 我将变量作为@dwOut 传递的原因是因为我使用的库(来自Project Jedi 的Win32 API 的旧版本)将DeviceIoControl() 定义为function DeviceIoControl(hDevice: HANDLE; dwIoControlCode: DWORD; lpInBuffer: LPVOID; nInBufferSize: DWORD; lpOutBuffer: LPVOID; nOutBufferSize: DWORD; lpBytesReturned: LPDWORD; lpOverlapped: LPOVERLAPPED): BOOL; stdcall; @Restless,尝试使用Windows单元的DeviceIoControl功能。 【参考方案1】:

问题似乎与作为@dwOut 传递的dwOut 变量有关,该变量代表DeviceIoControl 的var lpBytesReturned 参数,定义为

function DeviceIoControl(hDevice: THandle; dwIoControlCode: DWORD; lpInBuffer: Pointer;
  nInBufferSize: DWORD; lpOutBuffer: Pointer; nOutBufferSize: DWORD;
  var lpBytesReturned: DWORD; lpOverlapped: POverlapped): BOOL; stdcall;

所以用

替换你的代码
  Result := DeviceIoControl(hbat, IOCTL_BATTERY_QUERY_INFORMATION, @bqi,
                            SizeOf(bqi), Serial, SerialSize, dwOut, nil);

必须解决问题。

WinAPI

还可以检查此 msdn 条目 Enumerating Battery Devices 中转换为 delphi 的代码,这可以帮助您检测代码的任何其他问题。

uses
  SetupApi,
  Windows,
  SysUtils;

type

  BATTERY_QUERY_INFORMATION_LEVEL = (
    BatteryInformation,
    BatteryGranularityInformation,
    BatteryTemperature,
    BatteryEstimatedTime,
    BatteryDeviceName,
    BatteryManufactureDate,
    BatteryManufactureName,
    BatteryUniqueID,
    BatterySerialNumber);
  TBatteryQueryInformationLevel = BATTERY_QUERY_INFORMATION_LEVEL;

  _BATTERY_QUERY_INFORMATION = record
    BatteryTag: ULONG;
    InformationLevel: BATTERY_QUERY_INFORMATION_LEVEL;
    AtRate: Longint;
  end;
  BATTERY_QUERY_INFORMATION = _BATTERY_QUERY_INFORMATION;
  PBATTERY_QUERY_INFORMATION = ^BATTERY_QUERY_INFORMATION;
  TBatteryQueryInformation = BATTERY_QUERY_INFORMATION;


const
  GUID_DEVCLASS_BATTERY:TGUID='72631E54-78A4-11D0-BCF7-00AA00B7B32A';
  //DEFINE_GUID( GUID_DEVCLASS_BATTERY, 0x72631E54, 0x78A4, 0x11D0, 0xBC, 0xF7, 0x00, 0xAA, 0x00, 0xB7, 0xB3, 0x2A );
  METHOD_BUFFERED     = 0;
  FILE_DEVICE_BATTERY = $00000029;
  FILE_READ_ACCESS    = $0001;    // for files and pipes

  IOCTL_BATTERY_QUERY_TAG =
    (FILE_DEVICE_BATTERY shl 16) or (FILE_READ_ACCESS shl 14) or ($10 shl 2) or (METHOD_BUFFERED);
  IOCTL_BATTERY_QUERY_INFORMATION =
    (FILE_DEVICE_BATTERY shl 16) or (FILE_READ_ACCESS shl 14) or ($11 shl 2) or (METHOD_BUFFERED);

function GetBatteryInfo(InformationLevel : BATTERY_QUERY_INFORMATION_LEVEL) : string;
var
   cbRequired : DWORD;
   hdev     : HDEVINFO;
   idev     : Integer;
   did      : TSPDeviceInterfaceData;
   pdidd    : PSPDeviceInterfaceDetailData;
   hBattery : THandle;
   bqi      : TBatteryQueryInformation;
   dwWait, dwOut : DWORD;
   lpOutBuffer: PWideChar;
begin
  // enumerate the batteries
  hdev :=  SetupDiGetClassDevs(@GUID_DEVCLASS_BATTERY, nil, 0,  DIGCF_PRESENT OR DIGCF_DEVICEINTERFACE);
  if ( INVALID_HANDLE_VALUE <>  THandle(hdev) ) then
  begin
      idev:=0;//first battery
      ZeroMemory(@did, SizeOf(did));
      did.cbSize := SizeOf(did);
      if (SetupDiEnumDeviceInterfaces(hdev, nil, GUID_DEVCLASS_BATTERY, idev, did)) then
      begin
        try
          cbRequired := 0;
          SetupDiGetDeviceInterfaceDetail(hdev, @did, nil, 0, cbRequired, nil);
         if (ERROR_INSUFFICIENT_BUFFER= GetLastError()) then
         begin
            pdidd:=AllocMem(cbRequired);
            try
              pdidd.cbSize := SizeOf(TSPDeviceInterfaceDetailData);
              if (SetupDiGetDeviceInterfaceDetail(hdev, @did, pdidd, cbRequired, cbRequired, nil)) then
              begin
                 hBattery :=CreateFile(pdidd.DevicePath, GENERIC_READ OR GENERIC_WRITE, FILE_SHARE_READ OR FILE_SHARE_WRITE, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
                 if (INVALID_HANDLE_VALUE <> hBattery) then
                 begin
                  try
                    ZeroMemory(@bqi, SizeOf(bqi));
                     // With the tag, you can query the battery info.
                    dwWait := 0;
                      if (DeviceIoControl(hBattery, IOCTL_BATTERY_QUERY_TAG,  @dwWait, sizeof(dwWait), @bqi.BatteryTag, sizeof(bqi.BatteryTag), dwOut, nil)) then
                      begin
                        lpOutBuffer:=AllocMem(MAX_PATH);
                        try
                          ZeroMemory(lpOutBuffer,MAX_PATH);
                          bqi.InformationLevel:=InformationLevel;
                          if DeviceIoControl(hBattery, IOCTL_BATTERY_QUERY_INFORMATION, @bqi, SizeOf(BATTERY_QUERY_INFORMATION), lpOutBuffer, 255, dwOut,nil) then
                            Result:= WideCharToString(lpOutBuffer);
                        finally
                          FreeMem(lpOutBuffer);
                        end;
                      end;
                  finally
                    CloseHandle(hBattery)
                  end;
                 end;
              end;
            finally
              FreeMem(pdidd);
            end;
         end;
        finally
          SetupDiDestroyDeviceInfoList(hdev);
        end;
      end;
  end;
end;

begin
  try
    if not LoadsetupAPI then exit;
     Writeln(GetBatteryInfo(BatterySerialNumber));
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
  readln;
end.

WMI

最后顺便提一下,您可以使用 WMI 来检索相同的信息,在本例中使用 BatteryStaticData WMI 类

    $APPTYPE CONSOLE

    uses
      SysUtils,
      ActiveX,
      ComObj,
      Variants;

    // Battery Static Data

    procedure  GetBatteryStaticDataInfo;
    const
      WbemUser            ='';
      WbemPassword        ='';
      WbemComputer        ='localhost';
      wbemFlagForwardOnly = $00000020;
    var
      FSWbemLocator : OLEVariant;
      FWMIService   : OLEVariant;
      FWbemObjectSet: OLEVariant;
      FWbemObject   : OLEVariant;
      oEnum         : IEnumvariant;
      iValue        : LongWord;
    begin;
      FSWbemLocator := CreateOleObject('WbemScripting.SWbemLocator');
      FWMIService   := FSWbemLocator.ConnectServer(WbemComputer, 'root\WMI', WbemUser, WbemPassword);
      FWbemObjectSet:= FWMIService.ExecQuery('SELECT SerialNumber FROM BatteryStaticData','WQL',wbemFlagForwardOnly);
      oEnum         := IUnknown(FWbemObjectSet._NewEnum) as IEnumVariant;
      while oEnum.Next(1, FWbemObject, iValue) = 0 do
      begin
        Writeln(Format('SerialNumber    %s',[String(FWbemObject.SerialNumber)]));// String

        Writeln('');
        FWbemObject:=Unassigned;
      end;
    end;


    begin
     try
        CoInitialize(nil);
        try
          GetBatteryStaticDataInfo;
        finally
          CoUninitialize;
        end;
     except
        on E:EOleException do
            Writeln(Format('EOleException %s %x', [E.Message,E.ErrorCode])); 
        on E:Exception do
            Writeln(E.Classname, ':', E.Message);
     end;
     Writeln('Press Enter to exit');
     Readln;      
    end.

【讨论】:

我接受了建议并弄乱了我的代码,我仍然得到同样的错误。我还将您基于 API 的代码(因为我的测试框在该命名空间中没有可用的 WMI 类)直接放入一个新项目中,编译并运行它......同样的错误。我在我的盒子中搜索了不同的 SetupApi.pas 实现,依次尝试了它们……在两个测试盒(一个是台式机,另一个是 Panasonic Toughbook)上出现相同的错误。你是在哪个 Delphi 版本上构建的?你从哪里得到SetupApi.pas? (我使用了 Project Jedi 的三个版本;Delphi 7 没有附带 SetupApi.pas。) WinApi 代码在 delphi 7 中使用 jvcl\run 文件夹中包含的 JVCL 中的 SetupApi 单元进行了测试。 我担心你会这么说;这也是我用的。我只是通过将 SetupApi 使用子句明确设置到该文件来加倍确定,并确保其他包含的库没有从原始日期更改。仍然没有爱,这很奇怪,因为正如我所提到的,所有的 IOCTL_BATTERY_QUERY_INFORMATION 调用都很好......除了 this 一个。【参考方案2】:

总之,@RRUZ 和我发布的代码在 Windows 7 以及其他第三方应用程序下运行良好。它们不适用于在 Windows XP 下检索序列号。我还在 WinXP 和 7 下进行了测试,在完全相同的硬件上安装了操作系统,结果相同(在 Windows 7 下成功,而不是在 Windows XP 下)。

在 WinXP 下,IOCTL_BATTERY_QUERY_INFORMATIONInformationLevel 成员的值 BatterySerialNumber 似乎不受支持,但这并未直接记录在 Windows SDK 文档中。据记载,无效条目应为 GetLastError() 返回错误 1 ​​(ERROR_INVALID_FUNCTION),但在这种情况下返回 87(对于无效参数)。我认为这是因为枚举中的值无效,所以它使参数无效,但我不确定。

感谢所有人的帮助,尤其是 @RRUZ 的努力超越!

(顺便说一句,似乎可以从电池的唯一 ID 中提取序列号(使用 BatteryUniqueID 作为 InformationLevel 成员)并从唯一 ID 中删除制造商名称和设备名称。这太可怕了hack,但它是 Windows XP 的一种半可行的解决方法。)

【讨论】:

以上是关于如何正确检索电池序列号?的主要内容,如果未能解决你的问题,请参考以下文章

如何检索已连接的蓝牙设备的序列号?

如何检索 oracle 序列的当前值而不增加它?

亚马逊电子书B013090760260089是啥型号

从内部连接表中检索最新记录

关于如何存储和检索时间序列数据的建议

如何检索时间序列数据的最小值和最大值