Как вернуть BSTR, который содержит несколько нулевых символов из DLL COM

Я создаю COM-dll, которую можно использовать из PHP для чтения файла с отображением памяти, размер которого я уже знаю, хотя у меня нет проблем с чтением файла, я не могу правильно вернуть его как BSTR. Когда я использую dll, он возвращает только символы перед нулевым символом (в данном случае 3 символа), я знаю, что файлы могут содержать несколько нулевых символов, поэтому я указал размер в функции MultiByteToWideChar, но он все еще не работает .

STDMETHODIMP CMemReaderImpl::ReadFile(BSTR* filepath, BSTR* Ofile)
{

    if (*filepath == nullptr) {
        *Ofile = _com_util::ConvertStringToBSTR("err");
    }

    std::wstring wpath(*filepath, SysStringLen(*filepath));

    LPCWSTR lpath = wpath.c_str();

    HANDLE hFileMap;
    PCHAR lpBuffer = NULL;

    hFileMap = OpenFileMapping(
        FILE_MAP_ALL_ACCESS,
        FALSE,
        lpath
    );

    if (hFileMap == NULL) {
        char* err = "ERROR";
        *Ofile = _com_util::ConvertStringToBSTR(err);
    }

    lpBuffer = (PCHAR)MapViewOfFile(
        hFileMap,
        FILE_MAP_ALL_ACCESS,
        0,
        0,
        BUFF_SIZE
    );

    if (lpBuffer == NULL) {
        char* err = "ERROR";
        *Ofile = _com_util::ConvertStringToBSTR(err);
    }

    //where the magic happens

    int wslen = MultiByteToWideChar(CP_ACP, 0, lpBuffer, 1000, 0, 0);
    BSTR bstr = SysAllocStringLen(0, wslen);
    MultiByteToWideChar(CP_ACP, 0, lpBuffer, 1000, bstr, wslen);

    *Ofile = bstr;
    UnmapViewOfFile(lpBuffer);

    CloseHandle(hFileMap);

    return S_OK;
}

Я действительно хочу вернуть весь файл как BSTR *, чтобы им можно было манипулировать с помощью другой программы php, но пока ничего не работает.

PHP-код:

<?php
    $obj = new COM("MemReader.MemReader");
    $result = $obj->ReadFile("Local\\imagen3.file");
    echo $result; //reads first 3 characters fine
    echo $result[4]; //error nothing here
?>

person Caesar    schedule 24.05.2019    source источник
comment
Мне более любопытно, как OP определяет, что возвращаются только символы до первого нуля (т.е. они используют функцию, которая печатает строки с завершением). Вот шальная мысль. Как насчет отладки кода, чтобы проверить, что такое wslen на самом деле.   -  person WhozCraig    schedule 24.05.2019
comment
Итак, в чем ценность wslen? Это 512? Вы проверили содержимое bstr (с помощью отладчика памяти, чтобы увидеть 0 символов)?   -  person Simon Mourier    schedule 24.05.2019
comment
@SimonMourier Спасибо за быстрые ответы, отладка кода (в другом файле). Я вижу, что wslen равен 1000, но bstr - это просто II * первые три символа, я знаю, что есть другие символы, поскольку, если я печатаю символы по отдельности, я можно увидеть их содержимое, возможно, я пропустил вариант.   -  person Caesar    schedule 24.05.2019
comment
Ожидается 1000. Как ты тогда смотришь на bstr? Когда вы говорите, что здесь другие персонажи, в чем проблема? Я подозреваю, что на стороне метода все в порядке (bstr - это буфер размером 1000 * 2 байта с нулями внутри). Вы уверены, что не на стороне php или во время перехода (что бы это ни было) вы теряете символы после нулевых символов?   -  person Simon Mourier    schedule 24.05.2019
comment
@SimonMourier, может быть, вы правы, проблема может быть на стороне php, в любом случае я отредактирую свой вопрос, чтобы включить php-код (например, 4 строки).   -  person Caesar    schedule 24.05.2019
comment
Я не думаю, что это на стороне вашего php-кода, но я не уверен, что уровень взаимодействия php поддерживает BSTR с нулевыми байтами внутри...   -  person Simon Mourier    schedule 24.05.2019
comment
Весь этот код предполагает, что файл является текстовым по своей природе, но в текстовом файле с кодировкой ACP никогда не должно быть НИКАКИХ пустых значений. Итак, какова фактическая кодировка файла? Если он имеет нули, но не является чем-то вроде UTF-16, который использует нулевые байты (и НЕ должен передаваться в MultiByteToWideChar()), то файл на самом деле не является текстовым файлом, и в этом случае почему бы не вернуть содержимое файла в Вместо этого PHP как массив байтов?   -  person Remy Lebeau    schedule 25.05.2019
comment
Кстати, почему filepath передается как BSTR*, а не просто как BSTR? BSTR уже является указателем, а ReadFile() не изменяет filepath, поэтому дополнительная косвенность не нужна (если только PHP не использует ее внутренне, что противоречит правилам управления памятью COM). Кроме того, нет необходимости преобразовывать filepath в std::wstring, BSTR можно использовать как есть везде, где ожидается LPCWSTR. И ReadFile() необходимо return немедленно, когда он назначает сообщение об ошибке для *Ofile (не забудьте сначала очистить успешно полученные ресурсы Win32!).   -  person Remy Lebeau    schedule 25.05.2019
comment
@RemyLebeau спасибо, вы были правы, похоже, я просто делал дополнительные преобразования. Как вы сказали, я работаю не с текстовыми файлами, а с изображениями. Я попытался заменить PCHAR на BYTE*, и теперь моей возвращаемой переменной также является BYTE*, но кажется, что я могу вернуть только один байт (73 или I в данном конкретном случае).   -  person Caesar    schedule 26.05.2019


Ответы (1)


Я не могу говорить о PHP, но в COM BSTR не является правильным типом для передачи двоичных данных, вместо этого используйте SAFEARRAY(VT_UI1):

STDMETHODIMP CMemReaderImpl::ReadFile(BSTR filepath, SAFEARRAY** Ofile)
{
    if (!Ofile)
        return E_POINTER;
    *Ofile = nullptr;

    if (!filepath)
        return E_INVALIDARG;

    HANDLE hFileMap = OpenFileMapping(FILE_MAP_READ, FALSE, filepath);
    if (!hFileMap) {
        DWORD err = GetLastError();
        return HRESULT_FROM_WIN32(err);
    }

    LPBYTE lpBuffer = (LPBYTE) MapViewOfFile(hFileMap, FILE_MAP_READ 0, 0, BUFF_SIZE);
    if (!lpBuffer) {
        DWORD err = GetLastError();
        CloseHandle(hFileMap);
        return HRESULT_FROM_WIN32(err);
    }

    SAFEARRRAYBOUND bounds;
    bounds.lLbound = 0;
    bounds.cElements = BUFF_SIZE;

    SAFEARRAY *sa = SafeArrayCreate(VT_UI1, 1, &bounds);
    if (!sa) {
        UnmapViewOfFile(lpBuffer);
        CloseHandle(hFileMap);
        return E_OUTOFMEMORY;
    }

    void *data;
    SafeArrayAccessData(sa, &data); 
    memcpy(data, lpBuffer, BUFF_SIZE);
    SafeArrayUnaccessData(sa);

    UnmapViewOfFile(lpBuffer);
    CloseHandle(hFileMap);

    *Ofile = sa;
    return S_OK;
}

Я не знаю, совместимо ли это с PHP.

Если вы должны использовать BSTR, попробуйте SysAllocStringByteLen() сохранить байты как есть без преобразования в Unicode:

STDMETHODIMP CMemReaderImpl::ReadFile(BSTR filepath, BSTR* Ofile)
{
    if (!Ofile)
        return E_POINTER;
    *Ofile = nullptr;

    if (!filepath)
        return E_INVALIDARG;

    HANDLE hFileMap = OpenFileMapping(FILE_MAP_READ, FALSE, filepath);
    if (!hFileMap) {
        DWORD err = GetLastError();
        return HRESULT_FROM_WIN32(err);
    }

    LPSTR lpBuffer = (LPSTR) MapViewOfFile(hFileMap, FILE_MAP_READ 0, 0, BUFF_SIZE);
    if (!lpBuffer) {
        DWORD err = GetLastError();
        CloseHandle(hFileMap);
        return HRESULT_FROM_WIN32(err);
    }

    BSTR bstr = SysAllocStringByteLen(lpBuffer, BUFF_SIZE);
    if (bstr) {
        UnmapViewOfFile(lpBuffer);
        CloseHandle(hFileMap);
        return E_OUTOFMEMORY;
    }

    UnmapViewOfFile(lpBuffer);
    CloseHandle(hFileMap);

    *Ofile = bstr;
    return S_OK;
}

Если это не работает для PHP, НЕ ИСПОЛЬЗУЙТЕ MultiByteToWideChar(CP_ACP) для двоичных данных, так как CP_ACP испортит данные! Кодовая страница 28591 (ISO-8859-1) является лучшим выбором, чтобы избежать повреждения, поскольку байты, закодированные в ISO-8859-1, имеют те же числовые значения, что и кодовые точки Unicode, которые они представляют:

STDMETHODIMP CMemReaderImpl::ReadFile(BSTR filepath, BSTR* Ofile)
{
    if (!Ofile)
        return E_POINTER;
    *Ofile = nullptr;

    if (!filepath)
        return E_INVALIDARG;

    HANDLE hFileMap = OpenFileMapping(FILE_MAP_READ, FALSE, filepath);
    if (!hFileMap) {
        DWORD err = GetLastError();
        return HRESULT_FROM_WIN32(err);
    }

    LPSTR lpBuffer = (LPSTR) MapViewOfFile(hFileMap, FILE_MAP_READ 0, 0, BUFF_SIZE);
    if (!lpBuffer) {
        DWORD err = GetLastError();
        CloseHandle(hFileMap);
        return HRESULT_FROM_WIN32(err);
    }

    int wslen = MultiByteToWideChar(28591, 0, lpBuffer, BUFF_SIZE, nullptr, 0);
    if (wslen == 0) {
        DWORD err = GetLastError();
        UnmapViewOfFile(lpBuffer);
        CloseHandle(hFileMap);
        return HRESULT_FROM_WIN32(err);
    }

    BSTR bstr = SysAllocStringLen(nullptr, wslen);
    if (bstr) {
        UnmapViewOfFile(lpBuffer);
        CloseHandle(hFileMap);
        return E_OUTOFMEMORY;
    }

    MultiByteToWideChar(28591, 0, lpBuffer, BUFF_SIZE, bstr, wslen);

    UnmapViewOfFile(lpBuffer);
    CloseHandle(hFileMap);

    *Ofile = bstr;
    return S_OK;
}

В противном случае вы можете просто преобразовать каждый 8-битный байт как есть в 16-битный символ вручную:

STDMETHODIMP CMemReaderImpl::ReadFile(BSTR filepath, BSTR* Ofile)
{
    if (!Ofile)
        return E_POINTER;
    *Ofile = nullptr;

    if (!filepath)
        return E_INVALIDARG;

    HANDLE hFileMap = OpenFileMapping(FILE_MAP_READ, FALSE, filepath);
    if (!hFileMap) {
        DWORD err = GetLastError();
        return HRESULT_FROM_WIN32(err);
    }

    LPBYTE lpBuffer = (LPBYTE) MapViewOfFile(hFileMap, FILE_MAP_READ 0, 0, BUFF_SIZE);
    if (!lpBuffer) {
        DWORD err = GetLastError();
        CloseHandle(hFileMap);
        return HRESULT_FROM_WIN32(err);
    }

    BSTR bstr = SysAllocStringLen(nullptr, BUFF_SIZE);
    if (!bstr) {
        UnmapViewOfFile(lpBuffer);
        CloseHandle(hFileMap);
        return E_OUTOFMEMORY;
    }

    for (int i = 0; i < BUFF_SIZE; ++i)
        bstr[i] = (OLECHAR) lpBuffer[i];

    UnmapViewOfFile(lpBuffer);
    CloseHandle(hFileMap);

    *Ofile = bstr;
    return S_OK;
}

При этом, если вышеперечисленное по-прежнему не работает для PHP, вам может потребоваться обернуть возвращенные SAFEARRAY/BSTR внутри VARIANT, как многие языки сценариев обычно обрабатывают данные COM:

STDMETHODIMP CMemReaderImpl::ReadFile(BSTR filepath, VARIANT* Ofile)
{
    ...
    VariantInit(*Ofile);
    V_VT(*Ofile) = VT_UI1 | VT_ARRAY;
    V_ARRAY(*Ofile) = sa;
    ...
}
STDMETHODIMP CMemReaderImpl::ReadFile(BSTR filepath, VARIANT* Ofile)
{
    ...
    VariantInit(*Ofile);
    V_VT(*Ofile) = VT_BSTR;
    V_BSTR(*Ofile) = bstr;
    ...
}
person Remy Lebeau    schedule 25.05.2019
comment
эй, упаковка safearray внутри варианта сработала, я наконец могу передать данные в PHP, но в виде массива байтов с беззнаковым представлением. Думаю, теперь мне нужно преобразовать массив в строку, но это может занять много времени. Мне нужен был быстрый способ передачи файлов из С++ в php, хотя файлы mappe памяти могут быть ответом, но похоже, что мне нужно вернуться к передаче данных через сокеты TCP. Большое спасибо за вашу помощь - person Caesar; 26.05.2019