创建附带多平台本机代码的NuGet包
一般只包含托管代码的 .NET工程直接打包就完事了。但是现在有一个 .NET工程包含了平台相关 (P/Invoke) 的调用,需要平台相关的非托管程序集,这个程序集需要手动提供,打包的时候要怎么做?
实际上过程很简单…但是…M$这文档太详细了半天没找着对应位置,在某404搜索引擎上搜了半天才在nuget官方的repo里找到一个issue,里说了正确方法,结合之前搜到的教程终于是搞定了…所以在这里特地记录一下防止遗忘
假设现在我们有个类库工程叫做SuperAdd.NET
,它现在长这样:
1 2 3 4 5 6
| ├───SuperAdd.NET │ Omg.cs │ SuperAdd.NET.csproj │ └───SuperAdd.NET.Native SuperAdd.NET.Native.cpp
|
获取平台的动态链接库
文章重点不在C/C++这边,这里应该简略说的,不过呢为了演示还是写详细点…
文件夹SuperAdd.NET.Native
包含C++源码,SpuerAdd.NET.Native.cpp
的内容是:
1 2 3 4 5 6 7 8 9 10 11
| #ifdef _WIN32 #define EXPORT_API __declspec(dllexport) #else #define EXPORT_API __attribute__((visibility("default"))) #endif
#include <cstdint>
extern "C" EXPORT_API std::int32_t SuperAdd(std::int32_t x, std::int32_t y) { return x + y; }
|
显然这份C++源码是可以跨平台的
我们将它分别在win和linux下编译成动态链接库。
现在整个工程长这样:
1 2 3 4 5 6 7 8
| ├───SuperAdd.NET │ Omg.cs │ SuperAdd.NET.csproj │ └───SuperAdd.NET.Native SuperAdd.NET.Native.cpp SuperAdd.NET.Native.dll SuperAdd.NET.Native.so
|
NuGet包结构
M$的文档写的非常详细 https://docs.microsoft.com/zh-cn/nuget/create-packages/creating-a-package
这里总结一下:
- lib:存托管程序集,它的结构是:
lib/{tfm}/*.dll
- runtimes:存平台相关程序集,它的结构是:
runtimes/{rid}/native/*.*
其中,tfm
表示目标框架,就是netstandard2.1之类的名字,rid
是运行时标识符,类似win-x64
、linux-x64
之类的名字
所以为了打包,我们需要按照NuGet包的结构编排我们的项目
配置 .NET工程
由于我们跨win和linux,所以根据runtimes的结构,我们需要将动态链接库复制到对应文件夹里
现在的工程结构长这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| ├───SuperAdd.NET │ │ Omg.cs │ │ SuperAdd.NET.csproj │ │ │ └───Externs │ └───runtimes │ ├───linux-x64 │ │ └───native │ │ SuperAdd.NET.Native.so │ │ │ └───win-x64 │ └───native │ SuperAdd.NET.Native.dll │ └───SuperAdd.NET.Native SuperAdd.NET.Native.cpp SuperAdd.NET.Native.dll SuperAdd.NET.Native.so
|
其中,Externs
这个文件夹的名字是可以随意替换的,替换成啥都可以
接着打开SuperAdd.NET.csproj
,我们需要在里面添加一个Target
1 2 3 4 5
| <Target Name="IncludeAllDependencies" BeforeTargets="_GetPackageFiles"> <ItemGroup> <None Include="Externs\runtimes\**\*.dll" Pack="True" PackagePath="runtimes" /> </ItemGroup> </Target>
|
其中,第三行Include里面填的就是需要打包的文件,所以这里别忘了把Externs换成前面你的文件夹名字
然后在PropertyGroup
属性里添加<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
,这样就可以在构建工程时自动打包了,这是个可选项,不过我觉得加上方便些
现在,SuperAdd.NET.csproj
长这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <TargetFramework>netstandard2.1</TargetFramework> <Nullable>enable</Nullable> <GeneratePackageOnBuild>true</GeneratePackageOnBuild> </PropertyGroup>
<Target Name="IncludeAllDependencies" BeforeTargets="_GetPackageFiles"> <ItemGroup> <None Include="Externs\runtimes\**\*.*" Pack="True" PackagePath="runtimes" /> </ItemGroup> </Target>
</Project>
|
现在构建工程
1
| dotnet build --configuration Release
|
就可以看到输出里的NuGet包了
我们打开看一下
确确实实打包进去了
顺便贴一下Omg.cs
的内容:
1 2 3 4 5 6 7
| using System.Runtime.InteropServices;
public static class Omg { [DllImport("SuperAdd.NET.Native")] public static extern int SuperAdd(int x, int y); }
|
可以看到和C++侧的完全对应
测试
现在再新建个工程,导入我们刚刚生成的包(具体如何导入本地包可以自己搜一下)
直接上结果图吧
Surprise !
我们还可以打开构建目录里的 deps.json 文件
看到targets
字段下,我们的SuperAdd.NET
里面确实包含了runtimeTargets
,这样 .NET就会在运行时根据平台自动帮我们加载对应的动态链接库,不再需要手动编写deps.json了,还是挺方便的