2

I'm not familiar with C++/CLI so not sure how to free up the memory when using the code below (got the solution here and modified a little):

char* ManagedStringToUnmanagedUTF8Char( String^ s )
{
    array<unsigned char> ^bytes = Encoding::UTF8->GetBytes( s );
    pin_ptr<unsigned char> pinnedPtr = &bytes[0];
    return (char*)pinnedPtr;
}

The above code is working when I tested it by writing the char in a text file. Please let me know if I'm missing something (need to clean up pinnedPtr?).

Now when I use it:

char* foobar = ManagedStringToUnmanagedUTF8Char("testing");

//do something with foobar

//do I need to free up memory by deleting foobar here? 
//I tried 'delete foobar' or free(foobar) but it crashes my program
Community
  • 1
  • 1
Bruce
  • 23
  • 3
  • this is apparantly some language extension in Visual-C++ – moooeeeep Dec 14 '11 at 10:32
  • This cannot work, the array will be unpinned when the function returns. After which the garbage collector *can* move the array and make your program randomly fail. You have to copy it. – Hans Passant Dec 14 '11 at 13:05

2 Answers2

0

I'm not familiar with Visual-C++ either, but according to this article

Pinning pointers cannot be used as: [...] the return type of a function

I'm not sure whether the pointer will be valid when the function ends (even though it's disguised as a char*. It seems that you declare some local variables in the function that you want to pass to the calling scope. 'However, possibly these will be out of scope anyway when you return from the function. Maybe you should reconsider what you are trying to achieve in the first place?

Note, that in the article you have referenced a std::string (passed by value, i.e. by copy) is used as return parameter.

std::string managedStringToStlString( System::String ^s )
{
  Encoding ^u8 = Encoding::UTF8;
  array<unsigned char> ^bytes = u8->GetBytes( s );
  pin_ptr<unsigned char> pinnedPtr = &bytes[0];
  return string( (char*)pinnedPtr );
}

Thereby no local variables are passed out of their scope. The string is handled over by copy as an unmanaged std::string. This is exactly what this post suggests.

When you need a const char* later, you can use the string::c_str() method to get one. Note, that you can also write std::string to a file using file streams. Is this an option for you?

Community
  • 1
  • 1
moooeeeep
  • 31,622
  • 22
  • 98
  • 187
  • I used the function to write a UTF-8 character into text file and it worked everytime, so the pointer seems to be valid (luckily) when the function ends :) What I want to achieve is to create a function that converts managed .NET string to unmanaged char* (must be UTF-8) and my wrapper class (boundary between manage and unmanaged code) will use it a lot. I found many solutions on the net and this is the simplest one I can find.. – Bruce Dec 15 '11 at 01:09
  • Yes that is an option, can you teach me how to do that for my case? – Bruce Dec 15 '11 at 01:30
  • Ok I was wrong, I was lucky when the gc didn't collect it when I tested. I manually called System::GC::Collect() and it became some random characters in the calling scope. I guess I need to copy it but I don't know how.. – Bruce Dec 15 '11 at 03:43
  • I have added the suggestion from the question you have referenced. I think this solves your question as well. For the validity of local variables that are referenced beyond their scope, see [this very good post](http://stackoverflow.com/a/6445794/1025391). – moooeeeep Dec 15 '11 at 08:38
0

Hans Passant's comment is correct that the returned pointer to the buffer can be moved in memory by the garbage collector. This is because, when the function stack unwinds, pin_ptr will unpin the pointer.

The solution is to

  1. Obtain the System::String buffer and pin it so that the GC cannot move it.
  2. Allocate memory on the unmanaged heap (or just heap) where it is not under the GC's jurisdiction, and cannot be moved by the GC.
  3. Copy memory (and convert to desired encoding) from the System::String buffer to the buffer allocated on the unmanaged heap.
  4. Unpin the pointer so the GC can once again move the System::String in memory. (This is done when pin_ptr goes out of the function scope.)

Sample code:

char* ManagedStringToUnmanagedUTF8Char(String^ str)
{
    // obtain the buffer from System::String and pin it
    pin_ptr<const wchar_t> wch = PtrToStringChars(str);

    // get number of bytes required
    int nBytes = ::WideCharToMultiByte(CP_UTF8, NULL, wch, -1, NULL, 0, NULL, NULL);
    assert(nBytes >= 0);

    // allocate memory in C++ where GC cannot move
    char* lpszBuffer = new char[nBytes];

    // initialize buffer to null
    ZeroMemory(lpszBuffer, (nBytes) * sizeof(char)); 

    // Convert wchar_t* to char*, specify UTF-8 encoding
    nBytes = ::WideCharToMultiByte(CP_UTF8, NULL, wch, -1, lpszBuffer, nBytes, NULL, NULL);
    assert(nBytes >= 0);

    // return the buffer
    return lpszBuffer;
}

Now, when using:

char* foobar = ManagedStringToUnmanagedUTF8Char("testing");

//do something with foobar

//when foobar is no longer needed, you need to delete it
//because ManagedStringToUnmanagedUTF8Char has allocated it on the unmanaged heap.
delete foobar;
KTCheek
  • 309
  • 2
  • 4