How to port a C++/CLI project to .NET Core or .NET 5
Beginning with .NET Core 3.1 and Visual Studio 2019, C++/CLI projects can target .NET Core. This support makes it possible to port Windows desktop applications with C++/CLI interop layers to .NET Core/.NET 5+. This article describes how to port C++/CLI projects from .NET Framework to .NET Core 3.1.
C++/CLI .NET Core limitations
There are some important limitations to porting C++/CLI projects to .NET Core compared to other languages:
- C++/CLI support for .NET Core is Windows only.
- C++/CLI projects can't target .NET Standard, only .NET Core (or .NET Framework).
- C++/CLI projects don't support the new SDK-style project file format. Instead, even when targeting .NET Core, C++/CLI projects use the existing vcxproj file format.
- C++/CLI projects can't multitarget multiple .NET platforms. If you need to build a C++/CLI project for both .NET Framework and .NET Core, use separate project files.
- .NET Core doesn't support
-clr:pure
or-clr:safe
compilation, only the new-clr:netcore
option (which is equivalent to-clr
for .NET Framework).
Port a C++/CLI project
To port a C++/CLI project to .NET Core, make the following changes to the .vcxproj file. These migration steps differ from the steps needed for other project types because C++/CLI projects don't use SDK-style project files.
- Replace
<CLRSupport>true</CLRSupport>
properties with<CLRSupport>NetCore</CLRSupport>
. This property is often in configuration-specific property groups, so you may need to replace it in multiple places. - Replace
<TargetFrameworkVersion>
properties with<TargetFramework>netcoreapp3.1</TargetFramework>
. - Remove any .NET Framework references (like
<Reference Include="System" />
). .NET Core SDK assemblies are automatically referenced when using<CLRSupport>NetCore</CLRSupport>
. - Update API usage in .cpp files, as necessary, to remove APIs unavailable to .NET Core. Because C++/CLI projects tend to be fairly thin interop layers, there are often not many changes needed. You can use the .NET Portability Analyzer to identify unsupported .NET APIs used by C++/CLI binaries just as with purely managed binaries.
WPF and Windows Forms usage
.NET Core C++/CLI projects can use Windows Forms and WPF APIs. To use these Windows desktop APIs, you need to add explicit framework references to the UI libraries. SDK-style projects that use Windows desktop APIs reference the necessary framework libraries automatically by using the Microsoft.NET.Sdk.WindowsDesktop
SDK. Because C++/CLI projects don't use the SDK-style project format, they need to add explicit framework references when targeting .NET Core.
To use Windows Forms APIs, add this reference to the .vcxproj file:
<!-- Reference all of Windows Forms -->
<FrameworkReference Include="Microsoft.WindowsDesktop.App.WindowsForms" />
To use WPF APIs, add this reference to the .vcxproj file:
<!-- Reference all of WPF -->
<FrameworkReference Include="Microsoft.WindowsDesktop.App.WPF" />
To use both Windows Forms and WPF APIs, add this reference to the .vcxproj file:
<!-- Reference the entirety of the Windows desktop framework:
Windows Forms, WPF, and the types that provide integration between them -->
<FrameworkReference Include="Microsoft.WindowsDesktop.App" />
Currently, it's not possible to add these references using Visual Studio's reference manager. Instead, update the project file by editing it manually. In Visual Studio, you'll need to unload the project first. You can also use another editor like Visual Studio Code.
Build without MSBuild
It's also possible to build C++/CLI projects without using MSBuild. Follow these steps to build a C++/CLI project for .NET Core directly with cl.exe and link.exe:
- When compiling, pass
-clr:netcore
to cl.exe. - Reference necessary .NET Core reference assemblies.
- When linking, provide the .NET Core app host directory as a
LibPath
(so that ijwhost.lib can be found). - Copy ijwhost.dll (from the .NET Core app host directory) to the project's output directory.
- Make sure a runtimeconfig.json file exists for the first component of the application that will run managed code. If the application has a managed entry point, a
runtime.config
file will be created and copied automatically. If the application has a native entry point, though, you need to create aruntimeconfig.json
file for the first C++/CLI library to use the .NET Core runtime.
Known issues
There are a few known issues to look out for when working with C++/CLI projects that target .NET Core 3.1 or .NET 5+:
A WPF framework reference in .NET Core C++/CLI projects currently causes some extraneous warnings about being unable to import symbols. These warnings can be safely ignored and should be fixed soon.
If the application has a native entry point, the C++/CLI library that first executes managed code needs a runtimeconfig.json file. This config file is used when the .NET Core runtime starts. C++/CLI projects don't create
runtimeconfig.json
files automatically at build time yet, so the file must be generated manually. If a C++/CLI library is called from a managed entry point, then the C++/CLI library doesn't need aruntimeconfig.json
file (since the entry point assembly will have one that is used when starting the runtime). A simple sampleruntimeconfig.json
file is shown below. For more information, see the spec on GitHub.{ "runtimeOptions": { "tfm": "netcoreapp3.1", "framework": { "name": "Microsoft.NETCore.App", "version": "3.1.0" } } }
On Windows 7, loading a .NET Core C++/CLI assembly when the entry application is native may exhibit failing behavior. This failing behavior is due to the Windows 7 loader not respecting non-
mscoree.dll
entry points for C++/CLI assemblies. The recommended course of action is to convert the entry application to managed code. Scenarios involving Thread Local Storage (TLS) are specifically unsupported in all cases on Windows 7.C++/CLI assemblies may be loaded multiple times, each time into a new AssemblyLoadContext. If the first time that managed code in a C++/CLI assembly is executed is from a native caller, the assembly is loaded into a separate
AssemblyLoadContext
. If the first time that managed code is executed is from a managed caller, the assembly is loaded into the sameAssemblyLoadContext
as the caller (usually the default). To always load your C++/CLI assembly into the defaultAssemblyLoadContext
, add an "initialize" style call from your entry-point assembly to your C++/CLI assembly. For more information, see this dotnet/runtime issue.This limitation is removed starting in .NET 7. C++/CLI assemblies that target .NET 7 or a later version are always loaded into the default
AssemblyLoadContext
.
Feedback
Submit and view feedback for