In my projects I often see brown-field development environments, where the software under development consists of new components in a modern language as well as legacy components. The most often combination – in my experience – is C# on the top layer and C++/C on the bottom layer. Sometimes you will also find alas languages like Fortran or Cobol on the lowest layers, but those languages are a separate topic.
So how can you interact with the legacy C++/C with C# in your managed .NET runtime environment? C++/CLI is a beautiful possibility to create an intermediate layer to separate new software components from legacy software components.
In this post I show you how to interact using a StringBuilder object as example for a complex object, and a String object as example for a simple object. You might know that String objects in C#/.NET are not real reference types, nor value types, but contain a reference to a global string look-up table. String objects are also an interesting example, as .NET uses UNICODE as default string encoding, while C++/C use ASCII for string encoding in most cases.
Here’s a short example, in which the console output for both method calls of object doSomethingEfficient is “ABC”:
- The method SomeStringBuilderOperation uses the StringBuilder object directly within C++ via C++/CLI to retrieve/append strings.
- The method SomeStringOperation transfers the strings back and forth between C# and C++, and use the StringBuilder only in C# for additional operations (e. g. build some string for the UI).
C# example code:
using System.Text;
namespace StringBuilderToCppDemo
{
internal class StringBusinessLogic
{
internal void DoSomething()
{
DoSomethingEfficient doSomethingEfficient = new();
StringBuilder builder = new();
builder.Append("A");
builder.Append("B");
doSomethingEfficient.SomeStringBuilderOperation(builder);
Console.WriteLine(builder.ToString());
var secondString = doSomethingEfficient.SomeStringOperation("AB");
Console.WriteLine(secondString);
}
}
}
C++/CLI header example code:
using namespace System;
using namespace System::Text;
public ref class DoSomethingEfficient
{
public:
void SomeStringBuilderOperation(StringBuilder^ builder);
String^ SomeStringOperation(String^ managedString);
};
C++/CLI body example code:
#include <iostream>
#include <msclr/marshal_cppstd.h>
#include "DoSomethingEfficient.h"
void DoSomethingEfficient::SomeStringBuilderOperation(StringBuilder^ builder)
{
const char* unmanagedString = "C";
String^ managedString = gcnew String(unmanagedString);
builder->Append(managedString);
}
String^ DoSomethingEfficient::SomeStringOperation(String^ managedString)
{
std::string unmanagedString = msclr::interop::marshal_as<std::string>(managedString);
unmanagedString.append("C");
return gcnew String(unmanagedString.c_str());
}