I am having an issue std::string assignment with the below code in VS2019, where it is working fine in VS2015. I am using Visual Studio 2019 (v142) as Platform Toolset in VS2019 and Visual Studio 2015 (v140) as Platform Toolset in VS2015.
class Test
{
public:
Test()
{
m_DisplayName = "";
m_Type = "";
}
std::string m_DisplayName;
std::string m_Type;
};
void CMFCApplication2Dlg::TestFunction()
{
// Sample Data setting in vector
std::vector<Test> vTest;
Test obj;
obj.m_DisplayName = "Nam1gd";
obj.m_Type = "t1";
vTest.emplace_back(obj);
Test obj2;
obj2.m_DisplayName = "Nam2";
obj2.m_Type = "t2";
vTest.emplace_back(obj2);
VARIANT vtResult_o;
VariantInit(&vtResult_o);
vtResult_o.vt = VT_ARRAY | VT_UI1;
int usrCount = vTest.size();
ULONG nBufferSize = usrCount * sizeof(Test);
// Define a safe array of usrCount Item and Starting index as 0
SAFEARRAYBOUND safeBounds = { nBufferSize, 0 };
//Create the Safe Array passing it the bounds
SAFEARRAY* pSafeArray = SafeArrayCreate(VT_UI1, 1, &safeBounds);
Test* vTestArray = nullptr;
SafeArrayAccessData(pSafeArray, (void**)&vTestArray);
// Data setting to Safe array
for (int nIdx = 0; nIdx < usrCount; nIdx++)
{
// ******VALUES ASSIGNING CORRECTLY in VS 2015 ******/
// ******JUNK VALUE COMES IN THIS ASSIGNMENT in VS 2019 ******/
vTestArray[nIdx].m_DisplayName = vTest[nIdx].m_DisplayName;
vTestArray[nIdx].m_Type = vTest[nIdx].m_Type;
}
/* decrement the lock count*/
SafeArrayUnaccessData(pSafeArray);
vtResult_o.parray = pSafeArray;
}
Since the string value in the vTest[nIdx].m_DisplayName is small it is stored in the small buffer in s._Bx._Buf. In VS2015 while assigning, the small buffer in the source string is copied to the destination string's small buffer.
for (int nIdx = 0; nIdx < usrCount; nIdx++)
{
// ******VALUES ASSIGNING CORRECTLY in VS 2015 ******/
// ******JUNK VALUE COMES IN THIS ASSIGNMENT in VS 2019 ******/
vTestArray[nIdx].m_DisplayName = vTest[nIdx].m_DisplayName;
vTestArray[nIdx].m_Type = vTest[nIdx].m_Type;
}
However, in VS2019, it is observed that the during assignment the destination's buffer ( s._Bx._Buf) is updated with junk value and the actual value is updated to the heap s._Bx._ptr.
Ideally since s._Bx is a union either the s._Bx._Buf or s._Bx._ptr should be present. Please find the image below.

[Workaround]
However if I do a std::string casting in VS2019 it is assigning the std::string's small buffer in the source to the small buffer in the destination.
for (int nIdx = 0; nIdx < usrCount; nIdx++)
{
vTestArray[nIdx].m_DisplayName =(std::string)(vTest[nIdx].m_DisplayName);
vTestArray[nIdx].m_Type = (std::string)(vTest[nIdx].m_Type);
}
I will appreciate if some one can help me to understand why this difference in VS2015 and VS2019!
You are treating the array you obtained as if it contained alive
Testobjects, which it doesn't. That causes undefined behavior.Instead of assigning directly, which pretends that there is already an alive object, you should use placement-new to create and start the lifetime of the
Testobject explicitly first:and don't forget to destroy the objects when you are done with them with explicit destructor calls:
(Technically there might be some minor issue here requiring some additional
std::laundercall onvTestArrayif we read the standard strictly andSafeArrayAccessDatawould not be considered to produce a "pointer to a suitable created object" under the C++20 implicit object creation rules.)There is also a more minor issue in that the cast in
SafeArrayAccessData(pSafeArray, (void**)&vTestArray);is wrong. You cannot reinterpret a pointer toTest*as a pointer tovoid*. That is an aliasing violation. You should use an intermediate pointer of the correct type:(Please note that there should not be a
std::laundercall here against my advice in a previous edit, since there is noTestobject in its lifetime yet.)I am also not sure what the intended use of the array is, but you should be aware that the strings will generally allocate additional memory that is not part of the array. So this is not a reliable way to e.g. share memory between processes.
But if you don't have some specific use case in mind (and I might just not be aware here of the obvious if it is Microsoft-specific) then there would be no reason to make it so complicated when
std::vector, potentially reference-counted viastd::shared_ptr, already does all of this automatically.Doing it manually in the way I suggested is also not exception-safe, which a
std::vectorwould be. If an exception is thrown during construction of one of theTestobjects, then you will forget the destruction of the already-constructedTestobjects with this naive implementation.