一种让 MFC DLL 在运行时处理来自 Visual Basic 和 Visual C++ 的 BSTR 的方法






4.88/5 (16投票s)
2003 年 7 月 31 日
3分钟阅读

104985
使用 BSTR 类型在 VC++ 编写的 DLL 中正确输入/输出字符串。
引言
如果我们想将字符串作为输入传递给 DLL 或从 DLL 获取字符串作为输出,并且我们希望以最通用的方式执行此操作,那么微软告诉我们使用 BSTR
类型。不幸的是,如果从 Visual Basic 应用程序或 Visual C++ 应用程序调用 DLL,情况会有所不同。
在这篇短文中,我将介绍如何处理这个问题。
什么是 BSTR?(来自 MSDN 文档)
如果我们查看 BSTR
类型的实现,我们会得到以下定义
typedef wchar_t* BSTR
因此,BSTR
类型实际上是一个 typedef
定义:指向 UNICODE 字符的指针。
为了理解这一点,让我们看看以下两个定义
typedef wchar_t* LPWSTR typedef char* LPSTR
区别在于内部表示:BSTR
在起始地址之前包含一个 long
变量(包括字符串长度),并在字符串的最后一个字符之后包含一个额外的空字符。
BSTR 到 DLL 和来自 DLL:Visual Basic - Visual C++
同样来自 MSDN 文档(说实话,在其一个遥远的部分!),我们读到以下内容
- 当将字符串传递给 DLL 时,Visual Basic 总是创建一个包含 ANSI 字符(而不是 UNICODE 字符!)的新
BSTR
- 当从 DLL 获取字符串时,Visual Basic 总是获取一个包含 UNICODE 字符的
BSTR
从 DLL 的角度来看,这可能是一个问题,因为 Visual C++ 总是导出和导入 UNICODE 字符串。
因此,DLL 必须在运行时处理两种输入 BSTR
的情况
- 如果从 Visual Basic 应用程序调用:输入
BSTR
包含 ANSI 字符 - 如果从 Visual C++ 应用程序调用:输入
BSTR
包含 UNICODE 字符
幸运的是,DLL 将始终使用 UNICODE 字符导出 BSTR
。
使用 MFC 的 Visual C++ 编写的 DLL:BSTR2CString 函数
这些是 DLL DLL_example.dll 导出的两个函数,使用 MFC 并未定义 _UNICODE
符号的 Visual C++ 编写
void __declspec(dllexport) __stdcall FunctionWithInputBSTR(BSTR BSTR_str) { // Mandatory instruction in a MFC DLL AFX_MANAGE_STATE(AfxGetStaticModuleState()); // Convertion function: see below CString CString_str = BSTR2CString(BSTR_str); // Code using input CString_str } BSTR __declspec(dllexport) __stdcall FunctionWithOutputBSTR() { // Mandatory instruction in a MFC DLL AFX_MANAGE_STATE(AfxGetStaticModuleState()); CString CString_str = _T(""); // Code defining output CString_str return CString_str.AllocSysString(); }
这是对应的文件 DLL_example.def。
LIBRARY "DLL_example" DESCRIPTION 'DLL_example Windows Dynamic Link Library' EXPORTS ; Explicit exports can go here FunctionWithInputBSTR @1 FunctionWithOutputBSTR @2
如前所述,从 CString
构建输出 BSTR
的方式是不变的:return CString_str.AllocSysString();
函数 BSTR2CString(BSTR_str)
处理不同类型的输入 BSTR
static CString BSTR2CString(BSTR BSTR_str) { CString CString_str = _T(""); if (BSTR_str != NULL) // To be sure that input string is valid... { CString s; LPSTR p = s.GetBuffer(::SysStringLen(BSTR_str) + 1); BOOL UsedDefaultChar; ::WideCharToMultiByte(CP_ACP, 0, BSTR_str, -1, p, ::SysStringLen(BSTR_str)+1, NULL, &UsedDefaultChar); if (UsedDefaultChar) // BSTR_str contains an ANSI string CString_str = (LPCTSTR)BSTR_str; else // BSTR_str contains an UNICODE string CString_str = (LPCWSTR)BSTR_str; } return CString_str; }
可以看出,唯一要做的事情是尝试将输入 BSTR_str
从 UNICODE 转换为 ANSI,调用函数 ::WideCharToMultiByte
,并在标志 UsedDefaultChar
中记录如果 BSTR_str
中的某些 UNICODE 字符无法在 ANSI 中表示。
事实上,::WideCharToMultiByte
假设 BSTR_str
包含 UNICODE 字符:如果不是这样,将使用系统定义的默认字符来填充输出字符串(由 LPSTR p
指向),并且 UsedDefaultChar
将被设置为 TRUE
。
因此,根据标志 UsedDefaultChar
的值,将执行相应的转换。
从 Visual Basic 调用 DLL:示例
假设我们有一个带有 ListBox
List1
的表单。
这两个函数的声明是
Private Declare Sub FunctionWithInputBSTR Lib _
"DLL_example" (ByVal str As String)
Private Declare Function FunctionWithOutputBSTR Lib _
"DLL_example" () As String
这是一个如何使用它们的示例
Dim str as String
str = "Input String"
Call FunctionWithInputBSTR(str)
str = StrConv(FunctionWithOutputBSTR(), vbFromUnicode)
List1.AddItem (str)
请注意,在从 DLL 获取字符串后,需要从 UNICODE 转换为 ANSI 才能在 ListBox
中正确显示它。
从 Visual C++ 调用 DLL:MFC 应用程序的示例
与之前的示例一样,m_List1
是一个 ListBox
。
这是一个加载 DLL 并调用这两个函数的示例
typedef void (WINAPI* ptr_func1)(BSTR bstr); typedef BSTR (WINAPI* ptr_func2)(void); ptr_func1 FunctionWithInputBSTR = NULL; ptr_func2 FunctionWithOutputBSTR = NULL; HINSTANCE hLib; hLib = LoadLibrary(_T("DLL_example")); if (hLib == NULL) { MessageBox(_T("Unable to load .dll"), NULL, MB_ICONERROR); } else { FunctionWithInputBSTR = (ptr_func1)GetProcAddress(hLib, _T("FunctionWithInputBSTR")); FunctionWithOutputBSTR = (ptr_func2)GetProcAddress(hLib, _T("FunctionWithOutputBSTR")); BSTR bstr; CString str; // CString => BSTR conversion and call to function str = _T("Input String"); bstr = str.AllocSysString(); FunctionWithInputBSTR(bstr); // Call to function and BSTR => CString conversion bstr = FunctionWithOutputBSTR(); str = CString(bstr); // Add the CString to the ListBox m_List1.AddString((LPCTSTR)str); FreeLibrary(hLib); }
...就这样!
我希望有人会发现这篇文章有用...再见!
历史
03/07/31 - 第一版。