CSysError class:

// error.h #pragma once #include <Windows.h> #include <memory> class CError { public: CError(); virtual ~CError(); virtual const TCHAR* description() const = 0; unsigned long code() const; protected: unsigned long m_errCode; }; class CSysError : public CError { public: CSysError(); virtual ~CSysError(); virtual const TCHAR* description() const override; DWORD bytesInMsg() const; protected: std::shared_ptr<TCHAR> m_errMsg; }; // error.cpp #include "error.h" CError::CError() : m_errCode( 0UL ) {} CError::~CError() {} unsigned long CError::code() const { return m_errCode; } CSysError::CSysError() : CError() , m_errMsg( nullptr ) { DWORD dwErrCode = GetLastError(); HLOCAL pMsgBuf = NULL; FormatMessage( FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER , NULL , dwErrCode , MAKELANGID( LANG_ENGLISH, SUBLANG_ENGLISH_US ) , (PTSTR) &pMsgBuf , 0 , NULL ); if( pMsgBuf != NULL ) { m_errMsg.reset( (TCHAR*) pMsgBuf ); m_errCode = dwErrCode; } else { // LocalFree( pMsgBuf ); } } CSysError::~CSysError() {} const TCHAR* CSysError::description() const { return ( m_errMsg ) ? m_errMsg.get() : __TEXT( "" ); } DWORD CSysError::bytesInMsg() const { if( m_errMsg ) return HeapSize( GetProcessHeap(), HEAP_NO_SERIALIZE, m_errMsg.get() ); return 0UL; } 

Problem place:

 HANDLE hFile = CreateFile( __TEXT( "C:/Users/isnullxbh/Documents/Visual Studio 2015/Projects/winapi/example2.txt" ) , GENERIC_READ | GENERIC_WRITE , FILE_SHARE_READ , NULL , OPEN_EXISTING , NULL , NULL ); if( (unsigned) hFile == 0xffffffff ) { CSysError sysError; const TCHAR* pErrMsg = sysError.description(); DWORD dwBufSizeInBytes = sysError.bytesInMsg(); DWORD dwNumCharsForWrite = dwBufSizeInBytes / sizeof( TCHAR ); DWORD dwWrittenChars = 0; WriteConsole( hSTDErr, (LPCVOID) pErrMsg, dwNumCharsForWrite, &dwWrittenChars, NULL ); if( dwNumCharsForWrite != dwWrittenChars ) { // TODO } } 

When a destructor is called on a sysError object ( sysError class), indefinite behavior is observed (so to speak): with a release configuration everything goes without errors, with a debug configuration there are problems with std::shared_ptr destruction that complains about an incomplete TCHAR type. What tell me?

Call stack: enter image description here

    1 answer 1

    I think the problem is that you are working incorrectly with memory.

    We read the documentation for the FormatMessage function:

    lpBuffer [out]

    This specifies the formatted message. If dwFlags includes FORMAT_MESSAGE_ALLOCATE_BUFFER , it is

    Transfer:

    Pointer to a buffer that will accept a string terminated by zero containing a formatted message. If dwFlags contains FORMAT_MESSAGE_ALLOCATE_BUFFER , the function allocates a buffer using the LocalAlloc function and places a pointer to the buffer at the address specified in the lpBuffer .

    In other words, the memory for the string is allocated by the LocalAlloc function, and you push a pointer to this memory in std::shared_ptr , which, when destroyed, tries to free the memory using a completely different method (I suspect that this is delete[] ). Naturally, an indefinite behavior results. To clear such memory, you need to use LocalFree .

    I would copy the resulting string to std::string , which I would pass to std::shared_ptr , and I would immediately clear the memory with a LocalFree . Or you can create your own small wrapper class that automatically destroys such a string and would pass its instance to std::shared_ptr . For example, this class might look like this:

     template<class T> class LocalAllocWrapper { private : T* p; public : LocalAllocWrapper(T* _p) : p(_p) {} ~LocalAllocWrapper() { LocalFree(p); } T* data() { return p; } } 
    • If the class in the destructor calls LocalFree , then probably for symmetry it is worth calling LocalAlloc in the constructor? - VladD
    • one
      FormatMessage itself jerks LocalAlloc, - KoVadim
    • Thank you noted. Tell me, please, if FormatMessage would not jerk LocalAlloc, but HeapAlloc - would everything be normal in your opinion? - isnullxbh
    • @isnullxbh I don’t think everything would be fine. Everything would be fine if FormatMessage pulled new[] , but it cannot do this, because new[] implemented in the application program, and FormatMessage is in the system library. The fact is that the memory allocator in RTL can be implemented as you like. Always (more precisely, almost always) there are paired functions for allocating / freeing memory and their calls must be correlated. - user194374
    • @kff, thank you very much for the detailed answer) - isnullxbh