围绕非托管DLL的C#包装器库要求在构建过程中非托管DLL位于同一目录中

问题描述:

通常,当使用PInvoke引用封装了非托管DLL的托管DLL时,必须分别引用这两个DLL-托管的csproj中的托管DLL作为标准<Reference/>,而非托管的DLL链接为链接的<content/>(如概述) 此处).但是,我最近遇到了一个托管包装器库,它不仅在构建过程中自动复制非托管DLL,而且在同一目录中不存在非托管DLL时,实际上会产生一个构建错误!这是 Microsoft.Z3库,该库具有托管的DLL(Microsoft.Z3.dll)用PInvoke包装非托管DLL(libz3.dll),以便可以在C#中使用该库.

Usually when referencing a managed DLL that wraps an unmanaged DLL with PInvoke, you have to reference those two DLLs separately - the managed one as a standard <Reference/> in your csproj and the unmanaged one as linked <content/> (as outlined here). However, I recently came across a managed wrapper library which not only auto-copies the unmanaged DLL along with it during the build, but actually produces a build error when the unmanaged DLL is not present in the same directory! This is the Microsoft.Z3 library, which has a managed DLL (Microsoft.Z3.dll) wrapping an unmanaged DLL (libz3.dll) with PInvoke so you can use the library in C#.

如果将两个Z3 DLL放在一个目录中,仅引用Microsoft.Z3.dll,然后使用msbuild编译项目,则将在输出目录中获得这两个DLL,而根本不引用libz3.dll!在msbuild /verbosity:diag产生的输出中,我看到以下对libz3.dll的引用:

If you put the two Z3 DLLs together in a directory, referencing only Microsoft.Z3.dll, then compile your project with msbuild, you'll get both DLLs in the output directory without referencing libz3.dll at all! Looking in the output produced by msbuild /verbosity:diag, I see the following references to libz3.dll:

Primary reference "Microsoft.Z3, Version=4.7.1.0, Culture=neutral, PublicKeyToken=9c8d792caae602a2". (TaskId:9)
      Resolved file path is "C:\Users\ahelwer\source\test\Framework\lib\z3\Microsoft.Z3.dll". (TaskId:9)
      Reference found at search path location "{HintPathFromItem}". (TaskId:9)
      Found embedded scatter file "libz3.dll". (TaskId:9)
      The ImageRuntimeVersion for this reference is "v4.0.30319". (TaskId:9)

...

Output Item(s): 
      _ReferenceScatterPaths=
          C:\Users\ahelwer\source\test\Framework\lib\z3\libz3.dll
                  CopyLocal=true
                  FusionName=
                  HintPath=lib\z3\Microsoft.Z3.dll
                  OriginalItemSpec=C:\Users\ahelwer\source\test\Framework\lib\z3\Microsoft.Z3.dll
                  ResolvedFrom={HintPathFromItem}
                  Version=4.7.1.0 (TaskId:9)

以某种方式导致它被复制:

Which somehow leads to it being copied:

Task "Copy" (TaskId:22)
  Task Parameter:
      SourceFiles=
          C:\Users\ahelwer\source\test\Framework\lib\z3\Microsoft.Z3.dll
                  CopyLocal=true
                  FusionName=Microsoft.Z3, Version=4.7.1.0, Culture=neutral, PublicKeyToken=9c8d792caae602a2
                  HintPath=lib\z3\Microsoft.Z3.dll
                  ImageRuntime=v4.0.30319
                  OriginalItemSpec=Microsoft.Z3
                  ReferenceSourceTarget=ResolveAssemblyReference
                  ResolvedFrom={HintPathFromItem}
                  Version=4.7.1.0
          C:\Users\ahelwer\source\test\Framework\lib\z3\libz3.dll
                  CopyLocal=true
                  FusionName=
                  HintPath=lib\z3\Microsoft.Z3.dll
                  OriginalItemSpec=C:\Users\ahelwer\source\test\Framework\lib\z3\Microsoft.Z3.dll
                  ResolvedFrom={HintPathFromItem}
                  Version=4.7.1.0 (TaskId:22)
  Task Parameter:
      DestinationFiles=
          bin\Debug\Microsoft.Z3.dll
                  CopyLocal=true
                  FusionName=Microsoft.Z3, Version=4.7.1.0, Culture=neutral, PublicKeyToken=9c8d792caae602a2
                  HintPath=lib\z3\Microsoft.Z3.dll
                  ImageRuntime=v4.0.30319
                  OriginalItemSpec=Microsoft.Z3
                  ReferenceSourceTarget=ResolveAssemblyReference
                  ResolvedFrom={HintPathFromItem}
                  Version=4.7.1.0
          bin\Debug\libz3.dll
                  CopyLocal=true
                  FusionName=
                  HintPath=lib\z3\Microsoft.Z3.dll
                  OriginalItemSpec=C:\Users\ahelwer\source\test\Framework\lib\z3\Microsoft.Z3.dll
                  ResolvedFrom={HintPathFromItem}
                  Version=4.7.1.0 (TaskId:22)

它变得更加神秘,因为如果我将libz3.dll移出目录,构建将失败,并显示以下错误:

It gets more mysterious, because if I take libz3.dll out of the directory, the build fails with the following error:

"C:\Users\ahelwer\source\test\Framework\FrameworkTest.csproj" (default target) (1) ->
(_CopyFilesMarkedCopyLocal target) ->
  C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\MSBuild\15.0\bin\Microsoft.Common.CurrentVersion.targe
ts(4358,5): error MSB3030: Could not copy the file "C:\Users\ahelwer\source\test\Framework\lib\z3\libz3.dll" because it
 was not found. [C:\Users\ahelwer\source\test\Framework\FrameworkTest.csproj]

即使我以标准方式在csproj中以<content/>引用libz3.dll!

Even if I reference libz3.dll in the standard way with <content/> in my csproj!

问题:

  1. Microsoft.Z3.dll有什么特别之处,使它可以在构建过程中要求libz3.dll位于同一目录中?它带有某些标志吗?
  2. 如何将这种效果添加到我自己的托管包装器库中?
  3. 是否可以从Microsoft.Z3.dll中删除此效果,还是必须以其他方式重新编译它?

这是CSC(C#编译器)所称的.它适用于任何类型的文件.

This is what's CSC (the C# compiler) calls a "link resource". It works with any type of file.

例如,如果您在DLL项目中有这种代码:

So for example, if you have this kind of code in a DLL project:

using System.Runtime.InteropServices;

namespace Microsoft.Z3
{
    public static class DoSomething
    {
        [DllImport("libz3.dll")]
        public static extern int ReturnValue(int value);
    }
}

这是从Windows DLL导出的C代码:

And this C code exported from a Windows DLL:

#include "stdafx.h"

STDAPI ReturnValue(HRESULT value)
{
    return value;
}

您可以这样构建.NET DLL:

you can build the .NET DLL like this:

"<path to csc.exe>\csc.exe" DoSomething.cs -out:Microsoft.Z3.dll -target:library -linkresource:<path to libz3.dll>\libz3.dll

现在,当您引用此新的Microsoft.Z3.dll时,它的行为与真实的Z3事物相同,它将自动将libz3.dll复制到一边.

Now, when you reference this new Microsoft.Z3.dll, it will behave the same way as the real Z3 thing, it will copy the libz3.dll aside automatically.

请注意AFAIK,Visual Studio不支持此链接资源.

Note AFAIK, Visual Studio has no support for this linkresource stuff.

另外一个缺点是,如果要支持多个位,则必须附带两个.NET DLL,一个用于x64,一个用于x86,每个都嵌入其本机副本(否则您必须复制所有DllImport等等.

Plus one other drawback is if you want to support multiple bitnesses, you'll have to ship two .NET DLL, one for x64 and one for x86, each one embedding its native counterpart (or you'll have to duplicate all DllImport stuff etc.).