This article presents a C++/CLI class StringConvertor which can be used to make conversions between System::String and native types like a char*, BSTR etc. The class keeps track of allocated unmanaged memory handles and frees them automatically, so the caller needn't worry about freeing up unmanaged resources.

The class has several constructor overloads that accept various native types (as well as managed types) like char*, __wchar_t* etc and internally maintains a System::String object that can be extracted using the String operator or the ToString override. There are also various properties each exposing a native type like char*, BSTR etc which can be used to convert the string to your preferred unmanaged type.

The class reference follows with suitable examples. Make sure you read the Class usage tips section at the end though.

char* NativeCharPtr

Returns a char* (allocated on the native heap). The class handles cleaning up of this memory.

char* p3 = sc1.NativeCharPtr;
printf("%s \r\n", p3);

__wchar_t* NativeWideCharPtr

Returns a __wchar_t* (allocated on the native heap). The class handles cleaning up of this memory.

__wchar_t* p4 = sc1.NativeWideCharPtr;
printf("%S \r\n", p4);

Note - I've included a generic text mapping, NativeTCharPtr, for these two properties so you can use it with an LPTSTR. Since it's a #define, intellisense will not detect it. NativeTCharPtr maps to NativeWideCharPtr if _UNICODE is defined, else to NativeCharPtr.

array<Char>^ CharArray

BSTR BSTRCopy

Returns a BSTR allocated natively (unmanaged heap). The class handles cleaning up of this memory.

BSTR b1 = sc5.BSTRCopy;
printf("BSTR contains %S \r\n", b1);

Each call creates a new BSTR, and if you change the contents of the returned BSTRs, newly returned BSTRs will contain the original string; for example, in the below snippet, b1 and b2 are not the same string any longer.

BSTR b1 = sc5.BSTRCopy;
b1[0] = L'X';
BSTR b2 = sc5.BSTRCopy;

If for any reason, you want to change the string represented by the StringConvertor, use the InteriorCharPtr property.

std::string STLAnsiString

Returns an std::string object.

std::string s1 = sc5.STLAnsiString;
printf("%s \r\n", s1.c_str());

std::wstring STLWideString

Two of the primary purposes of the class are (1) to make it easy to convert from System::String to unmanaged types and vice-versa and (2) to free the user from the responsibility of freeing up the unmanaged memory allocations. The various properties and constructors handle the first role and the destructor handles the second one. When I say destructor, I mean destructor (not the finalizer), which means the destructor needs to get called as soon as the object goes out of scope or has served its purpose. For that, it's recommended that you either use the auto-variable non-handle declaration format or explicitly call delete on the StringConvertor object after use.

Try to avoid making a StringConvertor a member of a long-living class (or better still, never make it a class-member). Always try and limit the scope and life-time of StringConvertor objects. While, a finalizer has been provided (for unanticipated circumstances), ideally, the finalizer should never need to be called. To help you with avoiding this, in debug builds, an exception gets thrown if the finalizer is ever called and in release builds, Trace::WriteLine is used to write a warning message to any active trace listeners.

When you have functions returning unmanaged strings (like a char*), you need to make sure that the pointer you return is allocated by you separately (since the pointer returned by StringConvertor will be freed when the StringConvertor goes out of scope. The snippets below show two functions, one returning a BSTR and the other returning a char*.

Most of the data conversions make use of the Marshal class from the System::Runtime::InteropServices namespace. Now most of these functions allocate memory in the native heap and the class uses two vector<> objects to keep track of all allocated memory handles. One vector<> stores all the pointers that need to be freed using Marshal::FreeHGlobal while the other one stores all the BSTR pointers that need to be freed using Marshal::FreeBSTR. The initialization and clean-up of these vectors is done by the StringConvertorBase class which is an abstractref class that's the base class for the StringConvertor class.

Initially, I had designed the class so that the unmanaged pointers are cached, so that multiple calls to any of the properties that returned pointers to the native heap always returned the cached pointer. But I soon saw that this meant that, if the caller modifies data using one of these pointers, it dirties the internal state of the StringConvertor object which was obviously something that should not be allowed. Considering that, managed-unmanaged transitions should not be over-used and that in most frequent situations, the required life-time of the unmanaged data is very small, usually for making a function call, I assumed that any user of this class with any level of adequacy as a programmer would never abuse the class to such an extent that there'd be lots of unmanaged memory lying un-freed. So the vectors and the unmanaged pointers/handles they contain are freed up in the class destructor (as well as in the finalizer as a safety measure).

As earlier mentioned, if for any reason, you want to change the internal string of the StringConvertor object, use the InteriorCharPtr property and write to the buffer directly - but be very careful how you use it.

Why I left CString out?

Conversions to CString and from CString are so trivial, that it didn't make sense to add them to this class.

I believe I've covered all the basic data types and that these types can be used to create any other more complex data types. For example, the BSTR can be used to make a CComBSTR or a _bstr_t, while the char* (or __wchar_t*) can be used to make a CString.

Share

About the Author

Nish is a real nice guy who has been writing code since 1990 when he first got his hands on an 8088 with 640 KB RAM. Originally from sunny Trivandrum in India, he has been living in various places over the past few years and often thinks it’s time he settled down somewhere.

Nish has been a Microsoft Visual C++ MVP since October, 2002 - awfully nice of Microsoft, he thinks. He maintains an MVP tips and tricks web site - www.voidnish.com where you can find a consolidated list of his articles, writings and ideas on VC++, MFC, .NET and C++/CLI. Oh, and you might want to check out his blog on C++/CLI, MFC, .NET and a lot of other stuff - blog.voidnish.com.

When I first tried to include the header file for this class in a CLR project, it said it couldn't find wtypes.h, nor windef.h. So I went out and downloaded these and put them in my include directory. Now when I try to compile, I get about 25 errors. Are there particular wtypes.h and windef.h files I need for VC++.NET 2005?