I am using C++/CLI and I want to call the function WNetAddConnection2
from Windows Networking.
First, I know that C++/CLI is not the language of choice for my work, but I have no possibility to change that right now and e.g. use C# instead.
The problem now is, that this function takes wchar_t*, so I need to convert System::String^ to wchar_t*.
Solution 1): use pin_ptr
and PtrToSTringChars
from vcclr.h
Solution 2): use StringToHGlobalUni
. (The title mentions StringHToGlobalAnsi
because more people are searching for that so they might find this post and it's answers faster).
I have found out that both solutions work. But #1 does not really. I have put the WNet-functions into a ref class CWNetShare
with following constructor:
CWNetShare::CWNetShare (String^ i_sLocalDrive, ...) {
pin_ptr<const wchar_t> wszTemp;
wszTemp = PtrToStringChars(i_sLocalDrive);
m_wszLocalDrive = const_cast<wchar_t*>(wszTemp);
where m_wszLocalDrive
is a private CWNetShare
member of type wchar_t*
.
The real problem: while calling the constructor by m_oWNetShare = gcnew CWNetShare
from a Winform class constructor (I know, C++/CLI and Winforms...), everything seems fine. The string i_sLocalDrive
and others are converted and assigned correctly. But when accessing m_oWNetShare
later, the values in all m_wsz... variables are lost. It looks like the object was moved around by the GC.
Therefore I have made a test:
ref class CManaged {
public:
wchar_t* m_wszNothing;
wchar_t* m_wszPinned;
wchar_t* m_wszMarshal;
System::String^ m_sTest;
CManaged ()
{
m_sTest = "Hello";
m_wszNothing = L"Test";
pin_ptr<const wchar_t> wszTemp;
wszTemp = PtrToStringChars(m_sTest);
m_wszPinned = const_cast<wchar_t*>(wszTemp);
m_wszMarshal = static_cast<wchar_t*>(System::Runtime::InteropServices::Marshal::StringToHGlobalUni (m_sTest).ToPointer());
}
};
Again a winform with m_oManaged = gcnew CManaged;
in its constructor. When accessing m_oManaged later, then if m_oManaged was not moved, m_wszPinned
is ok.
But after GCing, it's showing nonsense. BUT m_wsznothing keeps it's value, so it's not a problem of wchar_t*
, but of the pin_ptr somehow. The address of m_oManaged
has changed, but the address of m_wszPinned
is the same, so why is the value lost then?
What is going wrong here?
Does pin_ptr and PtrToSTringChars have a use at all then?
I'm using marshalling now, which works.
PtrToStringChars is literally that: a pointer to the character array that the String^ holds internally.
When you're saving that pointer, it's a pointer to a managed object that the garbage collector is allowed to move. You're only guaranteed that it won't move for as long as the pin_ptr exists, which you're not keeping around. As soon as the pin_ptr no longer exists, the garbage collector is free to move the managed object around, and your pointer now points at some other object, somewhere in the managed heap.
Use PtrToStringChars if you're going to call an unmanaged function, and you don't need the string to persist beyond that one API call (and the unmanaged function doesn't keep a reference to the string). Use StringToHGlobalUni if you need to keep the unmanaged string around long-term.