如何正确检索电池序列号?
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,“参数不正确。”
这没有多大意义,因为如果我简单地将InformationLevel
从BatterySerialNumber
更改为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_INFORMATION
的 InformationLevel
成员的值 BatterySerialNumber
似乎不受支持,但这并未直接记录在 Windows SDK 文档中。据记载,无效条目应为 GetLastError()
返回错误 1 (ERROR_INVALID_FUNCTION
),但在这种情况下返回 87(对于无效参数)。我认为这是因为枚举中的值无效,所以它使参数无效,但我不确定。
感谢所有人的帮助,尤其是 @RRUZ 的努力超越!
(顺便说一句,似乎可以从电池的唯一 ID 中提取序列号(使用 BatteryUniqueID
作为 InformationLevel
成员)并从唯一 ID 中删除制造商名称和设备名称。这太可怕了hack,但它是 Windows XP 的一种半可行的解决方法。)
【讨论】:
以上是关于如何正确检索电池序列号?的主要内容,如果未能解决你的问题,请参考以下文章