如何从 COM DLL 返回包含多个空字符的 BSTR

Posted

技术标签:

【中文标题】如何从 COM DLL 返回包含多个空字符的 BSTR【英文标题】:How to return a BSTR which contains multiple null characters from a COM DLL 【发布时间】:2019-10-11 05:32:53 【问题描述】:

我正在创建一个 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
?>

【问题讨论】:

我更好奇 OP 如何确定只返回第一个 null 的字符(即它们是否使用打印终止字符串的函数)。这是一个疯狂的想法。如何调试代码以检查 wslen 实际上是什么。 那么,wslen 的值是多少?是512吗?你检查过 bstr 的内容吗(使用内存调试器可以看到 0 个字符)? @SimonMourier 感谢您的快速响应,调试代码(在另一个文件中)我可以看到 wslen 是 1000,但 bstr 只是“II*”前三个字符,我知道其他字符在那里,因为如果我单独打印字符,我可以看到它们的内容,也许我错过了一个选项。 预计为 1000。那你怎么看bstr?当你说“其他角色在这里”时,有什么问题?我怀疑在方法方面一切正常(bstr 是 1000*2 字节的缓冲区,里面有空值)。你确定它不是在 php 方面或在转换期间(无论是什么)你在空字符之后“丢失”字符吗? @SimonMourier 也许你是对的,问题可能出在 php 方面,无论如何我要编辑我的问题以包含 php 代码(如 4 行)。 【参考方案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;
    ...

【讨论】:

嘿,将安全数组包装在一个变体中有效,我终于可以将数据传递给 PHP,但作为一个无符号表示的字节数组。我想现在我必须将数组转换为字符串,但这可能需要很多时间。我想要一种将文件从 c++ 传递到 php 的快速方法,虽然内存映射文件可能是答案,但似乎我需要返回通过 TCP 套接字传递数据。非常感谢您的帮助

以上是关于如何从 COM DLL 返回包含多个空字符的 BSTR的主要内容,如果未能解决你的问题,请参考以下文章

从 bs4.element.tag 中提取标签返回空字符串

将字符串数组从 VB6 传递到 COM 对象

如何在 Ruby 中使用 Win32API 从 DLL 返回字符串

如何在 csv/文本文件上一次计算多个字符串或一个字符串,并使用 powershell 返回包含列表数据中的字符串的值

如何从 dll 正确返回 std::list

如何将 COM 对象的 C# 引用传递给 C++ DLL