This is the second in series of articles about monitoring batteries in Windows XP. In the first article,
Windows XP: Monitoring Batteries in C Sharp, I showed how to use the Windows Management Instrumentation (WMI) to get battery status for multiple batteries using C#. Now what I didn’t tell you was that we found a potential bug in the .NET Framework and the battery driver on the device we were working on. The bug appears to be that the battery driver reports two newline characters in the battery device id, which causes something to go wrong in reading any of the WMI WIN32_Battery properties (we have a case open with Microsoft on this.)
So, to get around the problem, I rewrote my WIN32_Battery class in C++, and included some C wrapper functions so that I could use the class from C#.
Just like with the C# class, I created a WIN32_Battery class:
class WIN32_Battery
{
public:
WIN32_Battery();
~WIN32_Battery();
void Refresh();
int NumberOfBatteries();
int GetPercentChargeRemaining(int BatteryNumber);
private:
int GetBatteryProperty(int BatteryNumber, TCHAR *Property);
BOOL Connect();
IEnumWbemClassObject* Batteries;
IWbemLocator *pLoc;
IWbemServices *pSvc;
int Count;
};
Then started by creating a constructor, destructor and then getting the data:
WIN32_Battery::WIN32_Battery()
{
Count = 0;
Batteries = NULL;
pLoc = NULL;
pSvc = NULL;
Connect();
Refresh();
}
WIN32_Battery::~WIN32_Battery()
{
if( Batteries != NULL )
{
Batteries->Release();
}
if( pLoc != NULL )
pLoc->Release();
if( pSvc != NULL )
pSvc->Release();
CoUninitialize();
}
void WIN32_Battery::Refresh()
{
HRESULT hres;
if( Batteries != NULL )
{
Batteries->Release();
}
hres = pSvc->ExecQuery(
bstr_t("WQL"),
bstr_t("SELECT * FROM Win32_Battery"),
WBEM_FLAG_RETURN_IMMEDIATELY,
NULL,
&Batteries);
if (FAILED(hres))
{
Batteries = NULL;
}
//Count the number of batteries
IEnumWbemClassObject *BatteryEnum;
Batteries->Clone( &BatteryEnum );
this->Count = 0;
IWbemClassObject *pclsObj;
ULONG uReturn = 0;
while (Batteries)
{
HRESULT hr = BatteryEnum->Next(WBEM_INFINITE, 1, &pclsObj, &uReturn);
if(0 == uReturn)
break;
this->Count++;
pclsObj->Release();
}
BatteryEnum->Release();
}
A couple of notes on the code differences from the C# code:
1. The constructor calls a new function, Connect(). More on this later.
2. In C# I didn’t need to count the batteries, but in C++ I needed to enumerate and count the batteries.
The WMI inteface is handled using COM, so we need to connect COM before using it. Note that I am not a COM expert, or even close, so this code may have some problems. I do know that this code is not meant to be called twice by the application, which also means as I am implementing it there should not be multiple instances of my WIN32_Battery class. The Connect() code is:
BOOL WIN32_Battery::Connect()
{
HRESULT hres;
hres = CoInitializeEx(0, COINIT_MULTITHREADED);
// If it failed try becuase the thread model is wrong, try again with a different model
if( hres == RPC_E_CHANGED_MODE)
hres = CoInitializeEx(0, COINIT_APARTMENTTHREADED);
if (FAILED(hres))
{
return 1
}
hres = CoInitializeSecurity(
NULL,
-1, // COM authentication
NULL, // Authentication services
NULL, // Reserved
RPC_C_AUTHN_LEVEL_DEFAULT, // Default authentication
RPC_C_IMP_LEVEL_IMPERSONATE, // Default Impersonation
NULL, // Authentication info
EOAC_NONE, // Additional capabilities
NULL // Reserved
);
if (FAILED(hres))
{
TCHAR ErrorMessage[1000];
wsprintf( ErrorMessage, L"Error in CoinitializeSecturity %X (%d)", hres, hres );
MessageBox(NULL,ErrorMessage,L"Error",0);
CoUninitialize();
return 2;
}
hres = CoCreateInstance(
CLSID_WbemLocator,
0,
CLSCTX_INPROC_SERVER,
IID_IWbemLocator, (LPVOID *) &pLoc);
if (FAILED(hres))
{
CoUninitialize();
return 3;
}
hres = pLoc->ConnectServer(
_bstr_t(L"ROOT\\CIMV2"), // Object path of WMI namespace
NULL, // User name. NULL = current user
NULL, // User password. NULL = current
0, // Locale. NULL indicates current
NULL, // Security flags.
0, // Authority (e.g. Kerberos)
0, // Context object
&pSvc // pointer to IWbemServices proxy
);
if (FAILED(hres))
{
pLoc->Release();
CoUninitialize();
return 4;
}
hres = CoSetProxyBlanket(
pSvc, // Indicates the proxy to set
RPC_C_AUTHN_WINNT, // RPC_C_AUTHN_xxx
RPC_C_AUTHZ_NONE, // RPC_C_AUTHZ_xxx
NULL, // Server principal name
RPC_C_AUTHN_LEVEL_CALL, // RPC_C_AUTHN_LEVEL_xxx
RPC_C_IMP_LEVEL_IMPERSONATE, // RPC_C_IMP_LEVEL_xxx
NULL, // client identity
EOAC_NONE // proxy capabilities
);
if (FAILED(hres))
{
pSvc->Release();
pLoc->Release();
CoUninitialize();
return 5;
}
return 0;
}
Then just like in the C# code, I added functions to get the number of batteries in the system and get the percent of battery charge remaining:
int WIN32_Battery::NumberOfBatteries()
{
return Count;
}
int WIN32_Battery::GetPercentChargeRemaining(int BatteryNumber)
{
int RetVal = 0;
RetVal = GetBatteryProperty(BatteryNumber, L"EstimatedChargeRemaining" );
return RetVal;
}
int WIN32_Battery::GetBatteryProperty(int BatteryNumber, TCHAR *Property)
{
int RetVal = 0;
IWbemClassObject *pclsObj;
IEnumWbemClassObject *BatteryEnum;
Batteries->Clone( &BatteryEnum );
ULONG uReturn = 0;
int BatteryCounter = 0;
while (BatteryEnum)
{
HRESULT hr = BatteryEnum->Next(WBEM_INFINITE, 1,
&pclsObj, &uReturn);
if(0 == uReturn)
{
break;
}
if( BatteryCounter == BatteryNumber )
{
VARIANT vtProp;
hr = pclsObj->Get(Property, 0, &vtProp, 0, 0);
RetVal = vtProp.uintVal;
VariantClear(&vtProp);
pclsObj->Release();
break;
}
pclsObj->Release();
BatteryCounter++;
}
BatteryEnum->Release();
return RetVal;
}
Then so that I could use the WIN32_Battery class as a native DLL from C#, I added some wrapper functions. These map to the constructor, destructor, get number of batteries and get percent. They are:
HANDLE InitWIN32_Battery()
{
WIN32_Battery *Batteries = new WIN32_Battery();
return (HANDLE)Batteries;
}
void DeinitWIN32_Battery( HANDLE Batts )
{
WIN32_Battery *Batteries = (WIN32_Battery *)Batts;
delete( Batteries );
}
void WIN32_Battery_Refresh(HANDLE Batts)
{
WIN32_Battery *Batteries = (WIN32_Battery *)Batts;
Batteries->Refresh();
}
int WIN32_Battery_NumberOfBatteries(HANDLE Batts)
{
WIN32_Battery *Batteries = (WIN32_Battery *)Batts;
return Batteries->NumberOfBatteries();
}
int WIN32_Battery_GetPercentChargeRemaining( HANDLE Batts, int BatteryNumber )
{
WIN32_Battery *Batteries = (WIN32_Battery *)Batts;
return Batteries->GetPercentChargeRemaining(BatteryNumber);
}
The function InitWIN32_Battery() returns a pointer to the class as a HANDLE to the C# app. The C# app passes the HANDLE back in to each of the other functions.
In the next articles, look for a series of applications that use these classes.
Copyright © 2010 – Bruce Eitman
All Rights Reserved