How to get the battery serial number?

In Delphi 7, I work on a library that implements an object that encapsulates information about batteries attached to the system. It works well, except for getting the serial number for the battery.

The code I use for this call is as follows:

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; 

Unfortunately, DeviceIoControl() always returns False , and if I check GetLastError() after that, it returns with error 87, "the parameter is incorrect."

That doesn't make much sense because the code works just fine if I just change the InformationLevel from BatterySerialNumber to BatteryUniqueID , say. Also, I used the battery descriptor ( hbat ) in other code calls before GetSerialNumber , and they all work fine, and I can call others after this failure also failed, so this is not a problem.

Any ideas? I am really at a loss.

+7
source share
2 answers

The problem is with the dwOut variable, which is passed as @dwOut, this variable represents the var lpBytesReturned DeviceIoControl , which is defined as

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

So, replacing your code with

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

Need to fix the problem.

Winapi

Also check out this code translated into delphi from this msdn Enumerating Battery Devices entry, which will help you detect any additional problems with your code.

 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

Finally, as a note, you can use WMI to get the same information, in this case using the BatteryStaticData WMI class

  {$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. 
+12
source

Thus, @RRUZ code and I published the work under Windows 7, as well as other third-party applications. They do not work for serial numbers in Windows XP. I also tested under WinXP and 7 with basic OS settings on the same hardware with the same results (success under Windows 7, not under Windows XP).

It appears that in WinXP, the BatterySerialNumber element for IOCTL_BATTERY_QUERY_INFORMATION InformationLevel not supported, but this is not documented directly in the Windows SDK docs. It is documented that invalid entries should return error 1 ( ERROR_INVALID_FUNCTION ) for GetLastError() , but in this case, 87 is returned instead (for an invalid parameter). I believe this is because this value is not allowed in the enumeration, so it makes the parameter invalid, but I'm not quite sure.

Thanks to everyone for their help, especially @RRUZ for going higher and higher!

(Aside, it seems you can extract the serial number from the battery Unique identifier (using BatteryUniqueID as a member of InformationLevel ) and remove the manufacturer name and device name from the unique identifier. Hack, but this is a semi-profitable workaround for Windows XP.)

0
source

All Articles