Quantcast
Channel: .NET – Marius Bancila's Blog
Viewing all articles
Browse latest Browse all 39

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

$
0
0

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 $(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 (34 downloads)


Viewing all articles
Browse latest Browse all 39

Trending Articles