C++/CLI
First of all, C++/CLI is a .NET language. You can write (managed) classes and assemblies, and they can be used by other .NET languages, C#, VB.NET etc, or in the opposite direction.
Second, C++/CLI has a close relationship with C++. C++/CLI is a different language than C++, instead, it is more like a superset of C++. In C++/CLI, you can mix unmanaged C++ with managed C++.
I guess writing a new assembly with C++/CLI may not be very favorable, given the complexity of C++ and hence C++/CLI. However, C++/CLI provides a prime opportunity to wrap up legacy C++ code as .NET assemblies, and therefore to be shared with the .NET world.
You can certainly wrap up unmanaged work into ActiveX/COM controls, but COM is so complex and there are many places that can go wrong, and the types can go through the COM interface is often limited to Automation compatible types. For example, often you can not pass by a class object, but you will have to use a generic IDispatch interface, which is inconvenient.
The other approach is to use P/Invoke for unmanaged C DLLs. Again, you have to wrap your type-rich C++ library into flat pure C DLLs. All classes are gone.
With C++/CLI, you have a lot of control on how the C++ class library is exposed to .NET. There is still the bridging code, but it works at a much finer level if you want, and restrictions are minimal.
Example
To create a C++/CLI class library, in your Visual Studio 2010, choose File | New | Project… | Visual C++ | CLR | Class Library. The created project, TestCppCli, is similar to a regular C++ project, but /clr option is added, and references to a few system assemblies are added to the project.
In this example, I will show how to pass a .NET string to an unmanaged function call. This example is an adaptation of How to convert from System::String* to Char* in Visual C++ at Microsoft Support. Note that other fundamental types, such as int, float etc., are quite effortless. Also, passing back a .NET string is relatively easy, because it is a managed object created in managed heap.
This is the header file with inline functions (the cpp file is empty):
1 // TestCppCli.h
2 #pragma once
3
4 #include <afx.h> // CString
5 #include <vcclr.h> // PtrToStringChars
6 #include <msclr/marshal.h> // marshal_as, marshal_context
7 #include <windows.h> // MessageBox
8
9 namespace TestCppCli {
10 public ref class Foo
11 {
12 public:
13 void MessageBox0(System::String^ s)
14 { // Not recommened.
15 // 1. exception-unsafe. If you have to use it, use a smart pointer (shared_ptr, auto_ptr etc) to wrap it.
16 // 2. concrete character type. Need to use template such that it's character type agnostic to client.
17 char* ss = (char*)(void*)System::Runtime::InteropServices::Marshal::StringToHGlobalAnsi(s);
18 ::MessageBoxA(0, ss, "Title", 0);
19 System::Runtime::InteropServices::Marshal::FreeHGlobal(System::IntPtr(ss));
20 }
21
22 void MessageBox1(System::String^ s)
23 {
24 CString ss(s); // requires MFC; agnostic to client's Unicode/non-Unicode setting.
25 // wide string obtained using efficient PtrToStringChars; a copy (Unicode) or convertion (Ansi) is then made.
26 ::MessageBox(0, ss, _T("Title"), 0);
27 }
28
29 void MessageBox2(System::String^ s)
30 {
31 pin_ptr<const wchar_t> ss = PtrToStringChars(s); // requires <vcclr.h>; only wide string; efficient
32 ::MessageBoxW(0, ss, L"Title", 0);
33 }
34
35 void MessageBox3(System::String^ s)
36 { // requires <msclr/marshal.h> for marshal_as/marshal_context
37 // context can live in an outer scope and be shared.
38 msclr::interop::marshal_context^ context = gcnew msclr::interop::marshal_context();
39 LPCTSTR ss = context->marshal_as<LPCTSTR>(s);// ss in context until context is destroyed. agnostic to Unicode/non-Unicode
40 ::MessageBox(0, ss, _T("Title"), 0);
41 delete context;
42 } // Marshal::StringToHGlobalUni or Marshal::StringToHGlobalAnsi used in implementation, so always a cost there.
43 };
44 }
As pointed out in the comment of MessageBox0, using StringToHGlobalAnsi directly is not exception safe, because FreeHGlobal may not be called if exception happens. This is an attempt to wrap them:
1 template<class Char_>
2 class NativeConstString
3 {
4 // this is how you use it.
5 public:
6 NativeConstString(System::String^ s);
7 ~NativeConstString() { System::Runtime::InteropServices::Marshal::FreeHGlobal(System::IntPtr(p_)); }
8 operator const Char_* () { return p_; }
9
10 // no copy or assignment
11 private:
12 NativeConstString(const NativeConstString&);
13 NativeConstString& operator = (const NativeConstString&);
14
15 private:
16 Char_* p_;
17 };
18
19 template<> NativeConstString<char>::NativeConstString(System::String^ s)
20 : p_( (char*)(void*)System::Runtime::InteropServices::Marshal::StringToHGlobalAnsi(s) )
21 {}
22
23 template<> NativeConstString<wchar_t>::NativeConstString(System::String^ s)
24 : p_( (wchar_t*)(void*)System::Runtime::InteropServices::Marshal::StringToHGlobalUni(s) )
25 {}
26
27 /*
28 template<>
29 class NativeConstString<wchar_t>
30 {
31 public:
32 NativeConstString(System::String^ s)
33 : p_( PtrToStringChars(s) )
34 {} // better than System::Runtime::InteropServices::Marshal::StringToHGlobalUni ?
35 ~NativeConstString() {}
36 operator const wchar_t* () { return p_; }
37 private:
38 pin_ptr<const wchar_t> p_; // !!FAIL. pin_ptr cannot be used a member of class; it must be a stack variable.
39 };
40 */
And here is a usage example of NativeConstString:
1 void MessageBox4(System::String^ s)
2 {
3 NativeConstString<TCHAR> ss(s);
4 ::MessageBox(0, ss, _T("Title"), 0);
5 }
Summary
This is a table to compare different approaches to convert a managed System::String to a unmanaged const C/C++ string:
| Approach |
Requires |
Char Support for Client |
Pros & Cons |
Implementation |
StringToHGlobalAnsi
StringToHGlobalUni |
System::Runtime::
InteropServices::Marshal |
explicit concrete types: char or wchar_t |
new copy created; exception unsafe in cleanup |
Make a copy of managed string in unmanaged heap |
| CString |
MFC/ATL String |
TCHAR (agnostic to project setting); or explicit char/wchar_t |
new copy created; auto cleanup |
PtrToStringChars (little cost), then make a copy (wchar_t) or convert to ansi (char) |
| PtrToStringChars |
#include <vcclr.h> |
wchar_t only |
efficient string pinning; auto cleanup |
Pin managed string until pin_ptr destructs. |
| marshal_as |
#include <msclr/marshal.h> |
LPCTSTR(agnostic to project setting); or explicit char/wchar_t |
new copy created; one-time context setup and teardown; new copy cannot be freed before context is destroyed; |
StringToHGlobalXXXX. New copies are created and added in a context (container) |
| NativeConstString |
System::Runtime::
InteropServices::Marshal |
TCHAR (agnostic to project setting); or explicit char/wchar_t |
new copy created; auto cleanup |
smart pointer around StringToHGlobalXXXX |
References:
Chapters 22, 23, 24, Pro Visual C++_CLI and the .NET 3.5 Platform
Chapters 8, 9, Expert C++/CLI
How to convert from System::String* to Char* in Visual C++
Sample: Mixing Unmanaged C++, C++/CLI, and C# code at Junfeng Zhang’s Windows Programming Notes
How do I mix C# and C++ code in a single assembly? at Junfeng Zhang’s Windows Programming Notes
MSDN: How to: Convert Between Various String Types