Copy native dependencies locally in a Visual Studi

2020-03-09 07:43发布

问题:

I have a mixed mode C++ project producing a managed dll assembly exporting some CLR classes (call it Managed.dll). This project is using a native dll, (call it Native.dll).

When I reference the Managed.dll from another project producing Client.exe, everything works as expected, except than I need to manually copy the Native.dll in the same folder as Client.exe.

If there a way to convince VS to copy locally (in the bin folder of Client.exe) not only Managed.dll but Native.dll as well?

I have tried to include Native.dll as a dependency assembly in the manifest but this didn't help.

Edit

Managed.dll is going to be a redistributable assembly. It will be installed in a folder in "C:\Program Files.....". When a developer using Visual Studio adds a reference to Managed.dll, Native.dll should be also copied in the \bin folder of his project.

回答1:

There are several ways to tell the VS to copy dlls to the destination folder:

1.Add the dll as a resource of the project. And tell the VS to copy it if the dll is newer

2.Add a new project that reference to the dll project, and set the OutDir to the folder you want. This project does nothing but copy the dll.

3.Use a PostBuildEvent in vcxproj file

<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
  <ClCompile>
  </ClCompile>
  <Link>
  </Link>
  <PostBuildEvent>
    <Command>
      echo off
      mkdir "$(ProjectDir)..\..\bin\$(Configuration)\"
      copy "$(OutDir)xxx.dll" "$(ProjectDir)..\..\lib\$(Configuration)\"
      echo on
    </Command>
  </PostBuildEvent>
</ItemDefinitionGroup>

4.Use a PreBuildEvent in vcxproj file

5.Use CustomBuild in vcxproj file

<ItemGroup>
<CustomBuild Include="..\..\xxx.dll">
  <FileType>Document</FileType>
  <Command>
   call mkdir &quot;$(OutDir)&quot; 2&gt;nul &amp; 
   copy /Y &quot;..\..\xxx.dll&quot; &quot;$(OutDir)xxx.dll&quot;
  </Command>
  <Message>Copying xxx.dll to $(OutDir)\xxx.dll</Message>
  <Outputs>$(OutDir)\xxx.dll</Outputs>
</CustomBuild>
</ItemGroup>

6.Use a makefile and copy dll in makefile. and use nmake to build

7.Write a bat file that do the copy job, and invoke the bat file as in 3-6

8.Use script such as python, which can also download the dll from internet. And invoke the py file as in 3-6.

9.Other build tools can help too, such as gradle

10.Make it a NuGet plugin

11.Sometimes I just write a bat, and execute the bat manually.

Update 01 (Self extract dll example):

1.Add you native dll as resource of managed dll

2.Add this init() method

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;

namespace DllSelfExtract
{
    public class SelfExtract
    {
        public static void Init()
        {
            String managedDllPath = System.Reflection.Assembly.GetExecutingAssembly().CodeBase;
            String nativeDllPath = managedDllPath.Replace("file:///", "").Replace("DllSelfExtract.DLL", "TestDll.dll");
            if(!File.Exists(nativeDllPath))
            {
                Stream dllIn = Assembly.GetExecutingAssembly().GetManifestResourceStream("DllSelfExtract.TestDll.dll");
                if (dllIn == null) return;

                using (Stream outFile = File.Create(nativeDllPath))
                {
                    const int sz = 4096;
                    byte[] buf = new byte[sz];
                    while (true)
                    {
                        int nRead = dllIn.Read(buf, 0, sz);
                        if (nRead < 1)
                            break;
                        outFile.Write(buf, 0, nRead);
                    }
                }
            }

            //LoadLibrary Here
        }
    }
}

3.In project that use your managed dll, invoke init() method first

SelfExtract.Init();

Update 02 (NuGet example):

1.Create a new NuGet project

2.Place the managed assemblies in the /lib directory

3.Place the non-managed shared libraries and related files in the /build subdirectory and rename all non-managed *.dll to *.dl_

4.Add a custom .targets file in the /build subdirectory with something like the following contents :

<?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)*">
      <TargetPath></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>

5.Add build rule for build folder in Package.nuspec

<files>
  <file src="lib\" target="lib" />
  <file src="tools\" target="tools" />
  <file src="content\" target="content" />
  <file src="build\" target="build" />
</files>

6.Build the package

7.In your other C# project just add this NuGet package.



回答2:

Using /ASSEMBLYLINKRESOURCE option in linker properties seems to be the simplest solution. It makes Visual Studio consider the native dll as a part of the assembly. Also, according to documentation provided by Microsoft, allows the native dll to be installed in the Global Assembly Cache

To set this linker option in a Visual C++ project:

  • Right click on the project name and select Properties
  • Select the Linker folder
  • In the Input property page find the Assembly Link Resource option
  • Write the file name of the native assembly e.g. MyNative.dll

You will need a Post Build event to copy the native dll to the output folder.

Referencing the managed assembly from any other Visual Project, forces the native dll to be copied together with the managed assembly in the /bin folder.