I am trying to create a Nuget package for a library that depends on ghostscript and therefore references gsdll32.dll - an unmanaged library. I can't just included that a standard dll reference. Where do I put this in the nuget directory structure?
6 Answers
Add a build
folder to the package and, if the package for example has the id MyPackage
, add a MSBuild target file called MyPackage.targets
to this folder. It is important that the .targets
file has the same name as the .nuspec
file. In the .nuspec
file you must have a section like this:
<files>
<file src="lib\*.*" target="lib" />
<file src="build\MyPackage.targets" target="build" />
</files>
This will add an MSBuild element in the project file pointing to the .targets
file.
Furthermore, to only register the managed dlls, add a section like this:
<references>
<reference file="MyManaged.dll" />
</references>
The .targets
file should look something like this:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="CopyMyPackageFiles" AfterTargets="AfterBuild">
<ItemGroup>
<MyPackageFiles Include="$(MSBuildThisFileDirectory)..\lib\*.*"/>
</ItemGroup>
<Copy SourceFiles="@(MyPackageFiles)" DestinationFolder="$(OutputPath)" >
</Copy>
</Target>
</Project>
Now, all files - including unmanaged files - will be copied to the project output folder (e.g. \bin\debug) after the build.

- 693
- 9
- 19
-
I appreciate this Lars - can you edit this into your answer? Links to blogs tend to stop working. – George Mauer Oct 09 '15 at 17:09
-
you made my day (period). – Lorenzo Solano Martinez Mar 10 '16 at 22:19
-
I don't understand at all how this is working. I do exactly what you're saying, then generate my .nupkg file and reference this file into my project. But when I build nothing that I wrote into my MyPackage.targets file is executed. I've tried the Message task with high Importance but nothing change. Once the package is created then all the dll and nuspec files are useless right? – Jérôme MEVEL Jun 07 '16 at 03:06
-
1@Jérôme it's kind of late, but I think you question is still worth answering. There is slight (and definitely not obvious) pitfall to .target file: name has to be same as package name. When it's like this, target is automatically added to .csproj file on package installation. You can check yours for
referencing your target in packages somewhere near the end. Only then it will work. No other magic involved. – Andriy K Feb 21 '17 at 23:46 -
@AndriyK thanks for having taken the time to answer. I managed to do it but I've been through several issues. You could read [my short answer](http://stackoverflow.com/a/40652794/1203116) if you are curious on how to do it with a .NET Core project – Jérôme MEVEL Feb 22 '17 at 01:48
-
@lars-michael Your **Include** attribute specifies a path to a nuget package without specifying the version. How does that work? My DLL would be at "$(MSBuildProjectDirectory)\..\Packages\MyPackage.1.0.0\lib\*.*" – Homr Zodyssey May 25 '17 at 16:47
-
2You can avoid having to hard-code the precise path as follows: `
– Matthew Sharpe Jul 18 '17 at 13:34 -
@Matt Thanks for your suggestion that solves the issue mentioned by Homr about having to deal with the version number of the package. I have modified the answer accordingly. – Lars Michael Aug 03 '17 at 14:27
-
@Homr You are right that you would have to modify the version number in the path. However, the alternative as suggested above by Matt will remove this hassle. I have modified the original answer accordingly. – Lars Michael Aug 03 '17 at 14:54
-
In your answer you don't specify : where to add the `
` tag (parent tag), where to add the ` – Elliot Woods Nov 26 '18 at 09:58` (file and parent tag), whether the `.targets` file needs to be customised or works as-is. -
2@ElliotWoods In the .nuspec file (https://learn.microsoft.com/en-us/nuget/reference/nuspec). .targets files needs to be customised according to where you put your native files. In the example, they are placed in a folder called `lib` – Lars Michael Nov 26 '18 at 10:09
-
The assembly doesn't get copied if the project references another project that includes the Nuget package (containing the unmanaged assembly). The targets file in the following answer caused the assembly to be copied to both project bin directories- https://stackoverflow.com/a/40652794/2035501 – Andrew Jan 15 '20 at 11:46
The above reference can work, but it actually modifies your post build event to push files over, which may not actually fix your issue if you have the situation we did.
The issue we were having was a dependent DLL could not be registered, but had to exist side by side with another DLL which needed to be registered by nuget so it needed to exist in the lib directory but not be registered.
The nuspec reference now allows you to specify which DLLs in the lib directory get explicitly registered in the visual studio project now, you simply need to add into your nuspec file in the metadata area an explicit references list (if this does not exist the default behavior of nuget is to attempt to register everything under lib).
Here is an example nuspec file of what I mean:
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>SomePackageID</id>
<version>1.0.1</version>
<title>Some Package Title</title>
<authors>Some Authors</authors>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>Blah blah blah.</description>
<references>
<reference file="ceTe.DynamicPDF.Rasterizer.20.x86.dll" />
</references>
</metadata>
<files>
<file src="\\SomeNetworkLocation\ceTe.DynamicPDF.Rasterizer.20.x86.dll" target="lib\ceTe.DynamicPDF.Rasterizer.20.x86.dll" />
<file src="\\SomeNetworkLocation\DPDFRast.x86.dll" target="lib\DPDFRast.x86.dll" />
</files>
</package>
As you can see, ceTe.DynamicPDF.Rasterizer.20.x86.dll
needs to be registered, but DPDFRast.x86.dll
simply needs to exist in that directory to support the other DLL and won't be registered but through some dynamic referencing magic will ultimately be copied over into the destination bin directory anyway because visual studio sees that the first DLL is dependent upon the second.
Here is the original nuspec reference.
-
1_"...through some dynamic referencing magic will ultimately be copied over into the destination bin directory anyway..."_. This seems not to be working for **unamanged** dll's, which is what the question is about. – Lars Michael Oct 09 '15 at 13:00
-
3Well, it did not work in my case but http://stackoverflow.com/a/33041626/798781 did. – Lars Michael Oct 12 '15 at 21:28
Response on the Nuget forum: http://nuget.codeplex.com/discussions/352689
pranavkm: The SQLCE package has a similar issue that we handle via PS scripts. Checkout out the scripts at https://bitbucket.org/davidebbo/nugetpackages/src/1cba18b864f7/SqlServerCompact/Tools.

- 117,483
- 131
- 382
- 612
I largely got this to work using Lars Michael's method, but one thing I needed to add comes from James Eby's answer. Visual Studio was trying to register all the dll's in my lib
directory, so I added a references
element to the metadata in the nuspec file to tell it to only register the managed dll:
<references>
<reference file="FANNCSharp.dll" />
</references>
Also in
<MyPackageFiles Include="$(MSBuildProjectDirectory)\..\Packages\MyPackage\lib\*.*"/>
I first tried the id of my package FANNCSharp-x64
, but it needed the full package name: FANNCSharp-x64.0.1.4
.

- 714
- 6
- 10
-
1I modified the original answer to address the things that you mention. Thanks. – Lars Michael Aug 03 '17 at 15:25
One problem I had was that the packages path wasn't always in the same place relative to the project file. The following worked for me:
Within the NuGet package, place your unmanaged DLLs in the lib\native folder.
Add the following script to the tools folder:
install.ps1
#This script creates or updates a PackagesPath property in the project file
param($installPath, $toolsPath, $package, $project)
$project.Save()
#Load the csproj file into an xml object
[xml] $xml = Get-Content -path $project.FullName
#grab the namespace from the project element
$nsmgr = New-Object System.Xml.XmlNamespaceManager -ArgumentList $xml.NameTable
$nsmgr.AddNamespace('a',$xml.Project.GetAttribute("xmlns"))
#find or create the property
$property = $xml.Project.SelectSingleNode("//a:PropertyGroup//a:PackagesPath", $nsmgr)
if (!$property)
{
$property = $xml.CreateElement("PackagesPath", $xml.Project.GetAttribute("xmlns"))
$propertyGroup = $xml.CreateElement("PropertyGroup", $xml.Project.GetAttribute("xmlns"))
$propertyGroup.AppendChild($property)
$xml.Project.InsertBefore($propertyGroup, $xml.Project.ItemGroup[0])
}
#find the relative path to the packages folder
$absolutePackagesPath = (get-item $installPath).parent.FullName
push-location (split-path $project.FullName)
$relativePackagesPath = Resolve-Path -Relative $absolutePackagesPath
pop-location
#set the property value
$property.InnerText = $relativePackagesPath
#save the changes.
$xml.Save($project.FullName)
- Add a targets file to the build folder. (Change "MyPackage" to the name of your package). Using a unique name for the target, like "CopyMyPackage", avoids conflicts with other packages trying to define the "AfterBuild" target. This targets file makes use of the $(PackagesPath) property defined by the above script.
MyPackage.targets
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="CopyMyPackage" AfterTargets="AfterBuild">
<ItemGroup>
<MyPackageSourceFiles Include="$(PackagesPath)\MyPackage.*\lib\native\*.*"/>
</ItemGroup>
<Copy SourceFiles="@(MyPackageSourceFiles)" DestinationFolder="$(OutputPath)" >
</Copy>
</Target>
</Project>
- Finally, add a "MyPackageReadMe.txt" to the Content folder. This will enable the package to install.
See also: http://alski.net/post/2013/05/23/Using-NuGet-25-to-deliver-unmanaged-dlls.aspx

- 1,033
- 10
- 14
For .NET Core this is pretty straightforward if you know what runtime platform your native code targets. You might notice a folder called "runtimes" in the .NET Core build folder under the bin tree when you build. It looks something like this:
These folders are designed to hold any platform specific stuff, including unmanaged/native DLLs.
In your NuGet package add a the following under the "Files" section:
<file src="[source path for file in package]" target="runtimes\[platform]\native\[file name]" />
When executing the application, the runtime environment will look for unmanaged dlls in the corresponding platform directory.
If you want to target multiple platforms, just add another file entry for each platform.

- 490
- 5
- 13