pin_ptr & PtrToStringChars vs. StringToHGlobalAnsi: Why does PtrToStringChars var loose its value?

5.7k views Asked by At

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.
enter image description here
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?
enter image description here

What is going wrong here?
Does pin_ptr and PtrToSTringChars have a use at all then?

I'm using marshalling now, which works.

1

There are 1 answers

1
David Yaw On BEST ANSWER

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.