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:
- Visual Basic always creates a new
BSTR containing ANSI characters (not UNICODE ones!) when passing a string to a DLL
- 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:
- If called from a Visual Basic application: input
BSTR contains ANSI characters
- 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)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
CString CString_str = BSTR2CString(BSTR_str);
}
BSTR __declspec(dllexport) __stdcall FunctionWithOutputBSTR()
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
CString CString_str = _T("");
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) {
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)
CString_str = (LPCTSTR)BSTR_str;
else
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;
str = _T("Input String");
bstr = str.AllocSysString();
FunctionWithInputBSTR(bstr);
bstr = FunctionWithOutputBSTR();
str = CString(bstr);
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.