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

1

6

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?

Oestriol answered 20/9, 2018 at 23:48 Comment(3)
Using the C++/WinRT extension usually requires building the project twice. Do you get the same errors on the second build?Carom
Yep. The same error happens every time I build.Oestriol
I have done with your process, and I could add c# runtime component to C++ runtime component. but c++ runtime component could not be used by native c++ desktop app. for your requrment, you could try to use uwp app service.Appreciation
S
3

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).

Schaerbeek answered 21/9, 2018 at 22:19 Comment(9)
The errors are generated from the mdmerge.exe tool. That is completely unrelated to C++/WinRT or the cppwinrt.exe tool.Carom
I might be wrong, but I was under an impression mdmerge.exe only needs to run if you're using C++/winrt.Schaerbeek
midlrt.exe and mdmerge.exe aren't specific to C++/WinRT. Those tools are required whenever you use IDL to describe your Windows Runtime types. C++/WinRT is a library that does, but you'd also run those tools when implementing Windows Runtime types in plain C++, or C++ with WRL.Carom
Yeah, but OP isn't using IDL. He's using C++/winrt.Schaerbeek
The OP is using IDL (Class.idl). I'm not aware of any other way to generate a .winmd file from native code other than using an .idl file. This is explained under Author APIs with C++/WinRT: If you're authoring a runtime class in a Windows Runtime Component.Carom
Actually, I'm not even sure this answer is accurate. You certainly can call into Windows Runtime APIs from a Desktop application. That's what the DualApiPartitionAttribute is for. It's also not correct that you need an .appxmanifest to resolve type names to components. That information is available through the .winmd metadata, and although I don't have documentation at hand, I'm fairly sure that information is used by the individual language projections to wire up the Windows Runtime machinery.Carom
You can't into custom Windows Runtime APIs from desktop applications. You can call system into system ones. Custom Windows Runtime APIs get registered in your AppXManifest.xml file (take a look at any installed UWP app, you'll find them), but for desktop applications you don't have an AppX manifest.Schaerbeek
Is this something that the Desktop Bridge is supposed to do?Oestriol
Sure, you could use Desktop Bridge to do this, for more your could refer this document.Unseasoned

© 2022 - 2024 — McMap. All rights reserved.