Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use the Windows Runtime to implement a C++ API using C#?

I'm working with a native C++ app which has a plugin system where it will LoadLibrary()/GetProcAddress() on a .dll to call functions. I'd like to implement one of these plugins, and inside the plugin I'd like to use a persistence framework to save/load a complicated object graph to disk.

It seems like the most-supported ORM on Windows is Entity Framework, and this page says that Entity Framework Core is the most modern flavor. This page seems to say that, in order to use Entity Framework Core, your data model has to be written in C#.

So, it sounds like I'll have to call C# from C++. Luckily, it seems like Entity Framework Core supports the Universal Windows Runtime, which is supposed to make calling between languages easy.

This presentation seems to say that interacting with the Universal Windows Runtime from C++ involves creating a Windows Runtime Component, and a compilation step can generate glue code from the Windows Runtime Component's .winmd file.

So, I can make a Visual Studio solution that includes two projects: An outer "C++ Windows Desktop Dynamic-Link Library" project, and an inner "C# Universal Windows Runtime Component."

Outer C++ Windows Desktop Dynamic-Link Library Inner C# Universal Windows Runtime Component

The docs then say to "reference the Windows Runtime component's Windows Runtime metadata (.winmd) file, and build." However, when I add a reference in the outer project, I get an error when I try to select the inner project, and there is no option to browse to select an arbitrary .winmd.

Error when trying to add a reference to inner project

This error occurs even if I go into the Solution Manager and delete all the platforms except x86 in both projects.

This is surprising because this Windows Blog post explicitly says that native code is supposed to be able to call UWP code by adding a reference to the .winmd (though the post uses the older C++/CX syntax).

If I start over and use a "C++/WinRT Windows Runtime Component" instead of the "C++ Windows Desktop Dynamic-Link Library" project, I still have problems.

Outer C++/WinRT Windows Runtime Component

If I do this, I can add the reference just fine.

Successful Reference

However, when I try to build, I get a build failure.

1>------ Build started: Project: InnerCSharp, Configuration: Debug x86 ------
1>  InnerCSharp -> C:\Users\lithe\source\repos\EntityFrameworkInsideC++\InnerCSharp\bin\x86\Debug\InnerCSharp.winmd
2>------ Build started: Project: OuterC++WinRT, Configuration: Debug Win32 ------
2>MIDLRT Processing C:\Users\lithe\source\repos\EntityFrameworkInsideC++\OuterC++WinRT\Class.idl
2>Class.idl
2>MIDLRT Processing C:\Program Files (x86)\Windows Kits\10\Include\10.0.17134.0\winrt\winrtbase.idl
2>winrtbase.idl
2>MIDLRT Processing C:\Program Files (x86)\Windows Kits\10\Include\10.0.17134.0\winrt\midlbase.idl
2>midlbase.idl
2>Processing WinMD c:\users\lithe\source\repos\entityframeworkinsidec++\innercsharp\bin\x86\debug\innercsharp.winmd
2>Processing WinMD c:\program files (x86)\windows kits\10\references\10.0.17134.0\windows.ai.machinelearning.preview.machinelearningpreviewcontract\1.0.0.0\windows.ai.machinelearning.preview.machinelearningpreviewcontract.winmd
... snip ...
2>Processing WinMD c:\program files (x86)\windows kits\10\references\10.0.17134.0\windows.ui.viewmanagement.viewmanagementviewscalingcontract\1.0.0.0\windows.ui.viewmanagement.viewmanagementviewscalingcontract.winmd
2>MDMERGE : error MDM2006: C:\Users\lithe\source\repos\EntityFrameworkInsideC++\InnerCSharp\bin\x86\Debug\InnerCSharp.winmd does not appear to be a valid Windows Runtime metadata file
2>MDMERGE : error MDM2005: Unable to open metadata file C:\Users\lithe\source\repos\EntityFrameworkInsideC++\InnerCSharp\bin\x86\Debug\InnerCSharp.winmd.
2>Microsoft(R) Metadata Merge Utility Version 10.0.45.
2>
2>
2>Creating output directory C:\Users\lithe\source\repos\EntityFrameworkInsideC++\Debug\OuterC++WinRT\Merged.
2>Load input metadata file C:\Users\lithe\source\repos\EntityFrameworkInsideC++\InnerCSharp\bin\x86\Debug\InnerCSharp.winmd.
2>Load input metadata file C:\Program Files (x86)\Windows Kits\10\References\10.0.17134.0\Windows.AI.MachineLearning.Preview.MachineLearningPreviewContract\1.0.0.0\Windows.AI.MachineLearning.Preview.MachineLearningPreviewContract.winmd.
... snip ...
2>Load input metadata file C:\Program Files (x86)\Windows Kits\10\References\10.0.17134.0\Windows.UI.ViewManagement.ViewManagementViewScalingContract\1.0.0.0\Windows.UI.ViewManagement.ViewManagementViewScalingContract.winmd.
2>Processing input metadata file C:\Users\lithe\source\repos\EntityFrameworkInsideC++\Debug\OuterC++WinRT\Unmerged\Class.winmd.
2>C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\Common7\IDE\VC\VCTargets\Microsoft.Cpp.CppWinRTEnabled.targets(244,9): error MSB3073: The command "mdmerge.exe -v -metadata_dir "C:\Users\lithe\source\repos\EntityFrameworkInsideC++\InnerCSharp\bin\x86\Debug\." -metadata_dir "C:\Program Files (x86)\Windows Kits\10\References\10.0.17134.0\Windows.AI.MachineLearning.Preview.MachineLearningPreviewContract\1.0.0.0\." -metadata_dir "C:\Program Files (x86)\Windows Kits\10\References\10.0.17134.0\Windows.ApplicationModel.Calls.CallsVoipContract\3.0.0.0\." -metadata_dir "C:\Program Files (x86)\Windows Kits\10\References\10.0.17134.0\Windows.ApplicationModel.SocialInfo.SocialInfoContract\2.0.0.0\." -metadata_dir "C:\Program Files (x86)\Windows Kits\10\References\10.0.17134.0\Windows.ApplicationModel.StartupTaskContract\3.0.0.0\." -metadata_dir "C:\Program Files (x86)\Windows Kits\10\References\10.0.17134.0\Windows.Devices.Custom.CustomDeviceContract\1.0.0.0\." -metadata_dir "C:\Program Files (x86)\Windows Kits\10\References\10.0.17134.0\Windows.Devices.DevicesLowLevelContract\3.0.0.0\." -metadata_dir "C:\Program Files (x86)\Windows Kits\10\References\10.0.17134.0\Windows.Devices.Printers.PrintersContract\1.0.0.0\." -metadata_dir "C:\Program Files (x86)\Windows Kits\10\References\10.0.17134.0\Windows.Devices.SmartCards.SmartCardBackgroundTriggerContract\3.0.0.0\." -metadata_dir "C:\Program Files (x86)\Windows Kits\10\References\10.0.17134.0\Windows.Devices.SmartCards.SmartCardEmulatorContract\5.0.0.0\." -metadata_dir "C:\Program Files (x86)\Windows Kits\10\References\10.0.17134.0\Windows.Foundation.FoundationContract\3.0.0.0\." -metadata_dir "C:\Program Files (x86)\Windows Kits\10\References\10.0.17134.0\Windows.Foundation.UniversalApiContract\6.0.0.0\." -metadata_dir "C:\Program Files (x86)\Windows Kits\10\References\10.0.17134.0\Windows.Gaming.XboxLive.StorageApiContract\1.0.0.0\." -metadata_dir "C:\Program Files (x86)\Windows Kits\10\References\10.0.17134.0\Windows.Graphics.Printing3D.Printing3DContract\4.0.0.0\." -metadata_dir "C:\Program Files (x86)\Windows Kits\10\References\10.0.17134.0\Windows.Networking.Connectivity.WwanContract\2.0.0.0\." -metadata_dir "C:\Program Files (x86)\Windows Kits\10\References\10.0.17134.0\Windows.Services.Store.StoreContract\3.0.0.0\." -metadata_dir "C:\Program Files (x86)\Windows Kits\10\References\10.0.17134.0\Windows.Services.TargetedContent.TargetedContentContract\1.0.0.0\." -metadata_dir "C:\Program Files (x86)\Windows Kits\10\References\10.0.17134.0\Windows.System.Profile.ProfileHardwareTokenContract\1.0.0.0\." -metadata_dir "C:\Program Files (x86)\Windows Kits\10\References\10.0.17134.0\Windows.System.Profile.ProfileSharedModeContract\2.0.0.0\." -metadata_dir "C:\Program Files (x86)\Windows Kits\10\References\10.0.17134.0\Windows.UI.ViewManagement.ViewManagementViewScalingContract\1.0.0.0\." -o "C:\Users\lithe\source\repos\EntityFrameworkInsideC++\Debug\OuterC++WinRT\Merged" -i "C:\Users\lithe\source\repos\EntityFrameworkInsideC++\Debug\OuterC++WinRT\Unmerged" -partial" exited with code 2.
2>Done building project "OuterC++WinRT.vcxproj" -- FAILED.
========== Build: 1 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========

I can, however, follow the steps in the earlier presentation to successfully reference Win2D, both in the C++/WinRT Windows Runtime project (without any additional configuration) and in the ComponentC++ Windows Desktop Dynamic-Link Library project (and running the cppwinrt command-line tool as described in this answer). However, that doesn't help me - I'm not trying to reference a NuGet package; I'm trying to reference a different project in the same solution.

So, after all that, the question is: How can I use the Universal Windows Runtime to call C# code from C++ code?

like image 872
Litherum Avatar asked Sep 20 '18 23:09

Litherum


1 Answers

You cannot call into Windows Runtime components from a Windows desktop application. This functionality is reserved for Universal Windows Platform (UWP) applications. Windows Runtime types are accessed/instantiated/called through the use of RoGetActivationFactory function, and that function only takes the class name, rather than both the class name and the DLL the class is in. That means the system must be able to locate where the class is. For system APIs this is easy - they're hardcoded in the Windows Registry. For custom classes in Windows Runtime components, it relies on AppX manifest telling which classes are in which DLLs. Unfortunately, for desktop applications you don't have an AppX manifest, so you cannot invoke custom classes.

There are other (and better) ways to invoke managed code from your desktop application.

One option is to use /clr flag when building your DLL. That will create a DLL that can call into C# code directly.

Another option is to host CLR in your process manually and invoke code that way.

My answer assumes that your application targets Windows desktop and not UWP, since your first DLL targeted desktop. If that is not the case, Windows Runtime component might be a solution, but setting up the build pipeline for that kind of setup is not very trivial. The errors you're seeing are from C++/winrt compiler, and it attempts to convert the .winmd file into header files. I don't know why it fails, but you should be able to disable by using regular Windows Runtime Component template (instead of C++/winrt one).

like image 109
Sunius Avatar answered Oct 25 '22 16:10

Sunius