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:
- .NET Core run-time configuration settings
- Deep-dive into .NET Core primitives, part 3: runtimeconfig.json in depth
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 )
Excellent piece, have you tried Visual C++/CLI in .Net Core 3.1, or is still not possible?
Yes, .NET Core 3.1 is what I discussed about in this article.
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.
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.
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\.
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?
Thanks for the hint with xcopy. As for .NET Core, what do you mean with dependencies?
Sorry, asked a wrong question. Could we use the same approach to consume C# libraries?
Thanks a lot for writing this! It was the final piece of the puzzle in my (several week) struggle to understand how this works 🙂