C++/CLI projects targeting .NET Core 3.x

The .NET Core framework version 3.1 was released earlier this month, alongside with Visual Studio 2019 16.4 (which you must install in order to use .NET Core 3.1). Among the changes, it includes support for C++/CLI components that can be used with .NET Core 3.x, in Visual Studio 2019 16.4. However, not everything works out of the box. In this article, I will show how you can create and consume C++/CLI components targeting .NET Core 3.1.

Prerequisites

You need to have to following:

  • Visual Studio 2019 16.4 (or a newer update; as of now, the latest update is 16.4.2)
  • Desktop development with C++ workload
  • C++/CLI support individual component

When you install this component you get two new project templates:

  • CLR Class Library (.NET Core)
  • CLR Empty Project (.NET Core)

Creating a C++/CLI component

To demonstrate how the whole thing works, let us create a CLR Class Library (.NET Core) project (that we will called CoreClassLib). In this class library, we will create a C++/CLI class called Display that contains a single method Print() that simply prints a message to the console.

#pragma once

namespace CoreClassLib 
{
	ref class Display
	{
	public:
		void Print(System::String^ message);
	};
}
#include "Display.h"

namespace CoreClassLib
{
   void Display::Print(System::String^ message)
   {
      System::Console::WriteLine(message);
   }
}

Then, we will create a C++ class, called Widget that has a single method called WriteText() that is supposed to print the message (to the console). To do so, it uses an instance of the Display class. The Widget is exported from the project, so it can be consumed from other modules. In order to be able to compile it, the mixed-managed components must not be part of the declaration of the Widget class. For this purpose, we use the PIMPL idiom below.

#pragma once
#include <string>

#ifdef DLL_EXPORT
#define DLL_API __declspec(dllexport)
#else
#define DLL_API __declspec(dllimport)
#endif

class WidgetImpl;

class DLL_API Widget
{
   WidgetImpl* impl;   

public:
   Widget();
   ~Widget();

   void WriteText(std::string const& message);
};
#include "Widget.h"
#include "Display.h"
#include <vcclr.h>

class WidgetImpl
{
   gcroot<CoreClassLib::Display^> display;
public:
   WidgetImpl()
      :display(gcnew CoreClassLib::Display())
   {
   }

   void WriteText(std::string const& message)
   {
      display->Print(gcnew System::String(message.c_str()));
   }
};

Widget::Widget()
   :impl(new WidgetImpl())
{
}

Widget::~Widget()
{
   delete impl;
}

void Widget::WriteText(std::string const& message)
{
   impl->WriteText(message);
}

To compile this as a mixed-mode module targeting .NET Core 3.1 you need the following settings in the project properties:

  • .NET Core Runtime Support (/clr:netcore) for Common Language Runtime Support
  • netcoreapp3.1 for .NET Core Target Framework

You can see these settings in the image below:

Consuming the mixed-mode component from a native application

To consume the exported Widget class that is using the C++/CLI Display class, we can create a C++ Console application with the following code (of course, you need to properly setup the additional include directories and additional library directories so it can locate the headers and .lib file).

#include "Widget.h"

int main()
{
    Widget widget;
    widget.WriteText("Hello, world!");
}

Although this compiles without errors, when you run it, you get the following exception:

The reason for this is that .NET Core requires a file called <appname>.runtimeconfig.json to accompany every .NET Core module. This file is used to define required shared frameworks and runtime options. You can read more about this file here:

The problem is that this file, which should be called CoreClassLib.runtimeconfig.json for our example, is not generated by Visual Studio when creating the CLR Class Library project. This is a know bug, as reported here: C++/CLI projects do not generate .runtimeconfig.json.

We can fix this by creating the CoreClassLib.runtimeconfig.json file with the following content.

{
  "runtimeOptions": {
    "framework": {
      "name": "Microsoft.NETCore.App",
      "version": "3.1.0"
    }
  }
}

However, this file must be available in the output folder. That can be done by copying it as a Post-build event.

xcopy /Y $(ProjectDir)CoreClassLib.runtimeconfig.json $(OutputPath)

After making these changes, the C++ console application works as expected.

You can get the demo application for this article from here:
Download: cppcli_core.zip (2666 downloads )

9 Replies to “C++/CLI projects targeting .NET Core 3.x”

  1. Excellent piece, have you tried Visual C++/CLI in .Net Core 3.1, or is still not possible?

  2. Hi Marius, it was more the “Visual” I was asking about. Your article is an excellent introduction to a called C++/CLI assembly in .Net Core 3.1. I’m curious to know if you had any success with a stand alone Visual C++/CLI form based app using it.

  3. Apologies if this appears twice – posting issue. While this is a great piece on a called C++/CLI, I’m curious to know if you tried a complete Visual C++/CLI app using forms in .NET Core 3.1 – I could not see how to.

  4. No, not really, because “error NETSDK1116: C++/CLI projects targeting .NET Core must be dynamic libraries.”
    On the other hand, creating a DLL with the Win Forms and loading that from a native app fails with an unhandled exception “System.BadImageFormatException: Could not load file or assembly ‘System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089′”, even though I am referencing the DLL from c:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App\3.1.0\.

  5. Great stuff!! Was very helpful to me. One minor correction to the supplied example: the xcopy in the post-build event shall have /Y flag to be able overwriting the existing file. Other than that a clear and great example. One question, though. If the .NET Core has other dependencies, do you know if/how that would work?

  6. Sorry, asked a wrong question. Could we use the same approach to consume C# libraries?

  7. Thanks a lot for writing this! It was the final piece of the puzzle in my (several week) struggle to understand how this works 🙂

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.