Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

A way for a MFC DLL to deal, at runtime, with BSTR from Visual Basic and from Visual C++

0.00/5 (No votes)
30 Jul 2003 2  
Correct input/output of strings to/from a DLL written in VC++, using BSTR type.

Introduction

If we want to pass strings to a DLL as input or to get strings from a DLL as output and we want to do this in the most general way, we are told from Microsoft to use the BSTR type. Unfortunately, things are different if we call the DLL from a Visual Basic application or from a Visual C++ one.

In this short article, I'm going to present how to deal with this.

What is a BSTR? (from MSDN documentation)

If we look at the implementation of the BSTR type, we get the following definition:

typedef wchar_t* BSTR

So, the BSTR type is actually a typedef definition: a pointer to UNICODE characters.

To understand this, let's look at the following two definitions:

typedef wchar_t* LPWSTR
typedef char* LPSTR

The difference is in the internal representation: a BSTR contains a long variable (including the string length) before the start address and an extra null character after the last character of the string.

BSTR to and from a DLL: Visual Basic - Visual C++

Again from the MSDN documentation (in a remote part of it, to be honest !), we read what follows:

  1. Visual Basic always creates a new BSTR containing ANSI characters (not UNICODE ones!) when passing a string to a DLL
  2. Visual Basic always gets a BSTR containing UNICODE characters when getting a string from a DLL

This can be a problem, from the DLL point of view, as Visual C++ always exports and imports UNICODE strings.

So, the DLL must deal at runtime, with both the cases of input BSTR:

  1. If called from a Visual Basic application: input BSTR contains ANSI characters
  2. If called from a Visual C++ application: input BSTR contains UNICODE characters

Luckily enough, the DLL will always export BSTR with UNICODE characters.

A DLL written in Visual C++ using MFC: the BSTR2CString function

These are two functions exported by the DLL DLL_example.dll, written in Visual C++, using MFC and without defining the _UNICODE symbol:

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();
}

This is the corresponding file DLL_example.def.

LIBRARY      "DLL_example"
DESCRIPTION  'DLL_example Windows Dynamic Link Library'

EXPORTS
    ; Explicit exports can go here
    FunctionWithInputBSTR  @1 
    FunctionWithOutputBSTR @2

As already mentioned, how to build the output BSTR from a CString is invariant: return CString_str.AllocSysString();

The function BSTR2CString(BSTR_str) deals with different kinds of input 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;
}

As it can be seen, the only thing to do is to try to convert the input BSTR_str from UNICODE to ANSI calling the function ::WideCharToMultiByte and recording in the flag UsedDefaultChar if some UNICODE character in BSTR_str cannot be represented in ANSI.

In fact, ::WideCharToMultiByte supposes that BSTR_str contains UNICODE characters: if this is not so, a system-defined default character will be used to fill the output string (pointed by LPSTR p) and UsedDefaultChar will be set to TRUE.

So, depending on the value of the flag UsedDefaultChar, the corresponding conversion is performed.

Calling the DLL from Visual Basic: example

Let's suppose we have a form with a ListBox, List1.

The declaration of the two functions are:

Private Declare Sub FunctionWithInputBSTR Lib _ 
       "DLL_example" (ByVal str As String)
Private Declare Function FunctionWithOutputBSTR Lib _ 
       "DLL_example" () As String

and this is a sample of how to use them:

Dim str as String

str = "Input String"

Call FunctionWithInputBSTR(str)
      
str = StrConv(FunctionWithOutputBSTR(), vbFromUnicode)

List1.AddItem (str)

Please note that, once having gotten the string from the DLL, a conversion from UNICODE to ANSI is needed to correctly show it in the ListBox.

Calling the DLL from Visual C++: example of an MFC application

As in the previous example, m_List1 is a ListBox.

This is an example of how to load the DLL and call the two functions:

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);
}

... and that's it!

I hope that someone will find this article useful... see you!

History

31/07/03 - First issue.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here