UWP中为 WinRT 组件创建 NuGet 包
这篇文章可以说是迟到了 N 多年的分享了,一直放在草稿箱里但是没有发布,今天赶紧地整理。
先搞清楚:WinRT 组件里到底有什么
假设我们有一个 WinRT 组件:Demo.winmd、Demo.dll。这两个文件虽然经常一起出现,但职责完全不同。.winmd 是 Windows Metadata 文件,它描述的是这个 WinRT 组件对外暴露的类型信息,比如:
namespace
class
interface
method
property
enum
WinRT 的语言投影也依赖这些 metadata。比如 C++/WinRT 会根据 WinRT metadata 生成对应的 C++ 投影代码。所以可以简单理解成:Demo.winmd 告诉编译器“这个组件长什么样”,它本身并不是业务代码的实现。.dll 才是实际的 native implementation。也就是说:Demo.dll 真正执行代码。对于一个典型的 WinRT Component,这两个文件是配套的。
Visual Studio 在构建 Windows Runtime Component 时,会生成 WinRT metadata;对于 C++/WinRT 项目,工具链也是先处理 IDL 生成 .winmd,再基于 metadata 生成相应的投影代码。
不要把 .winmd 理解成普通 .NET 程序集,也不要把它和 DLL 当成两个可以随便替换的文件。它们共同描述并实现了一个 WinRT 组件。
NuGet 包里应该放什么?
对于这个例子,一个最基本的 NuGet 包可以长这样:
com.xx.demo.nupkg
│
├─ lib
│ └─ uap10.0
│ └─ Demo.winmd
├─ runtimes
│ ├─ win10-x86
│ │ └─ native
│ │ └─ Demo.dll
│ └─ win10-x64
│ └─ native
│ └─ Demo.dll
└─ build
└─ native
└─ com.xx.demo.targets
这里其实就是整个事情最关键的部分,.nuspec 本身反而没什么好讲的——如果你平时用 NuGet,知道怎么写一个基本的 .nuspec 就够了。
为什么 .winmd 和 .dll 要放在不同目录?
这是很多第一次做 native NuGet 包的人容易疑惑的地方。.winmd 是给编译阶段使用的,所以我们把它放到 lib/uap10.0/,而 DLL 属于 native runtime binary,因此放到:runtimes/win10-x64/native/、runtimes/win10-x86/native/。
这样做的一个直接好处就是:同一个 NuGet 包可以同时支持多个 CPU 架构。微软的 UWP NuGet 示例也是把 WinMD 放到 lib\uap10.0,native DLL 放到 runtimes 对应的架构目录。 Microsoft Learn+1
C++ 项目为什么还需要 .targets?
NuGet 对 native C++ 包的处理方式,本身就和 .NET 程序集不太一样。微软的 native NuGet 文档也明确提到,native 包通常通过 build、content、tools 等目录以及 MSBuild 的 .props/.targets 来完成项目集成,而不是依赖 lib 直接添加 C++ Reference。 所以我们通常需要提供一个:com.xx.demo.targets。它的作用可以简单理解成:告诉 MSBuild:这个 NuGet 包里的 WinMD 和 DLL 怎么添加到当前项目。
一个简化后的思路大概是:
<ItemGroup Condition="'$(TargetPlatformIdentifier)' == 'UAP'">
<Reference Include="
$(MSBuildThisFileDirectory)
..\..\lib\uap10.0\Demo.winmd">
<Implementation>Demo.dll</Implementation>
</Reference>
<ReferenceCopyLocalPaths Include="
$(MSBuildThisFileDirectory)
..\..\runtimes\win10-$(Platform)\native\Demo.dll" />
</ItemGroup>
.nuspec 不需要写得很复杂
如果已经熟悉 NuGet,可以把它理解成一个文件映射:
<files>
<file
src=".\Demo.winmd"
target="lib\uap10.0" />
<file
src=".\Demo.dll"
target="runtimes\win10-x64\native" />
<file
src=".\com.xx.demo.targets"
target="build\native" />
</files>
如果还要支持 x86,就再放一个对应的 DLL:runtimes/win10-x86/native/Demo.dll。
这里最重要的不是 XML 本身,而是记住:
WinMD
→ lib/uap10.0
Native DLL
→ runtimes/<RID>/native
MSBuild 集成
→ build/native
XML 文档要不要一起打包?
如果组件是给别人使用的,比较建议加上。里面保存 C++/C# 的 API 文档注释,这样 IDE 可以显示对应的 API 注释。这个东西不影响组件运行,但对 SDK 类组件来说体验差别还是挺大的。