Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Add native files from NuGet package to project output directory

People also ask

How do I get files from a NuGet package?

on the toolbar of the Assembly Explorer window or choose File | Open from NuGet Packages Cache in the main menu . This will open the Open from NuGet Packages Cache dialog. The dialog lists packages from all NuGet cache locations on your machine. Use the search field in the dialog to find the desired package.


Using the Copy target in the targets file to copy required libraries won't copy those files to other projects which reference the project, resulting in a DllNotFoundException. This can be done with a much simpler targets file though, using a None element, as MSBuild will copy all None files to referencing projects.

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

Add the targets file to the build directory of the nuget package along with the required native libraries. The targets file will include all dll files in all child directories of the build directory. So to add an x86 and x64 version of a native library used by an Any CPU managed assembly you would end up with a directory structure similar to the following:

  • build
    • x86
      • NativeLib.dll
      • NativeLibDependency.dll
    • x64
      • NativeLib.dll
      • NativeLibDependency.dll
    • MyNugetPackageID.targets
  • lib
    • net40
      • ManagedAssembly.dll

The same x86 and x64 directories will be created in the project's output directory when built. If you don't need subdirectories then the ** and the %(RecursiveDir) can be removed and instead include the required files in the build directory directly. Other required content files can also be added in the same way.

The files added as None in the targets file won't be shown in the project when open in Visual Studio. If you are wondering why I don't use the Content folder in the nupkg it's because there's no way to set the CopyToOutputDirectory element without using a powershell script (which will only be run inside Visual Studio, not from the command prompt, on build servers or in other IDEs, and is not supported in project.json / xproj DNX projects) and I prefer to use a Link to the files rather than having an additional copy of the files within the project.

Update: Although this should also work with Content rather than None it appears that there's a bug in msbuild so files won't be copied to referencing projects more than one step removed (e.g. proj1 -> proj2 -> proj3, proj3 won't get the files from proj1's NuGet package but proj2 will).


Here is an alternative that uses the .targets to inject the native DLL in the project with the following properties.

  • Build action = None
  • Copy to Output Directory = Copy if newer

The main benefit of this technique is that the native DLL is copied into the bin/ folder of dependent projects transitively.

See the layout of the .nuspec file:

Screen capture of NuGet Package Explorer

Here is the .targets file:

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    <ItemGroup>
        <None Include="$(MSBuildThisFileDirectory)\..\MyNativeLib.dll">
            <Link>MyNativeLib.dll</Link>
            <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
        </None>
    </ItemGroup>
</Project>

This inserts the MyNativeLib.dll as if it was part of the original project (but curiously the file is not visible in Visual Studio).

Notice the <Link> element that sets the destination file name in the bin/ folder.


I had recently the same problem when I tried to build an EmguCV NuGet package including both managed assemblies and non-managed shared liraries (which also had to be placed in a x86 subdirectory) which had to be copied automatically to the build output directory after each build.

Here is a solution I came up with, that relies on only NuGet and MSBuild:

  1. Place the managed assemblies in the /lib directory of the package (obvious part) and the non-managed shared libraries and related files (e.g. .pdb packages) in the /build subdirectory (as described in the NuGet docs).

  2. Rename all non-managed *.dll file endings to something different, for example *.dl_ to prevent NuGet from moaning about alleged assemblies being placed at a wrong place ("Problem: Assembly outside lib folder.").

  3. Add a custom <PackageName>.targets file in the /build subdirectory with something like the following contents (see below for a description):

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <ItemGroup>
    <AvailableItemName Include="NativeBinary" />
  </ItemGroup>
  <ItemGroup>
    <NativeBinary Include="$(MSBuildThisFileDirectory)x86\*">
      <TargetPath>x86</TargetPath>
    </NativeBinary>
  </ItemGroup>
  <PropertyGroup>
    <PrepareForRunDependsOn>
      $(PrepareForRunDependsOn);
      CopyNativeBinaries
    </PrepareForRunDependsOn>
  </PropertyGroup>
  <Target Name="CopyNativeBinaries" DependsOnTargets="CopyFilesToOutputDirectory">
    <Copy SourceFiles="@(NativeBinary)"
          DestinationFiles="@(NativeBinary->'$(OutDir)\%(TargetPath)\%(Filename).dll')"
          Condition="'%(Extension)'=='.dl_'">
      <Output TaskParameter="DestinationFiles" ItemName="FileWrites" />
    </Copy>
    <Copy SourceFiles="@(NativeBinary)"
          DestinationFiles="@(NativeBinary->'$(OutDir)\%(TargetPath)\%(Filename).%(Extension)')"
          Condition="'%(Extension)'!='.dl_'">
      <Output TaskParameter="DestinationFiles" ItemName="FileWrites" />
    </Copy>
  </Target>
</Project>

The above .targets file will be injected on an installation of the NuGet package in the target project file and is responsible for copying the native libraries to the output directory.

  • <AvailableItemName Include="NativeBinary" /> adds a new item "Build Action" for the project (which also becomes available in the "Build Action" dropdown inside of Visual Studio).

  • <NativeBinary Include="... adds the native libraries placed in /build/x86 to current project and makes them accessible to the custom target which copies those files to the output directory.

  • <TargetPath>x86</TargetPath> adds custom metadata to the files and tells the custom target to copy the native files to the x86 subdirectory of the actual output directory.

  • The <PrepareForRunDependsOn ... block adds the custom target to the list of targets the build depends on, see the Microsoft.Common.targets file for details.

  • The custom target, CopyNativeBinaries, contains two copy tasks. The first one is responsible for copying any *.dl_ files to the output directory while changing their extension back to to the original *.dll. The second one simply copies the rest (for example any *.pdb files) to the same location. This could be replaced by a single copy task and an install.ps1 script which had to rename all *.dl_ files to *.dll during package installation.

However, this solution still would not copy the native binaries to the output directory of another project referencing the one which initially includes the NuGet package. You still have to reference the NuGet package in your "final" project as well.


If anyone else stumbles across this.

The .targets filename MUST equal the NuGet Package Id

Anything else wont work.

Credits go to: https://sushihangover.github.io/nuget-and-msbuild-targets/

I should've read more thoroughly as its actually noted here. Took me ages..

Add a custom <PackageName>.targets


It's a bit late but I've created a nuget package exaclty for that.

The idea is to have an additional special folder in your nuget package. I'm sure you already know Lib and Content. The nuget package I've created looks for a Folder named Output and will copy everything which is in there to the projects output folder.

The only thing you have to do is add a nuget dependency to the package http://www.nuget.org/packages/Baseclass.Contrib.Nuget.Output/

I've written a blog post about it: http://www.baseclass.ch/blog/Lists/Beitraege/Post.aspx?ID=6&mobile=0


There's a pure C# solution which I find rather easy to use and I don't have to bother with NuGet limitations. Follow these steps:

Include the native library in your project and set its Build Action property to Embedded Resource.

Paste the following code into class where you PInvoke this library.

private static void UnpackNativeLibrary(string libraryName)
{
    var assembly = Assembly.GetExecutingAssembly();
    string resourceName = $"{assembly.GetName().Name}.{libraryName}.dll";

    using (var stream = assembly.GetManifestResourceStream(resourceName))
    using (var memoryStream = new MemoryStream(stream.CanSeek ? (int)stream.Length : 0))
    {
        stream.CopyTo(memoryStream);
        File.WriteAllBytes($"{libraryName}.dll", memoryStream.ToArray());
    }
}

Call this method from the static constructor like as follows UnpackNativeLibrary("win32"); and it will unpack the library to disk just before you need it. Of course, you need to be sure that you have write permissions to that part of the disk.