如何使用 Windows 运行时使用 C# 实现 C++ API?

Posted

技术标签:

【中文标题】如何使用 Windows 运行时使用 C# 实现 C++ API?【英文标题】:How to use the Windows Runtime to implement a C++ API using C#? 【发布时间】:2019-02-25 08:00:38 【问题描述】:

我正在使用具有插件系统的本机 C++ 应用程序,它将在 .dll 上通过 LoadLibrary()/GetProcAddress() 调用函数。我想实现其中一个插件,在插件内部我想使用持久性框架将复杂的对象图保存/加载到磁盘。

Windows 上最受支持的 ORM 似乎是 Entity Framework,this page 表示 Entity Framework Core 是最现代的风格。 This page 似乎说,为了使用 Entity Framework Core,你的数据模型必须用 C# 编写。

所以,听起来我必须从 C++ 调用 C#。幸运的是,Entity Framework Core supports 似乎是通用 Windows 运行时,它应该使语言之间的调用变得容易。

This presentation 似乎说从 C++ 与通用 Windows 运行时交互涉及创建 Windows 运行时组件,并且编译步骤可以从 Windows 运行时组件的.winmd 文件生成粘合代码。

因此,我可以制作一个包含两个项目的 Visual Studio 解决方案:一个外部“C++ Windows 桌面动态链接库”项目和一个内部“C# 通用 Windows 运行时组件”。

docs 然后说“引用 Windows 运行时组件的 Windows 运行时元数据 (.winmd) 文件并构建。”但是,当我在外部项目中添加引用时,尝试选择内部项目时出现错误,并且没有选项可以浏览以选择任意.winmd

即使我进入解决方案管理器并删除两个项目中除x86 之外的所有平台,也会出现此错误。

这很令人惊讶,因为 this Windows Blog post 明确表示,本机代码应该能够通过添加对 .winmd 的引用来调用 UWP 代码(尽管帖子使用的是旧的 C++/CX 语法)。

如果我重新开始并使用“C++/WinRT Windows 运行时组件”而不是“C++ Windows 桌面动态链接库”项目,我仍然会遇到问题。

如果我这样做,我可以添加引用就好了。

但是,当我尝试构建时,我遇到了构建失败。

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 ==========

但是,我可以按照the earlier presentation 中的步骤在 C++/WinRT Windows 运行时项目(无需任何额外配置)和 ComponentC++ Windows 桌面动态链接库项目(并运行cppwinrt 命令行工具,如this answer 中所述)。但是,这对我没有帮助——我不想引用 NuGet 包;我正在尝试在同一解决方案中引用不同的项目。

那么,毕竟,问题是:如何使用通用 Windows 运行时从 C++ 代码调用 C# 代码?

【问题讨论】:

使用 C++/WinRT 扩展通常需要构建项目两次。您在第二次构建时遇到相同的错误吗? 是的。每次构建时都会发生相同的错误。 我已经完成了你的流程,我可以将 c# 运行时组件添加到 C++ 运行时组件中。但原生 c++ 桌面应用程序无法使用 c++ 运行时组件。根据您的要求,您可以尝试使用 uwp 应用服务。 【参考方案1】:

您不能从 Windows 桌面应用程序调用 Windows 运行时组件。此功能为通用 Windows 平台 (UWP) 应用程序保留。 Windows 运行时类型通过使用RoGetActivationFactory 函数来访问/实例化/调用,并且该函数只接受类名,而不是类名和类所在的DLL。这意味着系统必须能够定位班级在哪里。对于系统 API,这很容易——它们在 Windows 注册表中进行了硬编码。对于 Windows 运行时组件中的自定义类,它依赖于 AppX 清单来告知哪些类在哪些 DLL 中。不幸的是,对于桌面应用程序,您没有 AppX 清单,因此您无法调用自定义类。

还有其他(更好的)方法可以从您的桌面应用程序调用托管代码。

一种选择是在构建 DLL 时使用/clr 标志。这将创建一个可以直接调用 C# 代码的 DLL。

另一种选择是托管CLR in your process manually and invoke code that way。

我的回答假设您的应用程序针对的是 Windows 桌面而不是 UWP,因为您的第一个 DLL 是针对桌面的。如果不是这种情况,Windows 运行时组件可能是一个解决方案,但为这种设置设置构建管道并不是很简单。您看到的错误来自 C++/winrt 编译器,它尝试将 .winmd 文件转换为头文件。我不知道它为什么会失败,但是您应该可以使用常规的 Windows 运行时组件模板(而不是 C++/winrt 模板)来禁用它。

【讨论】:

错误是由 mdmerge.exe 工具生成的。这与 C++/WinRT 或 cppwinrt.exe 工具完全无关。 我可能错了,但我的印象是 mdmerge.exe 只有在使用 C++/winrt 时才需要运行。 midlrt.exe 和 mdmerge.exe 并非特定于 C++/WinRT。每当您使用 IDL 描述您的 Windows 运行时类型时,都需要这些工具。 C++/WinRT 是一个库,但您也可以在使用纯 C++ 或带有 WRL 的 C++ 实现 Windows 运行时类型时运行这些工具。 是的,但是 OP 没有使用 IDL。他正在使用 C++/winrt。 OP 正在使用 IDL (Class.idl)。除了使用 .idl 文件之外,我不知道有任何其他方法可以从本机代码生成 .winmd 文件。这在Author APIs with C++/WinRT: If you're authoring a runtime class in a Windows Runtime Component 下进行了解释。

以上是关于如何使用 Windows 运行时使用 C# 实现 C++ API?的主要内容,如果未能解决你的问题,请参考以下文章

如何从 C# 授予 Windows 10 中 Pcap 库的权限?

如何在 C# 程序中使用标准 Windows 定义

如何使用c#和windows服务创建无敌的windows应用程序

如何在 C# 中使用 TaskScheduler 设置“仅在登录时运行”和“运行方式”?

如何使用 C# 从 Windows 服务运行 EXE 程序?

如何在 Windows 启动时运行 C# 应用程序?