Требуется лучшее понимание интеллектуальных указателей с помощью Windows API

Мне сложно понять умные указатели (все еще на начальных этапах изучения tbh). Может быть, я слишком долго занимался проблемой, и мне не хватает простой концепции ...

Я сейчас превращаю все свои «новые / удаляемые» в интеллектуальные указатели, поэтому у меня нет такой большой проблемы с утечками / повреждениями памяти.

С unique_ptr вы не можете просто:

PCHAR test;
std::unique_ptr<char[]> buffer = std::make_unique<char[]>(10);
buffer.get() = test;

(Пожалуйста, поправьте меня, если я ошибаюсь) Поэтому вместо этого я передаю необработанный shared_ptr, чтобы получить адрес байтов, которые мне нужно просмотреть в заголовках PE. pFileBase будет иметь байты "MZ", но мой shared_ptr не возвращается с этими байтами. Что мне не хватает?

Есть ли способ вернуть функции WinAPI в интеллектуальный указатель? Я также знаю, что мой shared_ptr не char [], так что это мой следующий шаг по исправлению.

BOOL InitializeFromDisk(std::wstring &wsTempPath, char *pFileBase)
{
 ...
 pFileBase = (PCHAR)MapViewOfFile(hFileMapping, FILE_MAP_READ, 0, 0, 0);
 if (pFileBase == 0) return FALSE;
 return TRUE;
}
int main()
{
 std::shared_ptr<char> pFile = std::make_shared<char>(0);
 InitializeFromDisk(L"c:\\...", pFile.get());
 ...
 PIMAGE_DOS_SIGNATURE pDosHdr;
 std::copy(pFile, 2, pDosHdr); //I'm sure this line doesn't quit work yet 
}

person A C    schedule 25.07.2017    source источник
comment
Ценная ссылка: en.cppreference.com/w/cpp/memory   -  person Jesper Juhl    schedule 25.07.2017
comment
Вам нужно будет использовать обычный указатель, получить адрес, а затем построить unique_ptr из этого указателя (вам также нужно будет предоставить настраиваемый удалитель). Также InitializeFromDisk необходимо получить указатель по ссылке, иначе сайт вызова не увидит изменения значения указателя.   -  person NathanOliver    schedule 25.07.2017
comment
@NathanOliver Если специально не требуется, никогда не следует освобождать память с помощью стандартных методов c ++, которые были выделены тегом win32. Они представляют собой принципиально разные уровни оборудования.   -  person MooseBoys    schedule 25.07.2017
comment
@MooseBoys Вот почему я сказал, что вам также нужно будет предоставить собственный удалитель. UnmapViewOfFile должен вызываться после того, как OP будет выполнен с указателем, поэтому, если вы сделаете это в пользовательском устройстве удаления, вам больше не нужно будет помнить об этом.   -  person NathanOliver    schedule 25.07.2017
comment
API @NathanOliver win32, возвращающих простые указатели, чрезвычайно редки. Вероятно, не стоит полагаться на stl интеллектуальные указатели в таком коде, если он решает только небольшую часть вариантов использования, которые вам нужно охватить.   -  person MooseBoys    schedule 25.07.2017
comment
comment
Я бы не стал использовать для этого умный указатель. Я бы обернул API в класс и использовал RAII. Дескриптор или что-то еще будет получено в конструкторе и выпущено в деструкторе, и если что-то нужно обернуть в интеллектуальный указатель, это будет этот класс.   -  person user4581301    schedule 26.07.2017


Ответы (2)


Я мог бы сделать что-нибудь подобное. Умные указатели имеют конструкторы перемещения, поэтому их довольно эффективно возвращать, и это также дает лучший код. Обратите внимание на использование аргумента удаления в конструкторе shared_ptr.

std::shared_ptr<VOID> InitializeFromDisk(const std::wstring& wsTempPath, char *pFileBase)
{
    ...
    auto pMappedFile = MapViewOfFile(hFileMapping, FILE_MAP_READ, 0, 0, 0);
    if (pMappedFile == nullptr)
    {
        auto lastError = GetLastError();
        throw system_error(lastError, system_category());
    }
    return shared_ptr<VOID>(pMappedFile, [](auto p) { UnmapViewOfFile(p); });
}
person Michael Gunter    schedule 25.07.2017
comment
throw system_error(GetLastError(), system_category()); ... несколько хрупкий, поскольку у вас нет гарантии, что system_category() не изменит значение последней ошибки. - person zett42; 25.07.2017
comment
Спецификация C ++ не может гарантировать этого (потому что она ничего не знает о GetLastError), но я могу заверить вас, что ни одна версия реализации Microsoft C ++ никогда не установит последнее значение ошибки в system_category(). Не стесняйтесь копаться в коде. Это в C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\include\system_error - person Michael Gunter; 26.07.2017
comment
Какие у вас есть гарантии, что Microsoft никогда не изменит такое поведение? Кроме того, читатели вашего ответа могут принять его как пример, который можно применить к аналогичным случаям, что было бы опасно, потому что этот код в целом небезопасен. - person zett42; 26.07.2017

В win32 большинство API не возвращают объекты как простые объем памяти. Вместо этого они обычно возвращают либо HANDLEs, либо экземпляры объектов, производные от IUnknown. Чтобы освободить память, связанную с HANDLE, вы обычно вызываете CloseHandle. Чтобы освободить память, связанную с объектом IUnknown, вызовите ->Release(). Некоторые распределения требуют специальных вызовов освобождения. В вашем примере указатель, возвращаемый MapViewOfFile, должен быть освобожден с помощью UnmapViewOfFile.

Для наиболее распространенных типов объектов win32 интеллектуальный указатель оболочки реализованы в библиотеке Microsoft::WRL. Например:

Microsoft::WRL::ComPtr<ID3D12Device> spDevice;
D3D12CreateDevice(..., IID_PPV_ARGS(&spDevice));

Microsoft::WRL::Wrappers::FileHandle shFile;
shFile = CreateFile2(...);

И spDevice, и shFile будут соответствующим образом освобождены при выходе из области видимости.

Для других распределений, таких как возвращенный указатель MapViewOfFile, вам нужно будет создать свой собственный класс интеллектуального указателя / дескриптора.

person MooseBoys    schedule 25.07.2017
comment
Вы можете передать функцию удаления в конструктор интеллектуального указателя. Это лучшее решение, чем создание собственного класса интеллектуального указателя. - person Michael Gunter; 25.07.2017
comment
@MichaelGunter std::shared_ptr и т. Д. Плохо подходят для выделения стиля win32. Хотя это своего рода работает для случая MapViewOfFile, большинство выделений (например, HANDLEs) на самом деле вообще не являются указателями. - person MooseBoys; 25.07.2017
comment
С делетером все возможно. Если вы предоставите средство удаления (см. Мой ответ), shared_ptr не будет delete - вместо этого он вызовет средство удаления, которое позволяет вам использовать любой метод удаления, который вам нужен. - person Michael Gunter; 25.07.2017
comment
@MichaelGunter Хотя, конечно, можно создать собственный shared_ptr, который на самом деле не содержит указатель, это злоупотребление семантикой объекта. Вы могли бы так же легко построить shared_ptr, который начал бы воспроизводить звук через системные динамики и останавливал бы звук при выходе последнего экземпляра за пределы области видимости. Это не значит, что это хорошая идея ... - person MooseBoys; 25.07.2017
comment
Большинство дескрипторов - это просто typedef для void*, поэтому, конечно, вы можете использовать стандартные интеллектуальные указатели для управления ими. - person zett42; 25.07.2017
comment
Ну, HANDLE - указатель. В частности, это void*. Так что shared_ptr<void> или shared_ptr<VOID> подойдут. Единственный реальный аргумент против их использования таким образом - это потеря некоторой безопасности типов - учитывая shared_ptr<void>, вы не можете просто знать, представляет ли содержащийся указатель HWND, HANDLE, HMODULE или что-то еще, полностью подобное результату MapViewOfFile - person Michael Gunter; 25.07.2017
comment
@MichaelGunter Сохранение семантики - это именно причина, по которой этого не следует делать. Есть причина, по которой в win32 есть около 40 с лишним экземпляров typedef HANDLE HSomeObject; это потому, что они придают объекту семантику. - person MooseBoys; 25.07.2017
comment
Что ж, вы можете сделать то же самое для умных указателей: using SharedHWND = std::shared_ptr<void>. - person zett42; 25.07.2017
comment
Большинство дескрипторов в Win32 на самом деле являются типизированными указателями на структуры. В наши дни существует относительно немного типов дескрипторов, которые представляют собой просто typedef для HANDLE. В отличие от моего предыдущего комментария, например, HWND - это struct HWND__*. Таким образом, вы можете сохранить безопасность типов, используя эту структуру - HWND будет храниться в shared_ptr<HWND__>. - person Michael Gunter; 25.07.2017
comment
@MichaelGunter Это деталь реализации. Я думаю, std::shared_ptr<std::remove_pointer<HWND>::type> было бы более портативным. - person zett42; 25.07.2017
comment
@MichaelGunter, который истинен только тогда, когда STRICT определен, в противном случае это нетипизированные указатели void*. - person Remy Lebeau; 26.07.2017