如何从 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的主要内容,如果未能解决你的问题,请参考以下文章
如何在 Ruby 中使用 Win32API 从 DLL 返回字符串