Ошибка при вызове деструктора std::shared_ptr

348
06 января 2017, 10:22

Класс CSysError:

// 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;
}

Проблемное место:

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
        }
    }

Когда вызывается деструктор у объекта sysError ( класс CSysError ) наблюдается неопределенное поведение (если так можно выразится): при release-конфигурации все проходит без ошибок, при debug-конфигурации - наблюдаются проблемы с разрушением std::shared_ptr - жалуется на неполный тип TCHAR. Что подскажите ?

Call-стек:

Answer 1

Я думаю, проблема в том, что Вы неправильно работаете с памятью.

Читаем документацию к функции FormatMessage:

lpBuffer [out]

A pointer to a buffer that receives the null-terminated string that specifies the formatted message. If dwFlags includes FORMAT_MESSAGE_ALLOCATE_BUFFER, the function allocates a buffer using the LocalAlloc function, and places the pointer to the buffer at the address specified in lpBuffer.

Перевод:

Указатель на буфер, который примет строку, завершающуюся нулём, содержащую форматированное сообщение. Если dwFlags содержит FORMAT_MESSAGE_ALLOCATE_BUFFER, функция размещает буфер, используя функцию LocalAlloc, и помещает указатель на буфер по адресу, указанному в lpBuffer.

Иными словами, память для строки выделяется функцией LocalAlloc, а Вы пихаете указатель на эту память в std::shared_ptr, который при разрушении пытается освободить память совершенно другим методом (подозреваю, что это delete[]). Естественно, получается неопределённое поведение. Для очистки такой памяти нужно использовать LocalFree.

Я бы скопировал полученную строку в std::string, которую и передал бы в std::shared_ptr, а память немедленно очистил бы посредством LocalFree. Или же можно создать свой маленький класс-обёртку, автоматически разрушающую такую строку и передавал бы в std::shared_ptr его экземпляр. Например, этот класс может выглядеть так:

template<class T> class LocalAllocWrapper {
    private :
        T* p;
    public :
        LocalAllocWrapper(T* _p) : p(_p) {}
        ~LocalAllocWrapper() {
            LocalFree(p);
        }
        T* data() {
            return p;
        }
}
READ ALSO
Как использовать DirectShow с MinGW?

Как использовать DirectShow с MinGW?

При компиляции MinGW ругается, что в dshowh присутствуют не разрешённые заголовки

345
как изменить имя программы radstudio c++builder

как изменить имя программы radstudio c++builder

Нужно изменить не имя проекта, не имя (apk) установочного файла, а когда файл уже установился и в меню телефона появляется иконка для запуска...

422
Обновление QTreeView при изменении данных

Обновление QTreeView при изменении данных

Есть класс, наследованный от QAbstractItemModel и QTreeView на основе этой моделиМодель строится на основе таблицы вида id/name/parent_id взятой из базы данных...

380
Оптимизация вложенного SQL запроса

Оптимизация вложенного SQL запроса

Всем привет и с наступившим Новым Годом!

349