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.