Как правильно узнать серийный номер аккумулятора?

В 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, «параметр неверен».

В этом нет особого смысла, потому что код работает отлично, если, скажем, просто изменить InformationLevel с BatterySerialNumber на BatteryUniqueID. Кроме того, я использовал дескриптор батареи (hbat) в других вызовах кода до GetSerialNumber, и все они работают нормально, и я могу позвонить другим после того, как этот выйдет из строя, так что проблема не в этом.

Любые идеи? Я действительно в растерянности.


person Restless    schedule 30.05.2012    source источник
comment
Могут ли какие-либо другие программы в вашей системе получить серийный номер? Это могло быть связано с изворотливой реализацией DSDT, откуда считывается серийный номер, в разделе _BIF, я думаю.   -  person James    schedule 31.05.2012
comment
почему вы передаете переменную dwOut как @dwOut? попробуйте использовать этот код вместо Result := DeviceIoControl(hbat, IOCTL_BATTERY_QUERY_INFORMATION, @bqi, SizeOf(bqi), Serial, SerialSize, dwOut, nil);   -  person RRUZ    schedule 31.05.2012
comment
@RRUZ прав (это параметр var), а также дважды проверьте, что BatterySerialNumber = 8   -  person Francesca    schedule 31.05.2012
comment
Причина, по которой я передал переменную как @dwOut, заключается в том, что используемая мной библиотека (более старый перевод Win32 API из Project Jedi) определила DeviceIoControl() как function DeviceIoControl(hDevice: HANDLE; dwIoControlCode: DWORD; lpInBuffer: LPVOID; nInBufferSize: DWORD; lpOutBuffer: LPVOID; nOutBufferSize: DWORD; lpBytesReturned: LPDWORD; lpOverlapped: LPOVERLAPPED): BOOL; stdcall;.   -  person Restless    schedule 31.05.2012
comment
@Restless, попробуйте использовать функцию DeviceIoControl модуля Windows.   -  person RRUZ    schedule 31.05.2012
comment
@RRUZ: Я поставил Windows в конце предложений по использованию, чтобы на нее ссылались, и даже изменил вызов на Windows.DeviceIoControl(...). То же самое. Если бы я этого не видел, я бы этому не поверил. Я начинаю думать, что было бы проще просто очистить возвращаемое значение BatteryUniqueID, чтобы получить серийный номер, но это не решение, это пластырь.   -  person Restless    schedule 31.05.2012


Ответы (2)


Проблема, похоже, связана с переменной dwOut, которая передается как @dwOut, эта переменная представляет параметр var lpBytesReturned _ 3_, который определяется как

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

Также проверьте этот код, переведенный в delphi из этой записи msdn _ 6_, который может помочь вам обнаружить любые дополнительные проблемы с вашим кодом.

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.
person RRUZ    schedule 31.05.2012
comment
Я принял предложения и испортил свой код, но все равно получаю ту же ошибку. Я также взял ваш код на основе API (поскольку в моем тестовом блоке этот класс WMI не доступен в этом пространстве имен) прямо в новый проект, скомпилировал его и запустил ... та же ошибка. Я искал в своем ящике различные реализации SetupApi.pas, пробовал их по очереди ... та же ошибка на обоих тестовых боксах (один настольный, другой Panasonic Toughbook). На какой версии Delphi вы это построили? Где вы взяли SetupApi.pas? (Я использовал три версии из Project Jedi; Delphi 7 не поставлялся с SetupApi.pas.) - person Restless; 31.05.2012
comment
Код WinApi был протестирован в delphi 7 с использованием модуля SetupApi из JVCL, включенного в папку jvcl\run. - person RRUZ; 31.05.2012
comment
Я боялся, что ты так скажешь; это то, что я тоже использовал. Я просто дважды удостоверился, что явно установил для этого файла предложение SetupApi uses, а также убедился, что другие включенные библиотеки не были изменены по сравнению с исходными датами. По-прежнему нет любви, что странно, поскольку, как я уже упоминал, все вызовы IOCTL_BATTERY_QUERY_INFORMATION отлично работают ... кроме этого. - person Restless; 31.05.2012

Таким образом, код @RRUZ и я опубликовали отлично работают под Windows 7, а также с другими сторонними приложениями. Они не работают для получения серийного номера в Windows XP. Я также тестировал под WinXP и 7 с базовыми установками ОС на одном и том же оборудовании с идентичными результатами (успех под Windows 7, а не под Windows XP).

Похоже, что в WinXP значение BatterySerialNumber для члена IOCTL_BATTERY_QUERY_INFORMATION InformationLevel не поддерживается, но это не задокументировано непосредственно в документации Windows SDK. Документировано, что недопустимые записи должны возвращать ошибку 1 (ERROR_INVALID_FUNCTION) для GetLastError(), но в этом случае вместо этого возвращается 87 (для недопустимого параметра). Я утверждаю, что это связано с тем, что это значение в перечислении недействительно, поэтому оно делает параметр недействительным, но я не совсем уверен.

Спасибо всем за помощь, особенно @RRUZ за то, что сделали все возможное!

(Кстати, похоже, что можно извлечь серийный номер из уникального идентификатора батареи (используя BatteryUniqueID в качестве члена InformationLevel) и удалить имя производителя и имя устройства из уникального идентификатора. Это ужасный взлом, но это полу- жизнеспособное решение для Windows XP.)

person Restless    schedule 31.05.2012