Ajuster la fréquence de votre processeur avec l'API Windows Power Management - Page 2

Début de l'article

La fonction nous répond que non... et du coup nous ne pouvons obtenir les seuils d'ajustement mini et maxi que cette fonction devrait normalement nous fournir. Fort heureusement, lorsqu'il s'agit de lire la cadence du processeur ou l'état de la batterie, les choses se passent beaucoup mieux.  (source CppWatchProcessor):

#include using namespace std; #include #include typedef struct _PROCESSOR_POWER_INFORMATION { ULONG Number; ULONG MaxMhz; ULONG CurrentMhz; ULONG MhzLimit; ULONG MaxIdleState; ULONG CurrentIdleState; } PROCESSOR_POWER_INFORMATION, *PPROCESSOR_POWER_INFORMATION; int main(int argc, char* argv[]) { SYSTEM_POWER_POLICY spp; PROCESSOR_POWER_INFORMATION *ppi; SYSTEM_BATTERY_STATE sbs; SYSTEM_INFO si; while(true) { CallNtPowerInformation(SystemPowerPolicyAc, NULL, 0, &spp, sizeof(SYSTEM_POWER_POLICY)); cout << "Current throttle: " << (int)spp.DynamicThrottle << endl; cout << "Forced throttle: " << (int)spp.ForcedThrottle << endl; cout << "Min throttle: " << (int)spp.MinThrottle << endl; GetSystemInfo(&si); ppi = new PROCESSOR_POWER_INFORMATION[si.dwNumberOfProcessors]; CallNtPowerInformation(ProcessorInformation, NULL, 0, ppi, sizeof(PROCESSOR_POWER_INFORMATION)*si.dwNumberOfProcessors); for(DWORD i=0; i < si.dwNumberOfProcessors;i++) { cout << "Processeur " << i << endl; cout << "Frequence maxi: " << ppi[i].MaxMhz << endl; cout << "Frequence actuelle: " << ppi[i].CurrentMhz << endl; } delete[] ppi; cout << endl; CallNtPowerInformation(SystemBatteryState, NULL, 0, &sbs, sizeof(SYSTEM_BATTERY_STATE)); if(sbs.AcOnLine) cout << "Alimentation secteur" << endl; if(sbs.BatteryPresent) cout << "Une batterie est presente sur le systeme" << endl; if(sbs.Charging) cout << "Batterie en charge" << endl; if(sbs.Discharging) { cout << "Batterie en decharge" << endl; cout << "Autonomie estimee: " << sbs.EstimatedTime/60 << " minutes" << endl; } cout << endl; Sleep(3000); } return 0; }

Dans ce code, on notera la présence de la définition de la structure PROCESSOR_POWER_INFORMATION. Normalement celle-ci devrait se situer dans les fichiers en-tête du SDK. Mais la documentation Microsoft nous avertit que cette structure a été oubliée, d'où l'ajout dans le code. Bien sûr le jour où Microsoft aura corrigé ses fichiers en-tête, le code ne compilera plus :-) Le lecteur qui a lu l'article "Observez la charge de votre processeur multi-coeurs sous Windows" cité plus haut, sait que, dans le cas d'une machine équipée d'un processeur multi-cœurs, la WMI ne voit qu'un processeur et des cœurs, ce qui est logique. Avec CallNtPowerInformation, les choses fonctionnent à l'inverse. C'est pourquoi notre code questionne la fonction Win32 GetSystemInfo pour connaitre la le nombre de "processeurs". On utilise ensuite ce résultat pour allouer un tableau de PROCESSOR_POWER_INFORMATION. Le reste du code est trivial. Comme on le voit dans l'exemple ci-dessous CallNtPowerInformation peut être utilisée pour connaitre l'état de la batterie. Cependant, si l'on veut s'amuser à écrire du code aussi "geek" que possible, on notera l'alternative qui consiste à interroger le driver de la batterie avec la fonction DeviceIoControl. Le lecteur trouvera dans l'archive qui accompagne cet article l'exemple DemoBatterieIoControl qui peut servir de point de départ à l'écriture d'un code adapté aux besoins. Cet exemple est directement tiré du SDK Windows et n'est pas reproduit ici. Enfin, en ce qui concerne l'alimentation du PC, on pourrait encore interroger la fonction GetSystemPowerStatus. Voir source DemoGetSystemPowerStatus.

 

Les plans de consommation énergétique

 Depuis Windows Vista les paramètres relatifs à la consommation d'énergie du PC sont regroupés dans un ensemble de données appelé plan (ou Schemes dans la documentation). Par défaut, Windows propose 3 plans:

Un mode (ou plan) pour de bonnes performances, un mode pour de bonnes économies, et un mode équilibré. Il est possible d'ajouter de nouveaux plans, éventuellement de les supprimer ultérieurement depuis l'interface du panneau. Et bien sûr, cela est également possible par programmation, via l'API Power Management. Ce panneau ne laisse pas entrevoir toute la richesse des réglages, même lorsque l'on clique sur les options avancées d'un plan. Ainsi l'illustration ci-dessous ne montre que 3 paramètres relatifs au processeur. Nous allons bientôt découvrir qu'il en existe beaucoup plus.

Les plans de gestions d'énergie ont une structure hiérarchique. Au sommet, le plan lui-même. Ce plan contient des sous-groupes de données gérant chacun un composant de l'ordinateur. Il existe par exemple, dans un plan, un sous-groupe pour le processeur, un sous-groupe pour l'écran, un sous-groupe pour les disques, un sous-groupe pour la batterie, etc. Chaque sous-groupe contient un ensemble de valeurs de réglages, appelées settings dans la documentation. Les plans, que ce soit ceux par défaut ou qu'il s'agisse de plans ajoutés ultérieurement, sont identifiés par un GUID, ou Globally Unique IDentifier. Les sous-groupes sont à leur tour identifiés par des GUIDs. Enfin les valeurs sont elles marquées par des GUIDs. Il est important de bien garder à l'esprit que si chaque plan est associé à un GUID, par définition unique, les GUIDs des sous-groupes sont les mêmes dans tous plans. Il en va de même pour les settings. Enfin, les GUIDs des plans par défaut sont donnés dans la documentation, ainsi que les GUIDs des sous-groupes. Sauf erreur de votre serviteur, les GUIDs des settings ne sont pas documentés, mais on les obtient facilement en écrivant un peu de code Un GUID est un type de données de 16 octets, ainsi défini (en-tête guiddef.h) :

 

typedef struct _GUID { unsigned long Data1; unsigned short Data2; unsigned short Data3; unsigned char Data4[ 8 ]; } GUID;

 

Mais on le connait plus souvent sous cette forme:

{370F4BE5-D0FE-4128-9854-DE54765376CA

Une macro, DEFINE_GUID, déclarée dans l'en-tête initguid.h permet de convertir cette forme en la structure donnée plus haut.

 

Enumérer les plans

Voici un programme (DemoPowerEnumeratesur) qui énumère tous les plans d'énergie présents sur une machine. Ainsi qu'on le voit sur l'illustration ci-contre , on retrouve les GUIDs par défaut donnés par la documentation.

En outre il est possible d'obtenir les chaines de textes descriptives associés. Ce moyen est à priori le seul pour savoir à quoi servent les settings non documentés. 

#include using namespace std; #include #include #define BIGSIZE 1024 DWORD bigsize = BIGSIZE; void ConvertString(WCHAR* src, CHAR* dest) { BOOL UsedDefault; WideCharToMultiByte(CP_OEMCP, WC_NO_BEST_FIT_CHARS, src, -1, dest, BIGSIZE, " ", &UsedDefault); } void PrintGuid(GUID* guid) { cout << hex; cout << guid->Data1 << "-" << guid->Data2 << "-" << guid->Data3; for(int i=0;i<8;i++) cout << (int)guid->Data4[i]; cout << endl; } void PrintPowerScheme(GUID* guid) { DWORD result; UCHAR buffer[BIGSIZE]; CHAR chBuffer[BIGSIZE]; result = PowerReadDescription(NULL, guid, NULL, NULL, buffer, &bigsize); if(!result) { ConvertString((WCHAR*)buffer, chBuffer); cout << chBuffer << endl; } bigsize = BIGSIZE; result = PowerReadFriendlyName(NULL, guid, NULL, NULL, buffer, &bigsize); if(!result) { ConvertString((WCHAR*)buffer, chBuffer); cout << chBuffer << endl; } bigsize = BIGSIZE; } int main(int argc, char* argv[]) { UCHAR* buffer; DWORD buffersize; ULONG index; DWORD result; index = 0; result = 0; while(1) { result = PowerEnumerate(NULL, NULL, //Scheme GUID NULL, // SubGroup GUID ACCESS_SCHEME, index, NULL, // demander la taille du buffer &buffersize); if(result != 0 && result != ERROR_MORE_DATA) break; buffer = (UCHAR *)LocalAlloc(LPTR, buffersize); result = PowerEnumerate(NULL, NULL, //Scheme GUID NULL, // SubGroup GUID ACCESS_SCHEME, index, buffer, &buffersize); if(result) { LocalFree(buffer); break; } PrintGuid((GUID*)buffer); PrintPowerScheme((GUID*)buffer); cout << endl; LocalFree(buffer); index++; } return 0; }

 

La plupart des fonctions "Power" doivent être invoquées deux fois consécutivement. Une première fois pour obtenir la taille du buffer qui sera rempli par le second appel. La première partie du programme (dans main) alloue la mémoire à chaque fois. Ensuite par souci de simplicité, de concision et aussi par paresse, nous utilisons de gros tampons déclarés sur la pile. 

 

Créer un nouveau plan

Pour expérimenter de nouveaux réglages, il est pertinent de créer un nouveau plan. Faire cette opération entièrement et à partir de zéro n'est pas envisageable, nous ne connaissons même pas tous les settings existants. La technique consiste donc à dupliquer un des plans par défaut au moyen de la fonction PowerDuplicateScheme. Cette fonction génère ou reçoit le GUID du plan à créer. Le plus pratique est d'utiliser l'outil guidgen.exe ou uuidgen.exe de la SDK Windows pour obtenir un GUID humainement lisible, puis de l'utiliser comme illustré ci-dessous. Le code est extrait de DemoPowerDuplicateScheme, disponible dans l'archive qui accompagne cet article.

#include // pour le DEFINE_GUID ci-dessous // {370F4BE5-D0FE-4128-9854-DE54765376CA} DEFINE_GUID(PROGRAMMEZGUID, 0x370f4be5, 0xd0fe, 0x4128, 0x98, 0x54, 0xde, 0x54, 0x76, 0x53, 0x76, 0xca); // etc. GUID leguid = PROGRAMMEZGUID; GUID sous_guid = NO_SUBGROUP_GUID; GUID* active; GUID* target = &leguid; cout << "Plan actif" << endl; PowerGetActiveScheme(NULL, &active); PrintGuid(active); // voir programme complet cout << endl; result = PowerDuplicateScheme(NULL, active, &target); if(result == ERROR_ALREADY_EXISTS) cout << "Le plan existe deja" << endl; if(!result) { WCHAR texte[] = L"Un bon plan Programmez! : Abonnez vous ! :-)"; PowerWriteDescription(NULL, target, NULL, NULL, (UCHAR*)texte, sizeof(texte)+2); cout << "succes duplication" << endl; } cout << endl;

Suite de l'article