.NET Data Exchange with Legacy Components using C++/CLI

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());
}