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

标签 c# .net msbuild pinvoke

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

如果您将两个 Z3 DLL 放在一个目录中,仅引用 Microsoft.Z3.dll,然后使用 msbuild 编译您的项目,您将在输出目录中获得这两个 DLL,而根本不引用 libz3.dll!查看 msbuild /verbosity:diag 生成的输出,我看到以下对 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)

这会导致它被复制:

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 从目录中取出,构建将失败并出现以下错误:

"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]

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

问题:

  1. Microsoft.Z3.dll 有什么特别之处,使其能够在构建过程中要求 libz3.dll 位于同一目录中?它是用某些标志编译的吗?
  2. 如何将此效果添加到我自己的托管包装库中?
  3. 有没有办法从 Microsoft.Z3.dll 中消除这种影响,或者我必须以不同的方式重新编译它?

最佳答案

这就是 CSC(C# 编译器)所说的 "link resource" 。它适用于任何类型的文件。

例如,如果您的 DLL 项目中有此类代码:

using System.Runtime.InteropServices;

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

从 Windows DLL 导出的 C 代码:

#include "stdafx.h"

STDAPI ReturnValue(HRESULT value)
{
    return value;
}

您可以像这样构建 .NET DLL:

"<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 复制到一边。

请注意,据我所知,Visual Studio 不支持此链接资源内容。

另外一个缺点是,如果您想支持多位数,则必须提供两个 .NET DLL,一个用于 x64,一个用于 x86,每个都嵌入其 native 对应项(否则您必须复制所有DllImport 等)。

关于围绕非托管 DLL 的 C# 包装器库要求在构建期间非托管 DLL 位于同一目录中,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52674116/

相关文章:

c# - 需要用户控件中的接受按钮之类的东西

java - RSA加密中的 "A device attached to the system is not functioning"错误是什么?

asp.net - 更新绝对过期的缓存项

c# - 如何将外部 XML 包含到 XML 文件中——两个文件应该是什么样子

c# - VS 2017 添加 > Docker 支持中断构建 - "CleanWorkspace"任务意外失败(控制台应用程序)

c# - 如何通过 OpenXML 在当前事件的 Word 文档中设置自定义属性

c# - HttpWebResponse.GetResponseStream() (似乎)毫无异常(exception)地失败

.net - 我可以导入命名空间,但无法使用完全限定的类名

msbuild - 项目类型 'OutputPath' 缺少必需的属性 'PackageReference'

c# - 增加 version.props 中的数字