VC++ class objects with a VARIANT member have strange behavior - c++

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

Related

gcc8 is throwing compilation error due to non-trivially copyable type

class mapInfo
{
public:
mapInfo();
~mapInfo();
public:
int dataType_m;
private:
int *frequency;
};
//constructor is defined here.
mapInfo::mapInfo() :
dataType_m(0),
frequency(NULL)
{
}
//destructor is defined here
mapInfo::~mapInfo()
{
free(frequency);
frequency = NULL;
}
Result_t Maps::add(mapInfo &mapInfo_r)
{
if (maps_mp == NULL)
{
numMaps_m = 1;
maps_mp = (mapInfo *) calloc(1, sizeof(mapInfo));
}
else
{
numMaps_m++;
maps_mp = (mapInfo *) realloc(maps_mp, numMaps_m*sizeof(mapInfo));
}
maps_mp[numMaps_m-1] = mapInfo_r; // Default copy constructor
return 1;
}
While compiling with gcc8, getting the following compilation error. It looks like defining the destructor like above giving the compilation error for gcc8.
How to resolve this?
error: 'void* realloc(void*, size_t)' moving an object of non-trivially copyable type 'class xyyz::mapInfo'; use 'new' and 'delete' instead [-Werror=class-memaccess].
That’s simply not proper C++. Rewrite your code as follows (I’m guessing here with regards to the type of frequency, but definitely don’t use free on it):
#include <vector>
class map_info
{
public:
map_info();
private:
int data_type;
std::vector<int> frequency;
};
std::vector<map_info> maps_mp;
map_info::map_info() : data_type(0), frequency() {}
// …
void maps::add(map_info& map_info)
{
maps_mp.push_back(map_info);
}
maps_mp = (mapInfo *) realloc(maps_mp, numMaps_m*sizeof(mapInfo));
This is not sensible. You can't just move an object from one aree of memory to another if that object is non-trivial.
For example, consider a string object that keeps a pointer to the string. It could look like this:
class MyString
{
char* inner_ptr;
char buf[64];
...
};
And it might have a constructor like this:
MyString::MyString (const char* j)
{
if (strlen(j) < 64)
inner_ptr = buf;
else
inner_ptr = malloc (strlen(j) + 1);
strcpy(inner_ptr, j);
}
And a destructor like this:
MyString::~MyString()
{
if (buf != inner_ptr)
free (inner_ptr);
}
Now, think about what happens if you call relloc on an array of these. The short strings will still have their inner_ptrs pointing to the old object's buffer, which you just deallocated.
The error message explains this issue reasonable well. It is simply not legal to use realloc to move a non-trivial object. You have to construct a new object because the object needs a chance to handle the change in its address.

BSTR allocation confusion

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?

Convert CComPtr<IShelltem2> to LPWSTR*?

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

How to convert a SAFEARRAY to array of COM pointers

I have a function say foo which returns SAFEARRAY of COM pointers
SAFEARRAY* foo()
{
IMyClassPtr *objs = (IMyClassPtr*)callock(n, sizeof(IMyClassPtr));
CComSafeArray<IDispatch*> sa(n);
for(UINT index = 0; index < n; index++)
{
objs[index].CreateInstance(CLSID_MyClass);
(objs[index])->AddRef();
objs[index]->put_X(index);
objs[index]->put_Y(index+10);
sa.SetAt(index, objs[index]);
}
return sa.Detach();
}
This is my MyClass
class ATL_NO_VTABLE CMyClass :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CMyClass, &CLSID_MyClass>,
public IDispatchImpl<IMyClass, &IID_IMyClass, &LIBID_mylibAlias, /*wMajor =*/ 1, /*wMinor =*/ 0>
{
public:
CMyClass();
virtual ~CMyClass();
DECLARE_REGISTRY_RESOURCEID(IDR_MyClass)
BEGIN_COM_MAP(CMyClass)
COM_INTERFACE_ENTRY(IMyClass)
COM_INTERFACE_ENTRY(IDispatch)
END_COM_MAP()
DECLARE_PROTECT_FINAL_CONSTRUCT()
HRESULT FinalConstruct()
{
return S_OK;
}
void FinalRelease()
{
}
protected:
ULONG x;
ULONG y;
public:
STDMETHOD(get_X)(ULONG* pVal);
STDMETHOD(put_X)(ULONG newVal);
STDMETHOD(get_Y)(ULONG* pVal);
STDMETHOD(put_Y)(ULONG newVal);
};
OBJECT_ENTRY_AUTO(__uuidof(MyClass), CMyClass)
now I have done this,
SAFEARRAY* sa;
IMyClassPtr* objs;
sa = foo();
if(SUCCEEDED(SafeArrayAccessData(sa, (void**)&objs)))
{
// At this point, when I debug at the autos window objs contains some value
// and I can see this message also
// <No type information available in symbol file for oleaut32.dll>
}
Please help me fix this issue.
If your safe array contains IDisptach* your call to SafeArrayAccessDate returns an IDispatch**. In fact you get the start of the array of the first pointer.
Also you code to create the objects isn't very useful. You don't need to create the array of objects (and even there is a leak you don't free it again.
Just create one object at a time and assign it to the safe array. There is no need for a second pointer. Also there is a problem with your reference counting. AFAIK there is no need to call AddRef after you createdthe instance. Is is already locked. And you don't want a second lock.
Also you should always use QueryInterface to obtain a pointer of the given type you want to store (IDisptach), and again you need to call QueryInterface when you want to convert the IDispatch back into a IMyClass pointer.

How to delete the variables with types BSTR in C++

The code below is mine structure and class containing the structure reference.
typedef struct MESSAGE
{
int MessageType;
BSTR Name;
_bstr_t TimeStampIs;
} MESSAGE, *PMESSAGE;
typedef struct MESSAGENODE
{
PMESSAGE Message;
MESSAGENODE* pNext;
} MESSAGENODE, *PMESSAGENODE;
class Message
{
private:
PMESSAGENODE MessageQueueFront;
PMESSAGENODE MessageQueueBack;
public:
bool AddMessageToQueue(PMESSAGE Message);
void DeleteMessageQueue(void){
PMESSAGE pMess;
while((pMess = GetMachineMessage()) != NULL)
{
if((pMess->DialysisDataIs))
SysFreeString(pMess->Name.Detach());
delete pMess;
}
}m;
int main()
{
PMESSAGE Message;
Message = new MESSAGE;
Message->Name=L"ABC";
Message->TimeStampIs=L"25252";
m.AddMessageToQueue(Message);
m.DeleteMessageQueue();
return 0;
}
When i compile the above code i am getting the following errors in
DeleteMessageQueue function
error C2451: conditional expression of type '_bstr_t' is illegal error
C2228: left of '.Detach' must have class/struct/union
A couple of things, first the meat of your error
SysFreeString(pMess->Name.Detach());
Message::Name is a raw BSTR pointer, which I assure you does not have a member function called Detach(). The _bstr_t class, however, does. Change your struct to:
typedef struct MESSAGE
{
int MessageType;
_bstr_t Name;
_bstr_t TimeStampIs;
} MESSAGE, *PMESSAGE;
Once done, you can remove the SysFreeString() call entirely, since now both Name and TimeStampIs are smart pointers and will auto-free on object destruction.
Like this
SysFreeString(pMess->Name);
But there is no good reason to use BSTR in code like this. Nor is there any good reason to be writing your own linked list class. Do it the easy way (as selbie pointed out this isn't the only error in your code), I would recommend std::wstring and std::list.
#include <string>
#include <list>
struct MESSAGE
{
int MessageType;
std::wstring Name;
std::wstring TimeStampIs;
};
class Message
{
private:
std::list<MESSAGE> queue;
public:
...
};
The big advantage is then you don't have to delete anything. So all those issues go away.
Change this line:
SysFreeString(pMess->Name.Detach());
To this:
SysFreeString(pMess->Name);
pMess->Name = NULL;
But that's not your only problem...
Also, this line is flat out wrong:
Message->Name=L"ABC";
You are assigning a WCHAR* to a BSTR. Which for all intents and purposes will work just fine until the moment you release it via SysFreeString. (Which could crash.)
Allocate your string as follows:
pMess->Name = SysAllocString("ABC");
The _bstr_t is a useful string class that internally holds a BSTR. It takes care of all the SysAllocString/SysFreeString calls for you when converting to/from native WCHAR* strings. So it would make sense to use it for Name just like you use it for TimeStampIs.