65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.88/5 (16投票s)

2003 年 7 月 31 日

3分钟阅读

viewsIcon

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 文档(说实话,在其一个遥远的部分!),我们读到以下内容

  1. 当将字符串传递给 DLL 时,Visual Basic 总是创建一个包含 ANSI 字符(而不是 UNICODE 字符!)的新 BSTR
  2. 当从 DLL 获取字符串时,Visual Basic 总是获取一个包含 UNICODE 字符的 BSTR

从 DLL 的角度来看,这可能是一个问题,因为 Visual C++ 总是导出和导入 UNICODE 字符串。

因此,DLL 必须在运行时处理两种输入 BSTR 的情况

  1. 如果从 Visual Basic 应用程序调用:输入 BSTR 包含 ANSI 字符
  2. 如果从 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 - 第一版。

© . All rights reserved.