Porting a C++/CLI Project to .NET Core

Avatar

Mike

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:

  1. NativeApp. A C++ Windows app from Visual Studio’s ‘Windows Desktop Application’ template.
    1. This will be they app’s entry point.
    2. I’ve updated it to display the managed form (via the CppCliInterop project) and call a method on it when the IDM_ABOUT command is invoked.
  2. ManagedLibrary. A C# Windows Forms library targeting .NET Core.
    1. This will provide a WinForms form for the native app to display.
    2. 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.
  3. CppCliInterop. A .NET Framework C++/CLI Library.
      1. This will be used as the interop layer to connect the app to the managed WinForms library.
      2. It references ManagedLibrary and allows native projects to use it.
      3. 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.

  1. Replace <CLRSupport>true</CLRSupport> with <CLRSupport>NetCore</CLRSupport>. This tells the compiler to use /clr:netcore instead of /clr when building.
    1. This change can be done through Visual Studio’s project configuration interface if you prefer.
    2. 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.
  2. Replace <TargetFrameworkVersion>4.7</TargetFrameworkVersion> with <TargetFramework>netcoreapp3.1</TargetFramework>.
    1. 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.
  3. 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).
    1. <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.

C++/CLI scenario on .NET Core

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:

  1. Use /clr:netcore in place of /clr when calling cl.exe.
  2. Reference necessary .NET Core reference assemblies using /FU (.NET Core reference assemblies are typically installed under %ProgramFiles%\dotnet\packs\<SDK>\<Version>\ref).
  3. 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).
  4. 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.
  5. 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.

  1. C++/CLI support is Windows only, even when running on .NET Core. If you need interoperability cross-platform, use platform invokes.
  2. 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.
  3. 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.

 

11 comments

Comments are closed. Login to edit/delete your existing comments

  • Avatar
    Zbynek Zahradnik

    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

    • Avatar
      Mike RousosMicrosoft employee

      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 specifies which .NET Core version and framework is used. That’s also auto-generated now, though.

  • Avatar
    Zbynek Zahradnik

    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?

    • Avatar
      Mike RousosMicrosoft employee

      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.

      • Avatar
        Zbynek Zahradnik

        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?

  • Avatar
    David Hunter

    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.

  • Avatar
    Jean Gautier

    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