I am trying to call a C# library from C++. Here's the function that makes the first call:
CapsReport::CapsReport(const CCOMString& reportFileName, CCDatabase* pDatabase)
: m_pDatabase(pDatabase), m_pReportServer(__uuidof(ReportServer))
{
::CoInitialize(NULL);
SetReportName(reportFileName);
// public void Load(string DSNname, string userName, string password, string reportFileName)
BSTR dsnBstr = pDatabase->DataSource().GetManagedBstr();
BSTR userBstr = pDatabase->UserName().GetManagedBstr();
BSTR passwordBstr = pDatabase->Password().GetManagedBstr();
BSTR reportNameBstr = ::SysAllocString(reportFileName);
m_pReportServer->Load(dsnBstr, userBstr, passwordBstr, reportNameBstr);
::SysFreeString(reportNameBstr);
}
By the time the Load() method is called, all three BSTRs returned by GetManagedBstr() contain the password. I'm trying to figure out why.
DataSource(), UserName() and Password() all return objects of type CCOMString:
CCOMString UserName() { return m_User; };
CCOMString Password() { return m_Pwd; };
CCOMString DataSource() { return m_DSN; };
I didn't want to have to call ::SysAllocString() and ::SysFreeString() every time I wanted to pass a CCOMString into a C# library, so I added a private BSTR member to the CCOMString class initialized to NULL. In the destructor, I check to see if that member is null. If it is not, I call ::SysFreeString() and set it to NULL.
The GetManagedBstr() method uses an existing member function of CCOMString named AllocSysString(). First, here's GetManagedBstr():
BSTR CCOMString::GetManagedBstr()
{
m_bstr = AllocSysString();
return m_bstr;
}
Now, here's AllocSysString():
BSTR CCOMString::AllocSysString() const
{
#ifndef _UNICODE
int nLen = MultiByteToWideChar(CP_ACP, 0, (LPCSTR)m_pszString, GetLength(), NULL, NULL);
BSTR bstr = ::SysAllocString(NULL, nLen+1);
MultiByteToWideChar(CP_ACP, 0, (LPCSTR)m_pszString, GetLength(), bstr, nLen);
#else
int nLen = _tcslen(m_pszString);
//BSTR bstr = ::SysAllocStringLen(NULL, nLen);
BSTR bstr = ::SysAllocString(m_pszString);
// _tcscpy_s(bstr, nLen, m_pszString);
#endif
return bstr;
}
m_pszString is merely the plain ordinary C++ string wrapped by CCOMString.
A BSTR object is a pointer. Somehow, all three BSTR objects are ending up pointing to the same memory location, even though they should have come from different CCOMString objects. How is this happening?
Related
I'm using a variable of type CComPtr and I need to modify a LPWSTR* variable. The function I use extracts metadata about file description for executable files. I am not sure about how I should allocate memory for the LPWSTR* and how to change its value to the one of the CComPtr. lpszFileDesc must get the value of description.
BOOL ExeDescription(LPWSTR* lpszFileDesc, LPCWSTR filePath)
{
CComPtr<IShellItem2> item;
HRESULT hr = CoInitialize(nullptr);
*lpszFileDesc = NULL;
BOOL fResult = TRUE;
hr = SHCreateItemFromParsingName(filePath, nullptr, IID_PPV_ARGS(&item));
if (FAILED(hr))
{
fResult = FALSE;
}
else
{
CComPtr<WCHAR> description;
hr = item->GetString(PKEY_FileDescription, &description);
if (FAILED(hr))
{
fResult = FALSE;
}
else
{
if (!description)
{
*lpszFileDesc = PathFindFileNameW(filePath);
}
else
{
// here I want to copy the contents of description
// into lpszFileDesc but I don't know how
}
if (!*lpszFileDesc)
{
fResult = FALSE;
}
}
}
CoUninitialize();
return fResult;
}
Also, when I call this function how do I deallocate the memory for lpszFileDesc after calling the function?
For example if in wmain() I have:
LPWSTR* lpszFileDesc;
ExeDescription(LPWSTR* lpszFileDesc, LPCWSTR filePath);
How do I deallocate the memory if I don't need the file description after that?
Basic Errors
HRESULT hr = CoInitialize(nullptr);
...
CoUninitialize();
COM should be initialized only once at startup of the thread, because it defines the concurrency model of the thread (amongst other things). It's not up to your function to decide how COM will be initialized for the thread. Once COM is initialized for a thread, subsequent calls to CoInitialize[Ex] within that thread will fail anyway. So remove this code and put it into WinMain or the main function of the thread where you are using COM.
CComPtr<WCHAR> description;
Using CComPtr is wrong here, because IShellItem2::GetString() does not return an interface, but a simple C string. Such "raw" memory allocated by COM API must be freed using CoTaskMemFree(), which can be automated by using CComHeapPtr.
Preferred solution - change the interface
how do I deallocate the memory for lpszFileDesc
Do yourself a favor and use std::wstring instead of raw C string pointer to return a string from your function. The std::wstring destructor takes care of deallocation automatically. Manually managing the memory of C strings is too cumbersome and error-prone. When someone else reads your code and sees std::wstring, there will be no question about how the memory is managed.
I suggest to change your interface like this:
BOOL ExeDescription(std::wstring& fileDesc, LPCWSTR filePath);
... and the assignment within the function body becomes:
if (!description)
{
fileDesc = PathFindFileNameW(filePath);
}
else
{
fileDesc = description;
}
CComHeapPtr<WCHAR> has a conversion operator to WCHAR*, that's why the assignment to std::wstring simply works.
Call the function like this:
std::wstring fileDesc;
ExeDescription(fileDesc, filePath);
// No worries about deallocation of fileDesc!
Solution using original interface
That being said, here is a solution using your original interface. You can either use the COM allocator, as IShellItem2::GetString() already uses it (and there will be no copying in the common case) or use a different allocator (then you always have to copy). In both cases, the caller is responsible to call the right deallocation function, which you have to document (another reason why I would prefer the std::wstring solution).
Example of using the COM allocator:
BOOL ExeDescription(LPWSTR* lpszFileDesc, LPCWSTR filePath)
{
// ... other code ...
// GetString() uses CoTaskMemAlloc() internally
hr = item->GetString(PKEY_FileDescription, lpszFileDesc);
// ... other code ...
if (! *lpszFileDesc )
{
LPCWSTR fileName = PathFindFileNameW(filePath);
// Allocate buffer using the COM allocator and copy fileName to it.
std::size_t const len = wcslen(fileName);
*lpszFileDesc = reinterpret_cast<LPWSTR>(CoTaskMemAlloc(len * sizeof(WCHAR)));
if(*lpszFileDesc)
wcscpy_s(*lpszFileDesc, len, fileName);
}
// ... more code ...
}
Usage at the caller site:
LPWSTR fileDesc = nullptr;
ExeDescription(&fileDesc, filePath);
// ... use fileDesc ...
CoTaskMemFree(fileDesc);
Simplified usage with CComHeapPtr:
CComHeapPtr<WCHAR> fileDesc;
ExeDescription(&fileDesc, filePath);
// ... use fileDesc ...
// Deallocation happens automatically through CComHeapPtr's destructor
we are having a discussion as what is a good way to return multiple strings from one dll function. Currently we have 8 strings, but there will be more. For simplicity I now consider all strings will have equal lengths.
extern "C" int DLLNAME_ _stdcall GetResult(TestResults* testResults);
where
struct TestResults
{
int stringLengths;
char* string1;
char* string2;
char* string3;
char* string4;
...
};
or second option: where
struct TestResults
{
int stringLengths;
char string1[64];
char string2[64];
char string3[64];
char string4[64];
...
};
third option:
extern "C" int DLLNAME_ _stdcall GetResult(int stringLengths, char* string1, char* string2, char* string3, ...);
The dll will communicate over a serial line and retrieve information that will be filled into the strings. Where the memory needs to be allocated is open for discussion and can be part of the answer.
The background is that we have a VB6 application team that prefers the second method and a C++/C# team that prefers the first method. Last method looks to suit both teams but looks a bit strange to me with so many parameters.
Maybe there are more options. What is common practice under Windows? Any examples from the Windows API or arguments to choose one over the other?
Edit: The strings have a meaning as in first name, last name, email. We currently have eight, but in the future we might add a couple for example for address. An array would not be the correct choice for this, but that was not clear from the original context.
The best way is probably using a safe array storing BSTR strings.
Both VB and C# understand safe arrays very well: in C#, a safe array of BSTR strings is automatically converted to a string[] array.
On the C++ side, you can use the ATL::CComSafeArray helper class to simplify safe array programming.
You will find interesting material in this MSDN Magazine article (in particular, take a look at the paragraph Producing a Safe Array of Strings).
From the aforementioned article: On the C++ side, you can implement a C-interface DLL, exporting a function like this:
extern "C" HRESULT MyDllGetStrings(/* [out] */ SAFEARRAY** ppsa)
{
try {
// Create a SAFEARRAY containing 'count' BSTR strings
CComSafeArray<BSTR> sa(count);
for (LONG i = 0; i < count; i++) {
// Use ATL::CComBSTR to safely wrap BSTR strings in C++
CComBSTR bstr = /* your string, may build from std::wstring or CString */ ;
// Move the the BSTR string into the safe array
HRESULT hr = sa.SetAt(i, bstr.Detach(), FALSE);
if (FAILED(hr)) {
// Error...
return hr;
}
}
// Return ("move") the safe array to the caller
// as an output parameter (SAFEARRAY **ppsa)
*ppsa = sa.Detach();
} catch (const CAtlException& e) {
// Convert ATL exceptions to HRESULTs
return e;
}
// All right
return S_OK;
}
On the C# side, you can use this PInvoke declaration:
[DllImport("MyDll.dll", PreserveSig = false)]
public static extern void MyDllGetStrings(
[Out, MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_BSTR)]
out string[] result);
As you declare your function as extern "C" I suppose that you cannot use std::vector<std::string> as return type.
Another possibility would be:
struct String
{
int size; /* size of string */
const char* str; /* actual string */
}
struct TestResults
{
int size; /* number of strings */
String* arr; /* pointer to an array of String */
};
and then the same as before:
extern "C" int DLLNAME_ _stdcall GetResult(TestResults* testResults);
With that you are flexible to return as much strings as you like. Also loop through your TestResults is easy.
Edit #1: As said in the comments: use BSTR. So your struct would look like:
struct TestResults
{
int size; /* number of strings */
BSTR* arr; /* pointer to an array of BSTR */
};
A BSTR will be allocated by: BSTR MyBstr = SysAllocString(L"I am a happy BSTR");. This allocation also sets the member which contain the length of the string. You have to free the allocated memory with: SysFreeString(MyBstr);. Also you need to allocate the whole array BSTR*.
I have a class defined below:
class CVariable
{
public:
CVariable(CString strData, int nNum);
CVariable(BSTR bsData);
~CVariable();
public:
VARIANT GetVariant(){return m_bsVa;};
private:
VARIANT m_bsVa;
VARIANT m_nVa;
};
And implements are:
CVariable::CVariable(CString strData, int nNum)
{
VariantInit(&m_bsVa);
BSTR bsData = ::SysAllocString(strData);
m_bsVa.vt = VT_BSTR;
m_bsVa.bstrVal = bsData;
::SysFreeString(bsData);
VariantInit(&m_nVa);
m_nVa.vt = VT_I2;
m_nVa.lVal = nNum;
}
CVariable::CVariable(BSTR bsData) {
m_bsVa.vt = VT_BSTR;
m_bsVa.bstrVal = bsData;
}
CVariable::~CVariable()
{
VariantClear(&m_bsVa);
VariantClear(&m_nVa);
}
When I try to construct two instances using constructor CVariable(CString,int),
class member m_bsVas always have the same value,while m_nVas are different.The result is below:
As you see, v1 and v2 have the same m_bsVa but different m_nVa, while using constructor CVariable(BSTR) leads to the right result. I've no idea why this can happen?
Any help will be appreciated.
I see several problems with your code.
the CVariable(CString, int) constructor allocates a BSTR for m_bsVa, but then frees the BSTR immediately, leaving m_bsVa pointing at invalid memory, and allowing the next CVariable instance to potentially reuse the same memory address for its allocated BSTR. You need to leave the BSTR allocated until you are done using m_bsVa (or at least until you want to assign a new value to it). VariantClear() will free the BSTR for you.
the CVariable(BSTR) constructor is not initializing m_nVa at all, which will cause problems for subsequent operations on it, including VariantClear(). Also, the constructor is taking ownership of the caller's BSTR. That may or may not be OK, depending on how you use this constructor. If the caller is not expecting you to take ownership, then you need to make a copy of the BSTR using SysAllocString/Len().
VARIANT is not trivially copyable. You need to use the VariantCopy() function to copy data from one VARIANT to another. That means your CVariable class needs to implement a copy constructor and copy assignment operator. Which you need to do anyway so your class conforms to the Rule of Three.
GetVariant() is returning m_bsVa as-is, so the compiler will simply copy the values of m_bsVa's fields as-is into the caller's receiving VARIANT. Since BSTR is a pointer, the caller will have direct access to the original BSTR inside of your class. That may or may not be OK, depending on how you use GetVariant(). In the current implementation, any access to the returned BSTR should be treated as read-only - the caller must not call SysFreeString() on it, and must expect any change to the CVariable object may invalidate the BSTR. If that does not suit your needs, then GetVariant() should return a new VARIANT that has copied the data via VariantCopy(), and then the caller can call VariantClear() when done using the returned VARIANT.
With that said, try something more like this:
class CVariable
{
public:
CVariable(const CString &strData, int nNum);
CVariable(BSTR bsData);
CVariable(const CVariable &src);
~CVariable();
VARIANT GetVariant() const;
CVariable& operator=(const CVariable &src);
CVariable& operator=(BSTR src);
private:
VARIANT m_bsVa;
VARIANT m_nVa;
};
CVariable::CVariable(const CString &strData, int nNum)
{
::VariantInit(&m_bsVa);
m_bsVa.vt = VT_BSTR;
m_bsVa.bstrVal = ::SysAllocString(strData);
::VariantInit(&m_nVa);
m_nVa.vt = VT_I2;
m_nVa.lVal = nNum;
}
CVariable::CVariable(BSTR bsData)
{
::VariantInit(&m_bsVa);
m_bsVa.vt = VT_BSTR;
m_bsVa.bstrVal = bsData;
/* or this, if needed:
m_bsVa.bstrVal = ::SysAllocStringLen(bsData, ::SysStringLen(bsData));
*/
::VariantInit(&m_nVa);
}
CVariable::~CVariable()
{
::VariantClear(&m_bsVa);
::VariantClear(&m_nVa);
}
VARIANT CVariable::GetVariant() const
{
return m_bsVa;
/* or this, if needed:
VARIANT result;
::VariantInit(&result);
::VariantCopy(&result, &m_bsVa);
return result;
*/
}
CVariable& CVariable::operator=(const CVariable &src)
{
if (&src != this)
{
::VariantClear(&m_bsVa);
::VariantCopy(&m_bsVa, &src.m_bsVa);
::VariantClear(&m_nVa);
::VariantCopy(&m_nVa, &src.m_nVa);
}
return *this;
}
CVariable& CVariable::operator=(BSTR src)
{
::VariantClear(&m_bsVa);
m_bsVa.vt = VT_BSTR;
m_bsVa.bstrVal = src;
/* or this, if needed:
m_bsVa.bstrVal = ::SysAllocStringLen(src, ::SysStringLen(src));
*/
::VariantClear(&m_nVa);
return *this;
}
If you use the variant_t class instead of VARIANT directly, you can greatly simplify the code while still addressing all of the points mentioned above:
class CVariable
{
public:
CVariable(const CString &strData, int nNum);
CVariable(BSTR bsData);
variant_t GetVariant() const;
private:
variant_t m_bsVa;
variant_t m_nVa;
};
CVariable::CVariable(const CString &strData, int nNum)
: m_bsVa(strData), m_nVa(nNum)
{
}
CVariable::CVariable(BSTR bsData)
: m_bsVa(bsData)
{
}
variant_t CVariable::GetVariant() const
{
return m_bsVa;
}
In this constructor :
CVariable::CVariable(BSTR bsData) {
m_bsVa.vt = VT_BSTR;
m_bsVa.bstrVal = bsData;
}
You are leaving m_nVa uninitialized - it gets some random value. It shall look like this instead:
CVariable::CVariable(BSTR bsData) {
VariantInit(&m_bsVa);
m_bsVa.vt = VT_BSTR;
m_bsVa.bstrVal = bsData;
VariantInit(&m_nVa);
}
And in this constructor:
CVariable::CVariable(CString strData, int nNum)
{
VariantInit(&m_bsVa);
BSTR bsData = ::SysAllocString(strData);
m_bsVa.vt = VT_BSTR;
m_bsVa.bstrVal = bsData;
::SysFreeString(bsData);
VariantInit(&m_nVa);
m_nVa.vt = VT_I2;
m_nVa.lVal = nNum;
}
Do not call ::SysFreeString(bsData); as bsData is owned by m_bsVa.
SysFreeString() frees the memory and the next SysAllocString() call may create a new BSTR string at the same memory address.
Instead of using naked VARIANTs, I'd suggest you use the _variant_t class instead. In this case, you will not need to worry about VariantInit()/VariantClear() at all, as it implements ownership policies in C++ style for you.
I would suggest you to use a convenient C++ RAII wrapper around raw C VARIANTs, like CComVariant from ATL.
This will simplify your code, as CComVariant will properly initialize its wrapped raw VARIANT, and clean it up as well.
You can replace your VARIANT data members with safer CComVariant wrappers:
CComVariant m_bsVa;
CComVariant m_nVa;
Then you can initialize them in constructors like this:
CVariable::CVariable(const CString& strData, int nNum)
: m_bsVa(strData), m_nVa(nNum)
{}
CVariable::CVariable(BSTR bsData)
: m_bsVa(bsData)
{}
Note that you don't need to explicitly define a destructor, as in this case CComVariant's destructor will properly cleanup data members.
Your getter could be implemented like this:
const CComVariant& CVariable::GetVariant() const
{
return m_bsVa;
}
This point will not work. How to fix it. I understand that the CStrings leave the stack after the call to the function. are the any types of strings i C++ std:strings etc. Thats behave as C# strings. How I get it to work?
void Dialog1::GetOrderingKey(LPWSTR& lpOrderingKey)
{
CString OrderingKey;
m_Result.GetWindowText(OrderingKey);
lpOrderingKey = OrderingKey.GetBuffer(0);
}
LPWSTR lpOrderingKey;
GetOrderingKey(lpOrderingKey);
int returnValue = lpfnDllOrderingCodeDataW(lpSerialNumber, lpOrderingKey, data, _countof(data));
Just return a CString from GetOrderingKey():
CString Dialog1::GetOrderingKey()
{
CString OrderingKey;
m_Result.GetWindowText(OrderingKey);
return OrderingKey;
}
CString ordering_key = GetOrderingKey();
int returnValue = lpfnDllOrderingCodeDataW(lpSerialNumber, (LPCWSTR) ordering_key, data, _countof(data));
One straight forward and simplest way would be to declare it static.
void Dialog1::GetOrderingKey(LPWSTR& lpOrderingKey)
{
static CString OrderingKey;
I have the following method :
VariantFromString(strXMLPath ,vXMLSource);
and the signature of the method is:
HRESULT VariantFromString(PCWSTR wszValue, VARIANT &Variant);
Now while I am passing a CString as below:
char cCurrentPath[FILENAME_MAX];
if (!GetCurrentDir(cCurrentPath, sizeof(cCurrentPath)))
{
return errno;
}
CString strXMLPath = cCurrentPath;
strXMLPath += XMLFILE;
VariantFromString(strXMLPath ,vXMLSource);
I am getting the error: Can not Convert from CString to PCWSTR
You really should be using Unicode (wchar_t instead of char). That is how the OS operates internally, and would prevent having to constantly convert between char types like this.
But in this scenario, you could use CString::AllocSysString to convert it to a BSTR, which is compatible with PCWSTR. Just make sure it gets freed with SysFreeString.
[edit]
For example, you could change your function to:
VARIANT VariantFromString(const CString& str)
{
VARIANT ret;
ret.vt = VT_BSTR;
ret.bstrVal = str.AllocSysString();
return ret;
}