One of the new features of Visual Studio 2019 (beginning with version 16.4) and .NET Core 3.1 is the ability to build C++/CLI projects targeting .NET Core. This can be done either directly with cl.exe and link.exe (using the new /clr:netcore
option) or via MSBuild (using <CLRSupport>NetCore</CLRSupport>
). In this post, I’ll walk through the steps necessary to migrate a simple C++/CLI interop project to .NET Core. More details can be found in .NET Core documentation.
The sample project
First, I need to make a sample solution to migrate. I’m going to use an app with a native entry point that displays a Windows Forms form via C++/CLI. Migrating a solution with a managed entry point interoperating with native dependencies via C++/CLI would be just as easy, though. To get started, I’ve created a solution with three projects:
- NativeApp. A C++ Windows app from Visual Studio’s ‘Windows Desktop Application’ template.
- This will be they app’s entry point.
- I’ve updated it to display the managed form (via the
CppCliInterop
project) and call a method on it when theIDM_ABOUT
command is invoked.
- ManagedLibrary. A C# Windows Forms library targeting .NET Core.
- This will provide a WinForms form for the native app to display.
- I’ve added a text box to the form and a method to set the text box’s text. I’ve also multi-targeted this project for .NET Core and .NET Framework so that it can be used with either. This way we can focus on migrating just the C++/CLI portion of the sample.
- CppCliInterop. A .NET Framework C++/CLI Library.
-
- This will be used as the interop layer to connect the app to the managed WinForms library.
- It references
ManagedLibrary
and allows native projects to use it. - This is the project that needs to be migrated to .NET Core.
-
The sample code is available on GitHub. When you start the app, if you click on the Help -> About menu, the WinForms form will be displayed with text in its text box supplied by the NativeApp project.
Migrating a vcxproj to .NET Core
Now for the interesting part – updating the sample app to run on .NET Core. The changes needed are actually quite minimal. If you’ve migrated C# projects to .NET Core before, migrating C++/CLI projects is even simpler because the project file format doesn’t change. With managed projects, .NET Core and .NET Standard projects use the new SDK-style project file format. For C++/CLI projects, though, the same vcxproj format is used to target .NET Core as .NET Framework.
All that’s needed is to make a few changes to the project file. Some of these can be done through the Visual Studio IDE, but others (such as adding WinForms references) can’t be yet. So the easiest way to update the project file, currently, is to just unload the project in VS and edit the vcxproj directly or to use an editor like VS Code or Notepad.
- ReplaceÂ
<CLRSupport>true</CLRSupport>
 withÂ<CLRSupport>NetCore</CLRSupport>
. This tells the compiler to useÂ/clr:netcore
 instead ofÂ/clr
 when building.- This change can be done through Visual Studio’s project configuration interface if you prefer.
- Note thatÂ
<CLRSupport>
 is specified separately in each configuration/platform-specific property group in the sample project’s project file, so the update needs to be made four different places.
- ReplaceÂ
<TargetFrameworkVersion>4.7</TargetFrameworkVersion>
 withÂ<TargetFramework>netcoreapp3.1</TargetFramework>
.- These settings can be modified through Visual Studio’s project configuration interface in the ‘Advanced’ tab. Note, however, that changing a project’s CLR support setting as described in the previous step won’t changeÂ
<TargetFrameworkVersion>
 automatically, so be sure to clear the “.NET Target Framework Version” setting before selecting .NET Core Runtime Support.
- These settings can be modified through Visual Studio’s project configuration interface in the ‘Advanced’ tab. Note, however, that changing a project’s CLR support setting as described in the previous step won’t changeÂ
- Replace .NET Framework references (to System, System.Data, System.Windows.Forms, and System.Xml) with the following reference to WinForms components from the Windows Desktop .NET Core SDK. This step doesn’t have Visual Studio IDE support yet, so it must be done by editing the vcxproj directly. Notice that only a reference to the Windows Desktop SDK is needed because the .NET Core SDK (which includes libraries like System, System.Xml, etc.) is included automatically. There are different Framework references for WinForms, WPF, or both (as explained in the migration docs).
-
<FrameworkReference Include="Microsoft.WindowsDesktop.App.WindowsForms" />
-
With those changes made, the C++/CLI project will build successfully targeting .NET Core. If you’re using the latest version of Visual Studio 2019 (16.5 or 16.6 preview 1), everything should work at runtime, too, and the migration is done!
Prior to Visual Studio 2019 16.5 preview 2, C++/CLI libraries didn’t generate the .runtimeconfig.json file necessary for C++/CLI libraries to indicate which version of .NET Core they use, so it had to be added manually. So, if you’re using an older version of Visual Studio, you’ll need to create this CppCliInterop.runtimeconfig.json file manually and make sure it’s copied to the output directory:
{
"runtimeOptions": {
"tfm": "netcoreapp3.1",
"framework": {
"name": "Microsoft.WindowsDesktop.App",
"version": "3.1.0"
}
}
}
The app can now run on .NET Core! A migrated version of the source is available in the NetCore branch in the sample’s GitHub repository. Here’s the Windows form running in front of the loaded modules showing coreclr.dll loaded.
Building without MSBuild
Migrating this sample app to .NET Core was simply a matter of updating the project file to target .NET Core instead of .NET Framework. If you need to build C++/CLI assemblies directly with cl.exe and link.exe, that’s supported, too. The necessary steps are:
- UseÂ
/clr:netcore
 in place ofÂ/clr
 when calling cl.exe. - Reference necessary .NET Core reference assemblies usingÂ
/FU
 (.NET Core reference assemblies are typically installed under %ProgramFiles%\dotnet\packs\<SDK>\<Version>\ref). - When linking, include the .NET Core app host directory as aÂ
LibPath
. The .NET Core app host files are typically installed under %ProgramFiles%\dotnet\packs\Microsoft.NETCore.App.Host.win-x64\<Version>\runtime\win-x64\native). - Make sure that ijwhost.dll (which is needed to start the .NET Core runtime) is copied locally from the .NET Core app host location. MSBuild does this automatically if building a vcxproj project.
- Create a .runtimeconfig.json file, as discussed previously.
A few caveats
As you can see, with Visual Studio 2019 and .NET Core 3.1, targeting .NET Core with C++/CLI projects is easy. There are a few C++/CLI limitations to look out for, though.
- C++/CLI support is Windows only, even when running on .NET Core. If you need interoperability cross-platform, use platform invokes.
- C++/CLI projects cannot target .NET Standard – only .NET Core or .NET Framework – and multi-targeting isn’t supported, so building a library that will be used by both .NET Framework and .NET Core callers will require two project files.
- If a project uses APIs that aren’t available in .NET Core, those calls will need to be updated to .NET Core alternatives. The .NET Portability Analyzer can help to find any Framework dependencies that won’t work on .NET Core.
Wrap-up and resources
Hopefully this sample shows how to take advantage of the new functionality in Visual Studio 2019 and .NET Core 3.1 to migrate C++/CLI projects to .NET Core. The following links may be useful for further reading.
- C++/CLI .NET Core migration docs
- The sample used in this post (the original sample is in the master branch and the .NET Core updates are in the netcore branch)
- .NET Portability Analyzer
Hi!
Thank you for your efforts porting C++/CLI to .Net Core.
That is great news for all C++ developers porting their exsiting code bases from .Net framework to .Net Core.
What is the story with CMake though?
Microsoft is supporting CMake more and more and I miss this aspect in your otherwise great post!
Thank you,
Jean
Hi, I just tried building a C++/CLI solution that works fine with netcoreapp3.1 with netcoreapp5.0 and was getting some weird link errors.
Should I be even trying this yet? If yes I will create some issues.
As far as I know, this should work. Please file some issues so we can investigate!
How about linux? how to link cpp and dotnet on linux ?
The best option for interoperating between managed and native on Linux is with pinvokes.
And an additional question. In .NET Framework, I needed to decide whether I wanted to generate 32-bit code or 64-bit code. If I wanted both, I had to create two project files. And I ended up with two mixed-mode assemblies. And there was no good way to ship them both and have the runtime decide which one will get used in the end. Is this all the same in .NET Core?
The 32-bit/64-bit story is pretty much the same in .NET Core as it was in .NET Framework. One option is to compile the mixed-mode library with two different names (.x86.dll vs .64.dll) and have the managed code reference them both and choose at runtime which to call into.
Thank you. Yes, that’s what I did in .NET Framework, but there were still issues. Some hosts (ASP.NET, namely) attempted to load all referenced assemblies upfront – which, of course, has failed because either the 32- or 64-bit assembly did not match the process bitness. Do you know whether this is different, let’s say, in Kestrel?
Looks good, I will try it out.
Are there any prerequisites for running the resulting assemblies? I mean, in .NET Framework, some VC++ redistributables had to be present, and they could not be linked in statically. How does this work in .NET Core?
Thank you
It's similar to the .NET Framework experience. The prerequisites for running are:
1. The correct .NET Core runtime needs to be present. This could be either a machine-wide install or shipped alongside your app.
2. ijwhost.dll needs to be distributed with your app (but the build tools will copy that into the output directory for you automatically)
3. If your C++/CLI library is called from a native entry point, you'll need a runtimeconfig.json file that...
Thank you. Unfortunately, the necessity of copying ijwhost.dll will make it impossible to create/consume NuGet packages with anything that uses C++/CLI, because of bug in NuGet (with PackageReference) that forgets to copy over explicitly listed files that are not referenced. See https://github.com/NuGet/Home/issues/7276#issuecomment-423029619 which still does not work.