BSTR Различия между SysAllocString() и BSTR()?

Я пишу программу, которая принимает входные данные из командной строки и использует эти входные данные для подключения к удаленному серверу WMI и выполнения методов. Мой код выглядит следующим образом:

#define _WIN32_DCOM
#include <comdef.h>
#include <WbemIdl.h>
#include <stdio.h>
#pragma comment(lib, "wbemuuid.lib")

int wmain(int argc, wchar_t* argv[]) {
    // <SNIP>

    /* Build dynamic string containing the hostname of the target and namespace.
       Example: \\DSKT-390047\ROOT\CIMV2 */
    wchar_t cimv2[256] = { 0 };
    swprintf_s(cimv2, 256, L"\\\\%ls\\ROOT\\CIMV2", argv[1]);

    IWbemServices* pSvc = NULL;
    hr = pLoc->ConnectServer(BSTR(cimv2), BSTR(argv[2]), BSTR(argv[3]), NULL,
    0, NULL, NULL, &pSvc);
    if (FAILED(hr)) {
        pLoc->Release();
        CoUninitialize();
        return 1;
    }

    // <SNIP>
}

Метод ConnectServer() требует 8 аргументов, 5 из которых имеют тип BSTR. Microsoft уточняет, что для создания типов BSTR мне нужно сделать следующее:

BSTR MyBstr = SysAllocString(L"foo");

Однако я видел, как некоторые разработчики также использовали BSTR(variable), где переменная имеет тип wchar_t*, поэтому мне интересно, в чем разница между ними? Я не могу найти много информации о последнем.

Хорошо ли мне поступать так, как сейчас? Или мне следует сначала выделить BSTR типы, использовать их, а затем освободить?

Я думаю, что то, что я делаю сейчас, позволяет избежать необходимости освобождать эти выделенные объекты, но что-то не так.

🤔 А знаете ли вы, что...
C++ обладает возможностью шаблонного программирования, что позволяет создавать обобщенные алгоритмы и структуры данных.


117
2

Ответы:

Решено

(BSTR) приведение типа wchar_t* — это серьезная программная ошибка, которая, хотя и будет компилироваться и может работать, приведет к сбоям, когда функция, которая принимает BSTR, попытается обработать его, потому что тип BSTR имеет размер, передаваемый перед указателем и поэтому может содержать нулевые символы. Подробнее здесь. Он был создан таким образом, чтобы быть совместимым с wchar_t* там, где необходим wchar_t*, т. е. вы можете передать BSTR функции, которая ожидает wchar_t*. Но не наоборот.

BSTR на самом деле представляет собой такую ​​структуру:

struct BSTR
{
     unsigned int sz = <the_size>;
     wchar_t str[the_size];
     wchar_t nullchar = 0;
};

где вы фактически получаете указатель на str, а не на начало структуры, чтобы сделать ее совместимой с wchar_t*, чтобы приведение к wchar_t* работало в таких функциях, как wcslen, если BSTR не содержит нулевых символов. Это может эффективно помешать программисту обнаружить свои ошибки.

Просто попробуйте, например, использовать SysStringLen для wchar_t*. Функции, которые работают с BSTR, вычитают 4 из переданного значения и ожидают, что там будет размер.

Правильный способ — использовать _bstr_t для переноса, который внутренне создаст SysAllocString при создании и SysFreeString при разрушении.

_bstr_t f(L"Hello");
// pass f to a function that expects a BSTR.

В большинстве случаев BSTR действительно содержит строку с нулевым завершением, и ничего страшного не произойдет, если вы выполните преобразование между BSTR и wchar_t* без использования API автоматизации.

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

Когда вы используете SysString* API, подсистема COM может использовать тот же API, чтобы правильно определить размер блока памяти и точно маршалировать ваш вызов, доставляя строковый аргумент точно так, как он находится где-то еще (и это «где-то еще» может быть другим потоком, процессом и даже удаленная система). Точность передачи аргументов будет потеряна, если вы приведете wchar_t* к BSTR и не будете использовать API, предназначенные для типа BSTR. Однако если маршалинг не используется, то зачастую разницы нет.

В общем, вам следует использовать связанные с BSTR API и распределения, чтобы оставаться в безопасности и не гадать, действительно ли это требуется в данном контексте или нет. Для этого у вас также есть хорошие помощники, такие как bstr_t и wil::unique_bstr и другие.