il y avait une recherche d'erreur exe dans la section outil de Visual Studio, ce qui le fait assez bien lorsque vous n'avez besoin que d'un message d'erreur pour le débogage.
ColdCat
@ColdCat: Pour le débogage, il est beaucoup plus facile d'ajouter simplement une @err,hrmontre et de demander au débogueur de convertir automatiquement le dernier code d'erreur en une représentation lisible par l'homme. Le ,hrspécificateur de format fonctionne pour toute expression qui s'évalue à une valeur intégrale, par exemple une 5,hrmontre affichera "ERROR_ACCESS_DENIED: Accès refusé." .
IInspectable
2
Dans la GetLastError()documentation: " Pour obtenir une chaîne d'erreur pour les codes d'erreur système, utilisez la FormatMessage()fonction. ". Consultez l'exemple de récupération du dernier code d'erreur sur MSDN.
Remy Lebeau
Réponses:
145
//Returns the last Win32 error, in string format. Returns an empty string if there is no error.
std::stringGetLastErrorAsString(){//Get the error message, if any.
DWORD errorMessageID =::GetLastError();if(errorMessageID ==0)return std::string();//No error message has been recorded
LPSTR messageBuffer =nullptr;size_t size =FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, errorMessageID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),(LPSTR)&messageBuffer,0, NULL);
std::string message(messageBuffer, size);//Free the buffer.LocalFree(messageBuffer);return message;}
Je pense que vous devez en fait passer (LPSTR)&messageBufferdans ce cas, car sinon, FormatMessageA ne pourrait pas modifier sa valeur pour pointer vers le tampon alloué.
Kylotan
2
Oh, wow, ouais c'est un peu bizarre. Comment modifierait-il le pointeur? Mais en lui passant l'adresse du pointeur (pointeur vers un pointeur), mais en le convertissant en un pointeur normal ... Bizarrerie Win32. Merci pour la tête haute, je l'ai corrigé dans ma propre base de code (et ma réponse). Prise très subtile.
Jamin Gray
1
Merci beaucoup, votre exemple est beaucoup plus clair que celui de MSDN. De plus, celui de MSDN n'a même pas pu compiler. Il comprend un en- strsafe.htête, qui n'est pas du tout sûr , cela provoque un tas d'erreurs de compilation dans winuser.het winbase.h.
Hi-Angel
2
Certains ID d'erreur ne sont pas pris en charge. Par exemple, 0x2EE7, ERROR_INTERNET_NAME_NOT_RESOLVED provoque une nouvelle erreur lors de l'appel de FormatMessage: 0x13D, le système ne peut pas trouver le texte du message pour le numéro de message 0x% 1 dans le fichier de messages pour% 2.
Brent
3
Problèmes avec cette implémentation: 1GetLastErrorest potentiellement appelée trop tard. 2Pas de support Unicode. 3Utilisation d'exceptions sans mettre en œuvre des garanties de sécurité d'exception.
IInspectable
65
Mis à jour (11/2017) pour prendre en considération certains commentaires.
@ Hi-Angel - L'exemple suppose que vous compilez avec UNICODE défini. «FormatMessage» est en fait une macro qui se développe soit en «FormatMessageA» pour les tampons de caractères Ansi / MBCS, soit en «FormatMessageW» pour les tampons UTF16 / UNICODE, selon la manière dont l'application est compilée. J'ai pris la liberté d'éditer l'exemple ci-dessus pour invoquer explicitement la version qui correspond au type de tampon de sortie (wchar_t).
Bukes
2
Ajouter FORMAT_MESSAGE_IGNORE_INSERTS: "Si vous ne contrôlez pas la chaîne de format, vous devez passer FORMAT_MESSAGE_IGNORE_INSERTS pour éviter que% 1 ne cause des problèmes." blogs.msdn.microsoft.com/oldnewthing/20071128-00/?p=24353
Roi Danton
1
Problèmes avec cette implémentation: 1échec de la spécification de l' FORMAT_MESSAGE_IGNORE_INSERTSindicateur important . 2GetLastErrorpotentiellement appelé trop tard. 3Restriction arbitraire du message à 256 unités de code. 4Aucune gestion des erreurs.
IInspectable
1
sizeof (buf) doit être ARRAYSIZE (buf) car FormatMessage attend la taille du tampon en TCHAR, pas en octets.
Kai
1
Ne pouvons-nous pas utiliser 256au lieu de (sizeof(buf) / sizeof(wchar_t)? est-ce acceptable et sûr?
GetLastError renvoie un code d'erreur numérique. Pour obtenir un message d'erreur descriptif (par exemple, à afficher à un utilisateur), vous pouvez appeler FormatMessage :
// This functions fills a caller-defined character buffer (pBuffer)// of max length (cchBufferLength) with the human-readable error message// for a Win32 error code (dwErrorCode).// // Returns TRUE if successful, or FALSE otherwise.// If successful, pBuffer is guaranteed to be NUL-terminated.// On failure, the contents of pBuffer are undefined.
BOOL GetErrorMessage(DWORD dwErrorCode, LPTSTR pBuffer, DWORD cchBufferLength){if(cchBufferLength ==0){return FALSE;}
DWORD cchMsg =FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,/* (not used with FORMAT_MESSAGE_FROM_SYSTEM) */
dwErrorCode,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
pBuffer,
cchBufferLength,
NULL);return(cchMsg >0);}
En C ++, vous pouvez considérablement simplifier l'interface en utilisant la classe std :: string:
#include<Windows.h>#include<system_error>#include<memory>#include<string>typedef std::basic_string<TCHAR>String;StringGetErrorMessage(DWORD dwErrorCode){
LPTSTR psz{nullptr};const DWORD cchMsg =FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM
| FORMAT_MESSAGE_IGNORE_INSERTS
| FORMAT_MESSAGE_ALLOCATE_BUFFER,
NULL,// (not used with FORMAT_MESSAGE_FROM_SYSTEM)
dwErrorCode,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),reinterpret_cast<LPTSTR>(&psz),0,
NULL);if(cchMsg >0){// Assign buffer to smart pointer with custom deleter so that memory gets released// in case String's c'tor throws an exception.auto deleter =[](void* p){::LocalFree(p);};
std::unique_ptr<TCHAR,decltype(deleter)> ptrBuffer(psz, deleter);returnString(ptrBuffer.get(), cchMsg);}else{auto error_code{::GetLastError()};throw std::system_error( error_code, std::system_category(),"Failed to retrieve error message string.");}}
REMARQUE: ces fonctions fonctionnent également pour les valeurs HRESULT. Changez simplement le premier paramètre de DWORD dwErrorCode à HRESULT hResult. Le reste du code peut rester inchangé.
Ces implémentations apportent les améliorations suivantes par rapport aux réponses existantes:
Exemple de code complet, pas seulement une référence à l'API à appeler.
Fournit des implémentations C et C ++.
Fonctionne pour les paramètres de projet Unicode et MBCS.
Prend le code d'erreur comme paramètre d'entrée. Ceci est important, car le dernier code d'erreur d'un thread n'est valide qu'à des points bien définis. Un paramètre d'entrée permet à l'appelant de suivre le contrat documenté.
Implémente une sécurité d'exception appropriée. Contrairement à toutes les autres solutions qui utilisent implicitement des exceptions, cette implémentation ne perdra pas de mémoire au cas où une exception serait levée lors de la construction de la valeur de retour.
Une gestion correcte des erreurs / un rapport d'erreur, contrairement à certaines des autres réponses, qui ignorent silencieusement les erreurs.
Cette réponse a été intégrée à partir de la documentation Stack Overflow. Les utilisateurs suivants ont contribué à l'exemple: stackptr , Ajay , Cody Gray ♦ , IInspectable .
Au lieu de lancer std::runtime_error, je suggère de jeter std::system_error(lastError, std::system_category(), "Failed to retrieve error message string.")où lastErrorserait la valeur de retour GetLastError()après l'échec de l' FormatMessage()appel.
zett42
Quel est l'avantage d'utiliser FormatMessagedirectement votre version C ?
Micha Wiedenmann
@mic: C'est comme demander: "Quel est l'avantage des fonctions en C?" Les fonctions réduisent la complexité en fournissant des abstractions. Dans ce cas, l'abstraction réduit le nombre de paramètres de 7 à 3, implémente le contrat documenté de l'API en passant des indicateurs et des paramètres compatibles, et encode la logique de rapport d'erreur documentée. Il fournit une interface concise, avec une sémantique facile à deviner, vous évitant d'avoir à investir 11 minutes pour lire la documentation .
IInspectable le
J'aime cette réponse, il est très clair qu'une attention particulière a été portée à l'exactitude du code. J'ai deux questions. 1) Pourquoi utilisez-vous ::HeapFree(::GetProcessHeap(), 0, p)dans le deleter au lieu de ::LocalFree(p)comme le suggère la documentation? 2) Je me rends compte que la documentation dit de le faire de cette façon, mais ne reinterpret_cast<LPTSTR>(&psz)viole pas la règle stricte d'aliasing?
Teh JoE
@teh: Merci pour vos commentaires. Question 1): Honnêtement, je ne sais pas si c'est sûr. Je ne me souviens pas, à qui ou pourquoi l'appel a HeapFreeété lancé, alors que c'était encore un sujet dans la documentation SO. Je vais devoir enquêter (bien que la documentation semble claire, ce n'est pas sûr). Question 2): Ceci est double. Pour une configuration MBCS, LPTSTRest un alias pour char*. Ce casting est toujours en sécurité. Pour une configuration Unicode, lancer en wchar_t*est en tout cas louche. Je ne sais pas si cela est sûr en C, mais ce n'est probablement pas en C ++. Je devrais aussi enquêter là-dessus.
IInspectable
13
En général, vous devez utiliser FormatMessagepour convertir un code d'erreur Win32 en texte.
Formate une chaîne de message. La fonction nécessite une définition de message en entrée. La définition du message peut provenir d'un tampon passé dans la fonction. Il peut provenir d'une ressource de table de messages dans un module déjà chargé. Ou l'appelant peut demander à la fonction de rechercher la ou les ressources de la table de messages du système pour la définition du message. La fonction trouve la définition de message dans une ressource de table de message basée sur un identifiant de message et un identifiant de langue. La fonction copie le texte du message formaté dans un tampon de sortie, traitant toutes les séquences d'insertion intégrées si nécessaire.
J'ai confirmé que ce code fonctionne. TS ne devrait-il pas accepter cette réponse?
swdev
2
Si c'est nécessaire pour un lancement supplémentaire, il existe un moyen plus simple de le faire en C # avec Win32Exception
SerG
2
@swdev: pourquoi quelqu'un devrait-il accepter une réponse en C # à une question marquée c ou c ++ ? En l'état, cette réponse ne répond même pas à la question posée.
IInspectable
1
Eh bien, je ne me souviens pas avoir voté sur cette proposition de réponse. J'ai corrigé la faille évidente dans la logique de @ swdev. Mais puisque vous n'allez pas me croire, je vais maintenant vous le prouver: Tenez, faites un autre vote défavorable. Celle-ci vient de moi, car cette réponse - même si elle peut être utile compte tenu d'une question différente - n'est tout simplement pas utile étant donné la question qui a été posée.
IInspectable
2
"Je suppose que vous avez des idées utiles à offrir au-delà de l'évidence" - En effet, je l'ai .
IInspectable
4
Si vous avez besoin de prendre en charge MBCS ainsi que Unicode, la réponse de Mr.C64 n'est pas tout à fait suffisante. Le tampon doit être déclaré TCHAR et converti en LPTSTR. Notez que ce code ne traite pas la nouvelle ligne ennuyeuse que Microsoft ajoute au message d'erreur.
Dans le cas où le CStringc'tor lèverait une exception, cette implémentation perd la mémoire allouée par l'appel à FormatMessage.
IInspectable
C'est vrai, mais j'utilise ce code depuis de nombreuses années et cela n'a jamais posé de problème. Le seul cas dans lequel le ctor CString est susceptible de déclencher est l'échec d'allocation de mémoire, et la plupart du code MFC - y compris les éléments fournis par Microsoft - ne gère pas les conditions de manque de mémoire aussi gracieusement que vous le souhaitez. Heureusement, la plupart des PC ont maintenant tellement de mémoire que vous devez travailler assez dur pour tout utiliser. Toute utilisation qui génère une instance CString temporaire (y compris le retour d'un CString) court ce risque. C'est un compromis risque / commodité. De plus, si cela se produit dans un gestionnaire de messages, l'exception sera interceptée.
victimofleisure
La plupart de ce commentaire est faux, désolé. "Cela ne m'est jamais arrivé" est un sacré point faible à souligner, surtout quand on sait comment le code peut échouer. La quantité de mémoire n'a aucun impact sur l'espace d'adressage disponible alloué à un processus. La RAM n'est qu'une optimisation des performances. Copy-elision empêche l'attribution d'un temporaire, lorsque le code est écrit pour autoriser NRVO. Le fait que des exceptions soient interceptées ou non dans un gestionnaire de messages dépend du bitness du processus et des paramètres externes. J'ai soumis une réponse qui montre que la gestion des risques n'entraîne pas d'inconvénients.
IInspectable
En outre, le risque de manquer de mémoire lors de la construction d'un temporaire est sans objet. À ce stade, toutes les ressources ont été libérées, et rien de mauvais n'en sortira.
IInspectable
1
Non, je suis désolé que le code ne vous soit pas utile. Mais TBH est assez bas sur ma liste de problèmes.
victimofleisure
3
voidWinErrorCodeToString(DWORD ErrorCode,string&Message){char* locbuffer = NULL;
DWORD count =FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER, NULL,ErrorCode,0,(LPSTR)&locbuffer,0,nullptr);if(locbuffer){if(count){int c;int back =0;//// strip any trailing "\r\n"s and replace by a single "\n"//while(((c =*CharPrevA(locbuffer, locbuffer + count))=='\r')||(c =='\n')){
count--;
back++;}if(back){
locbuffer[count++]='\n';
locbuffer[count]='\0';}Message="Error: ";Message+= locbuffer;}LocalFree(locbuffer);}else{Message="Unknown error code: "+ to_string(ErrorCode);}}
Pourriez-vous également ajouter quelques explications?
Robert
1
Problèmes avec cette implémentation: 1pas de prise en charge Unicode. 2Formatage inapproprié du message d'erreur. Si l'appelant a besoin de traiter la chaîne retournée, il peut simplement le faire. Votre implémentation laisse l'appelant sans option. 3Utilisation d'exceptions mais manque de sécurité d'exception appropriée. Dans le cas où les std::stringopérateurs lèvent des exceptions, le tampon alloué par FormatMessageest divulgué. 4Pourquoi ne pas simplement renvoyer a std::stringau lieu de laisser l'appelant passer un objet par référence?
IInspectable
1
Depuis c ++ 11, vous pouvez utiliser la bibliothèque standard au lieu de FormatMessage:
#include<system_error>
std::string GetLastErrorAsString(){
DWORD errorMessageID =::GetLastError();if(errorMessageID ==0){return std::string();//No error message has been recorded}else{return std::system_category().message(errorMessageID);}}
Il n'y a qu'une petite fenêtre où l'appel GetLastErrorproduit un résultat significatif. Étant donné qu'il s'agit de C ++, la seule option sûre ici consiste à demander à l'appelant de fournir le dernier code d'erreur. Cela n'aide certainement pas que le code présente des appels GetLastErrordeux fois . De plus, bien que pratique, le manque inhérent de prise en charge des caractères étendus de C ++ ne rend pas l' error_categoryinterface universellement utile. Cela ne fait qu'ajouter à la longue histoire d'opportunités manquées de C ++.
IInspectable
Bon point sur l'appel inutile à GetLastError. Mais je ne vois pas de différence entre appeler GetLastErrorici ou demander à l'appelant de l'appeler. En ce qui concerne C ++ et wchar: n'abandonnez pas l'espoir, Microsoft commence à autoriser les applications à être uniquement UTF-8 .
Chronial
« Je ne vois aucune différence » - Considérez le site appel suivant: log_error("error", GetLastErrorAsString());. Considérez également que log_errorle premier argument de est de type std::string. L'appel de conversion (invisible) c'tor a simplement abandonné vos garanties pour capturer une valeur significative à partir GetLastErrordu point où vous l'appelez.
IInspectable
Pourquoi pensez-vous que le constructeur std :: string appelle une fonction Win32?
Chronial
1
mallocappelle HeapAllocle tas de processus. Le tas de processus est évolutif. Si elle a besoin de se développer, il finira par appeler VirtualAlloc , qui ne défini dernier code d'erreur du thread appelant. Maintenant, cela manque totalement le point, à savoir: C ++ est un champ de mines. Cette implémentation ne fait qu'ajouter à cela, en fournissant une interface avec des garanties implicites qu'elle ne peut tout simplement pas respecter. Si vous pensez qu'il n'y a pas de problème, il devrait vous être facile de prouver l'exactitude de la solution proposée. Bonne chance.
IInspectable
0
Je vais laisser ceci ici car je devrai l'utiliser plus tard. C'est une source pour un petit outil compatible binaire qui fonctionnera aussi bien en assemblage, C et C ++.
GetErrorMessageLib.c (compilé en GetErrorMessageLib.dll)
#include<Windows.h>/***
* returns 0 if there was enough space, size of buffer in bytes needed
* to fit the result, if there wasn't enough space. -1 on error.
*/
__declspec(dllexport)intGetErrorMessageA(DWORD dwErrorCode, LPSTR lpResult, DWORD dwBytes){
LPSTR tmp;
DWORD result_len;
result_len =FormatMessageA(
FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_ALLOCATE_BUFFER,
NULL,
dwErrorCode,
LANG_SYSTEM_DEFAULT,(LPSTR)&tmp,0,
NULL
);if(result_len ==0){return-1;}// FormatMessage's return is 1 character too short.++result_len;
strncpy(lpResult, tmp, dwBytes);
lpResult[dwBytes -1]=0;LocalFree((HLOCAL)tmp);if(result_len <= dwBytes){return0;}else{return result_len;}}/***
* returns 0 if there was enough space, size of buffer in bytes needed
* to fit the result, if there wasn't enough space. -1 on error.
*/
__declspec(dllexport)intGetErrorMessageW(DWORD dwErrorCode, LPWSTR lpResult, DWORD dwBytes){
LPWSTR tmp;
DWORD nchars;
DWORD result_bytes;
nchars = dwBytes >>1;
result_bytes =2*FormatMessageW(
FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_ALLOCATE_BUFFER,
NULL,
dwErrorCode,
LANG_SYSTEM_DEFAULT,(LPWSTR)&tmp,0,
NULL
);if(result_bytes ==0){return-1;}// FormatMessage's return is 1 character too short.
result_bytes +=2;
wcsncpy(lpResult, tmp, nchars);
lpResult[nchars -1]=0;LocalFree((HLOCAL)tmp);if(result_bytes <= dwBytes){return0;}else{return result_bytes *2;}}
version en ligne (GetErrorMessage.h):
#ifndefGetErrorMessage_H#defineGetErrorMessage_H#include<Windows.h>/***
* returns 0 if there was enough space, size of buffer in bytes needed
* to fit the result, if there wasn't enough space. -1 on error.
*/staticinlineintGetErrorMessageA(DWORD dwErrorCode, LPSTR lpResult, DWORD dwBytes){
LPSTR tmp;
DWORD result_len;
result_len =FormatMessageA(
FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_ALLOCATE_BUFFER,
NULL,
dwErrorCode,
LANG_SYSTEM_DEFAULT,(LPSTR)&tmp,0,
NULL
);if(result_len ==0){return-1;}// FormatMessage's return is 1 character too short.++result_len;
strncpy(lpResult, tmp, dwBytes);
lpResult[dwBytes -1]=0;LocalFree((HLOCAL)tmp);if(result_len <= dwBytes){return0;}else{return result_len;}}/***
* returns 0 if there was enough space, size of buffer in bytes needed
* to fit the result, if there wasn't enough space. -1 on error.
*/staticinlineintGetErrorMessageW(DWORD dwErrorCode, LPWSTR lpResult, DWORD dwBytes){
LPWSTR tmp;
DWORD nchars;
DWORD result_bytes;
nchars = dwBytes >>1;
result_bytes =2*FormatMessageW(
FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_ALLOCATE_BUFFER,
NULL,
dwErrorCode,
LANG_SYSTEM_DEFAULT,(LPWSTR)&tmp,0,
NULL
);if(result_bytes ==0){return-1;}// FormatMessage's return is 1 character too short.
result_bytes +=2;
wcsncpy(lpResult, tmp, nchars);
lpResult[nchars -1]=0;LocalFree((HLOCAL)tmp);if(result_bytes <= dwBytes){return0;}else{return result_bytes *2;}}#endif/* GetErrorMessage_H */
cas d'utilisation dynamique (supposé que le code d'erreur est valide, sinon une vérification -1 est nécessaire):
exemple d'utilisation avec l'assembly gnu comme dans MinGW32 (encore une fois, supposé que le code d'erreur est valide, sinon -1 vérification est nécessaire).
Cela n'ajoute vraiment rien d'utile. En plus de cela, il appelle la version ANSI de FormatMessage, sans raison apparente, et se limite arbitrairement à 80 caractères, encore une fois, sans aucune raison. J'ai peur, ce n'est pas utile.
IInspectable
vous avez raison, j'espérais que personne ne remarquerait l'absence de version Unicode. Je vais vérifier comment définir une chaîne Unicode dans gnu as et modifier ma solution. désolé pour la malhonnêteté.
Dmitry
ok la version unicode est en place. et ce n'est pas une raison arbitraire; tous les messages d'erreur contiennent moins de 80 caractères ou ne valent pas la peine d'être lus, et le code d'erreur est plus important que le message d'erreur. Il n'y a pas de messages d'erreur standard qui dépassent 80 caractères, c'est donc une hypothèse sûre, et quand ce n'est pas le cas, cela ne fuit pas de mémoire.
Dmitry
3
"tous les messages d'erreur comportent moins de 80 caractères ou ne valent pas la peine d'être lus" - C'est faux. Le message d'erreur pour ERROR_LOCK_VIOLATION(33) est: «Le processus ne peut pas accéder au fichier car un autre processus a verrouillé une partie du fichier». C'est à la fois clairement plus de 80 caractères et cela vaut vraiment la peine d'être lu si vous essayez de résoudre un problème et de le trouver dans un fichier journal de diagnostic. Cette réponse n'ajoute aucune valeur substantielle par rapport aux réponses existantes.
IInspectable
Ce n’est pas moins naïf. Il échoue simplement moins souvent. Sauf pour l'implémentation de l'assembly qui concerne la taille de la mémoire tampon (prétendant avoir de la place pour 200 caractères, alors qu'elle n'a de la place que pour 100). Encore une fois, cela n'ajoute rien de substantiel, ce n'est déjà dans aucune des autres réponses. En particulier, c'est pire que cette réponse . Consultez la liste à puces et observez les problèmes que pose votre proposition de mise en œuvre, en plus de ceux que je viens de souligner.
@err,hr
montre et de demander au débogueur de convertir automatiquement le dernier code d'erreur en une représentation lisible par l'homme. Le,hr
spécificateur de format fonctionne pour toute expression qui s'évalue à une valeur intégrale, par exemple une5,hr
montre affichera "ERROR_ACCESS_DENIED: Accès refusé." .GetLastError()
documentation: " Pour obtenir une chaîne d'erreur pour les codes d'erreur système, utilisez laFormatMessage()
fonction. ". Consultez l'exemple de récupération du dernier code d'erreur sur MSDN.Réponses:
la source
(LPSTR)&messageBuffer
dans ce cas, car sinon, FormatMessageA ne pourrait pas modifier sa valeur pour pointer vers le tampon alloué.strsafe.h
tête, qui n'est pas du tout sûr , cela provoque un tas d'erreurs de compilation danswinuser.h
etwinbase.h
.1
GetLastError
est potentiellement appelée trop tard.2
Pas de support Unicode.3
Utilisation d'exceptions sans mettre en œuvre des garanties de sécurité d'exception.Mis à jour (11/2017) pour prendre en considération certains commentaires.
Exemple simple:
la source
1
échec de la spécification de l'FORMAT_MESSAGE_IGNORE_INSERTS
indicateur important .2
GetLastError
potentiellement appelé trop tard.3
Restriction arbitraire du message à 256 unités de code.4
Aucune gestion des erreurs.256
au lieu de(sizeof(buf) / sizeof(wchar_t)
? est-ce acceptable et sûr?MSDN a un exemple de code qui montre comment utiliser
FormatMessage()
etGetLastError()
ensemble: Récupération du code de la dernière erreurla source
FormatMessage transformera le retour entier de GetLastError en un message texte.
la source
GetLastError renvoie un code d'erreur numérique. Pour obtenir un message d'erreur descriptif (par exemple, à afficher à un utilisateur), vous pouvez appeler FormatMessage :
En C ++, vous pouvez considérablement simplifier l'interface en utilisant la classe std :: string:
REMARQUE: ces fonctions fonctionnent également pour les valeurs HRESULT. Changez simplement le premier paramètre de DWORD dwErrorCode à HRESULT hResult. Le reste du code peut rester inchangé.
Ces implémentations apportent les améliorations suivantes par rapport aux réponses existantes:
FORMAT_MESSAGE_IGNORE_INSERTS
drapeau. Voir l'importance de l'indicateur FORMAT_MESSAGE_IGNORE_INSERTS pour plus d'informations.Cette réponse a été intégrée à partir de la documentation Stack Overflow. Les utilisateurs suivants ont contribué à l'exemple: stackptr , Ajay , Cody Gray ♦ , IInspectable .
la source
std::runtime_error
, je suggère de jeterstd::system_error(lastError, std::system_category(), "Failed to retrieve error message string.")
oùlastError
serait la valeur de retourGetLastError()
après l'échec de l'FormatMessage()
appel.FormatMessage
directement votre version C ?::HeapFree(::GetProcessHeap(), 0, p)
dans le deleter au lieu de::LocalFree(p)
comme le suggère la documentation? 2) Je me rends compte que la documentation dit de le faire de cette façon, mais nereinterpret_cast<LPTSTR>(&psz)
viole pas la règle stricte d'aliasing?HeapFree
été lancé, alors que c'était encore un sujet dans la documentation SO. Je vais devoir enquêter (bien que la documentation semble claire, ce n'est pas sûr). Question 2): Ceci est double. Pour une configuration MBCS,LPTSTR
est un alias pourchar*
. Ce casting est toujours en sécurité. Pour une configuration Unicode, lancer enwchar_t*
est en tout cas louche. Je ne sais pas si cela est sûr en C, mais ce n'est probablement pas en C ++. Je devrais aussi enquêter là-dessus.En général, vous devez utiliser
FormatMessage
pour convertir un code d'erreur Win32 en texte.À partir de la documentation MSDN :
La déclaration de FormatMessage:
la source
Si vous utilisez c #, vous pouvez utiliser ce code:
la source
Si vous avez besoin de prendre en charge MBCS ainsi que Unicode, la réponse de Mr.C64 n'est pas tout à fait suffisante. Le tampon doit être déclaré TCHAR et converti en LPTSTR. Notez que ce code ne traite pas la nouvelle ligne ennuyeuse que Microsoft ajoute au message d'erreur.
De plus, par souci de concision, je trouve la méthode suivante utile:
la source
CString
c'tor lèverait une exception, cette implémentation perd la mémoire allouée par l'appel àFormatMessage
.la source
1
pas de prise en charge Unicode.2
Formatage inapproprié du message d'erreur. Si l'appelant a besoin de traiter la chaîne retournée, il peut simplement le faire. Votre implémentation laisse l'appelant sans option.3
Utilisation d'exceptions mais manque de sécurité d'exception appropriée. Dans le cas où lesstd::string
opérateurs lèvent des exceptions, le tampon alloué parFormatMessage
est divulgué.4
Pourquoi ne pas simplement renvoyer astd::string
au lieu de laisser l'appelant passer un objet par référence?Depuis c ++ 11, vous pouvez utiliser la bibliothèque standard au lieu de
FormatMessage
:la source
GetLastError
produit un résultat significatif. Étant donné qu'il s'agit de C ++, la seule option sûre ici consiste à demander à l'appelant de fournir le dernier code d'erreur. Cela n'aide certainement pas que le code présente des appelsGetLastError
deux fois . De plus, bien que pratique, le manque inhérent de prise en charge des caractères étendus de C ++ ne rend pas l'error_category
interface universellement utile. Cela ne fait qu'ajouter à la longue histoire d'opportunités manquées de C ++.GetLastError
. Mais je ne vois pas de différence entre appelerGetLastError
ici ou demander à l'appelant de l'appeler. En ce qui concerne C ++ et wchar: n'abandonnez pas l'espoir, Microsoft commence à autoriser les applications à être uniquement UTF-8 .log_error("error", GetLastErrorAsString());
. Considérez également quelog_error
le premier argument de est de typestd::string
. L'appel de conversion (invisible) c'tor a simplement abandonné vos garanties pour capturer une valeur significative à partirGetLastError
du point où vous l'appelez.malloc
appelleHeapAlloc
le tas de processus. Le tas de processus est évolutif. Si elle a besoin de se développer, il finira par appeler VirtualAlloc , qui ne défini dernier code d'erreur du thread appelant. Maintenant, cela manque totalement le point, à savoir: C ++ est un champ de mines. Cette implémentation ne fait qu'ajouter à cela, en fournissant une interface avec des garanties implicites qu'elle ne peut tout simplement pas respecter. Si vous pensez qu'il n'y a pas de problème, il devrait vous être facile de prouver l'exactitude de la solution proposée. Bonne chance.Je vais laisser ceci ici car je devrai l'utiliser plus tard. C'est une source pour un petit outil compatible binaire qui fonctionnera aussi bien en assemblage, C et C ++.
GetErrorMessageLib.c (compilé en GetErrorMessageLib.dll)
version en ligne (GetErrorMessage.h):
cas d'utilisation dynamique (supposé que le code d'erreur est valide, sinon une vérification -1 est nécessaire):
cas d'utilisation normal (suppose que le code d'erreur est valide, sinon -1 vérification de retour est nécessaire):
exemple d'utilisation avec l'assembly gnu comme dans MinGW32 (encore une fois, supposé que le code d'erreur est valide, sinon -1 vérification est nécessaire).
résultat:
The process cannot access the file because another process has locked a portion of the file.
la source
FormatMessage
, sans raison apparente, et se limite arbitrairement à 80 caractères, encore une fois, sans aucune raison. J'ai peur, ce n'est pas utile.ERROR_LOCK_VIOLATION
(33) est: «Le processus ne peut pas accéder au fichier car un autre processus a verrouillé une partie du fichier». C'est à la fois clairement plus de 80 caractères et cela vaut vraiment la peine d'être lu si vous essayez de résoudre un problème et de le trouver dans un fichier journal de diagnostic. Cette réponse n'ajoute aucune valeur substantielle par rapport aux réponses existantes.