Including Native DLLs in a NuGET Package

On my current project, many of the tools we use are shared across projects. We’ve made good use of Azure DevOps pipelines and private package feeds to easily and automatically publish and share the artifacts. Most of the time, things have worked really smoothly.

Until recently, when I had a package that depended on a third-party native dynamic link library (DLL). After the expected amount of wailing and gnashing of teeth, I was able to make it work. There are a lot of posts around the web on this topic, but the tooling has evolved over the years. Here’s what worked for me in late 2019.

(Much of the background for this approach came from this post on Nupkg Containing Native Libraries. I’ve found it’s still fairly accurate.)

Prerequisites

  1. I had already set up ADO to build both my main project (which consumes the package) and the package itself.
  2. Both projects have already migrated away from the old NuGET packages.config format to the new and integrated PackageReference style.

Setting up the Package

The native DLLs need to be included in the final NuGET archive, so my first attempt looked a lot like this:

    
        true
        lib\$(TargetFramework)\XXUtils.dll
    

This was sufficient to get the file to show up in the package, but no matter what I did, I couldn’t get it to land in the right spot. It was always treated as if it were an asset— like an image instead of code.

Instead, I created a .nuspec file myself. For simple packages, Visual Studio & ADO can generate this file for you, but for this setup, I needed to provide my own. That let me include elements, which I needed to tell NuGET where to place the included files. Here’s what my final .nuspec file looked like:

$id$
$version$

Internal Technologies Inc.
Internal Technologies Inc.
false
OPCHelper
None.
Copyright 2019

I actually did include all of the runtime IDs in the file (this isn’t just an example). It sounds like the generic RIDs of win only work in special circumstances, so instead of dealing with those, I  added the exact same source DLL to each of the possible Windows versions.

The $xxx$ replacement tokens are populated from the project file, but I had to enter the other fields by hand.

Consuming the Package

Consuming a package with native DLLs works just fine. Mostly. To perfect it, I had to specify the architecture for my project. If you select “Any CPU,” it won’t copy the DLLs into the build folder, so be sure to select “x86” or “x64” as appropriate.

Packages Work Well

My team has really enjoyed having the ADO private package feeds. Allowing developers from other teams to share our work (and vice versa), without needing to worry about checking out our repositories or adding project references has been extremely valuable. By including native DLLs, we can now share all of our work across teams without worrying at all about what’s below the surface.