Search code examples
nugetnuget-package

Add native files from NuGet package to .NET application output directory


I am trying to get native files installed alongside a .NET 8.0 C# assembly from a NuGet package. The goal is to use P/Invoke to call APIs in a native DLL, so I have to know where that DLL is located relative to the C# application using the package, and the DLL's dependencies must be found.

I tried the runtimes hierarchy mentioned here, but that didn't work. I put the native files in the runtimes\net8.0\native directory in the package, and I see them listed in obj\project.assets.json in "runtimeTargets", but when I build the solution, they are not copied to the output directory, so I can't find the API DLL and it wouldn't find its dependencies.

Following the advice in this answer, I tried to arrange for the native files to be installed in the output directory for a C# Console Application, but the copy step doesn't run.

In that case, my NuGet package contained the native files in the build directory along with the following Project.targets file:

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <ItemGroup>
    <NativeDependencies
      Include="$(MSBuildThisFileDirectory)*.*"
      Exclude="$(MSBuildThisFileDirectory)*.targets"
    />
    <None Include="@(NativeDependencies)">
      <Link>%(FileName)%(Extension)</Link>
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
  </ItemGroup>
</Project>

I installed my NuGet package in the console application; there were no errors. I built the solution, but the native files were not copied/linked.

I see the Project.targets file referenced in obj/Project.csproj.nuget.g.targets, so seemingly there's an appropriate reference to Project.targets, but it never runs:

    <Import Project="$(NuGetPackageRoot)...\build\Project.targets" Condition="Exists('$(NuGetPackageRoot)...\build\Project.targets')" />

I set Visual Studio to run MSBuild with diagnostic level output, but see no mention of "Project.targets" or "NativeDependencies".

What have I missed?


Solution

  • As described here, and clarified by @Martin Ullrich, it was enough to put the native files in runtimes\win-x64\native for my case†. I then only needed to reference the DLL via [DllImport("name")]. (I had tried that before and couldn't seem to get it to work, hence the posted question, but there were other issues that I missed then.)

    My Project.nuspec contains the following:

      <files>
        ...
        <file src="path\*.dll" target="runtimes\win-x64\native"/>
      </files>
    

    After installing the package and building the C# console application, the native DLLs are copied to, for example, ...\bin\x64\Debug\net8.0\runtimes\win-x64\native. The DllImport shown above, in the .cs file using P/Invoke, then finds the native DLL with the required APIs without needing any path information.

    † I previously misapprehended net8.0 as the RID to use, but there are different RIDs for native versus managed runtimes. When I used net8.0 rather than win-x64, I needed the native runtimes' relative pathname in my source: [DllImport("runtimes\\net8.0\\native\\name")].