如果可以选择,混合模式程序集与单独的互操作 DLL 的 pros/cons 是什么?

Given the choice, what are the pros/cons of mixed-mode assemblies vs. separate interop DLLs?

当同时提供 "mixed-mode assembly" 和 "separate interop dll" 版本的第 3 方组件时,每个版本的优缺点是什么?

一个很好的例子是System.Data.SQLite

上面的link是这样说的:

[Mixed-mode assembly] packages should only be used in cases where the assembly binary must be deployed to the Global Assembly Cache for some reason.

但是为什么?混合模式程序集在我的项目中似乎工作得很好,没有安装 GAC(只是 xcopy 到应用程序的 exe 目录)。少一个DLL真好。它 感觉 更整洁。那么有什么缺点呢?

反之亦然,为什么 would/should 人们偏爱双 DLL "native-dll + interop-dll" 版本?

Disclaimer: For a definite answer, you'd have to ask someone from the development team, but here's my best guess.

在标准配置中,托管程序集将尝试定位并加载它需要的本机 DLL。它将在依赖于平台的目录(x86x64)中搜索。然后它将加载在那里找到的 DLL,并继续向它抛出 P/Invoke 互操作。

这是与 .NET 中的本机库进行互操作的相当标准的过程 - 唯一的 custom System.Data.SQLite 代码是尝试定位 DLL 并加载的代码正确的版本。其余的都是普通的P/Invoke。但即使是与图书馆打交道,这也是司空见惯的做法。

这种方法的主要优点是库用户可以为AnyCPU平台构建他的项目,处理器架构将在 运行 时解决 - 如果您在 x86 或 x64 上 运行,一切都会按预期工作,前提是两个本机 DLL 都可用。库作者得到的支持请求更少。


让我们将其与混合模式方法进行比较。混合模式 DLL 有一些缺点,主要的缺点是它 必须 是特定于平台的。因此,如果您选择这种方法,则必须将您的应用程序绑定到特定平台。如果你想同时支持 x86 和 x64,你必须构建单独的版本,每个版本 linking 到 System.Data.SQLite.

的正确版本

如果你没有完全正确,那么 boom。更糟糕的是,如果你在你的 x64 开发机器上为 AnyCPU 平台构建它,乍一看它 似乎 工作正常,但它会崩溃在您客户的旧 x86 机器上。不得不处理这类问题并不好,使用单独的 DLL 的简单解决方案完全解决了这个问题。

我能想到的另一个缺点是无法从内存中加载程序集,但这至多应该是一个小小的不便,因为这也适用于本机 DLL。

至于 GAC,我的猜测是,在单独的本机程序集的情况下,搜索它们的代码在某些情况下可能无法找到它们,或者它可能会找到不同版本的 DLL。 System.Data.SQLite 中的代码非常努力地尝试定位本机 DLL,但是混合模式 DLL 本来就没有这样的问题,所以失败不是一种选择。


尽管如此,你说:

It feels tidier.

让我们仔细看看这个。 :)

System.Data.SQLite 有相当不寻常的 混合模式互操作方法。通常,您会使用 C++/CLI 构建混合模式程序集。这使您可以将来自 相同 C++/CLI 项目的托管代码和本机代码合并到一个 DLL 中,并使用所谓的 C++ Interop 来处理一个到另一个的调用managed/unmanaged 屏障的一侧。这样做的好处是它比P/Invoke更轻更快,因为它可以避免大部分编组。

System.Data.SQLite 做了一些不同的事情:它将其 C# 代码构建到 netmodule,然后 uses the C++ linker 到 link netmodule 与本机 SQLite 代码。这导致混合模式装配。

有趣的是,unline C++/CLI,C# 没有 direct 机制来在同一个混合模式程序集中调用本机代码,因为 C# 并不是真正想要的首先用于混合模式程序集。因此,来自这个最终汇编的 C# 代码将简单地 P/Invoke 本身 。是的,你没有看错。这仍然感觉整洁吗?尽管如此,这是一个聪明的技巧。 :)

看看UnsafeNativeMethods.cs中的代码:

#if PLATFORM_COMPACTFRAMEWORK
    //
    // NOTE: On the .NET Compact Framework, the native interop assembly must
    //       be used because it provides several workarounds to .NET Compact
    //       Framework limitations important for proper operation of the core
    //       System.Data.SQLite functionality (e.g. being able to bind
    //       parameters and handle column values of types Int64 and Double).
    //
    internal const string SQLITE_DLL = "SQLite.Interop.099.dll";
#elif SQLITE_STANDARD
    //
    // NOTE: Otherwise, if the standard SQLite library is enabled, use it.
    //
    internal const string SQLITE_DLL = "sqlite3";
#elif USE_INTEROP_DLL
      //
    // NOTE: Otherwise, if the native SQLite interop assembly is enabled,
    //       use it.
    //
    internal const string SQLITE_DLL = "SQLite.Interop.dll";
#else
    //
    // NOTE: Finally, assume that the mixed-mode assembly is being used.
    //
    internal const string SQLITE_DLL = "System.Data.SQLite.dll";
#endif

如果您想查看构建过程,请查看 C++ MSBuild 项目。这里有一些 linker 选项显示了网络模块的使用(在 <AdditionalDependencies> 中):

<Link>
  <AdditionalOptions>$(INTEROP_ASSEMBLY_RESOURCES) %(AdditionalOptions)</AdditionalOptions>
  <AdditionalLibraryDirectories>$(INTEROP_LIBRARY_DIRECTORIES)</AdditionalLibraryDirectories>
  <AdditionalDependencies>$(ProjectDir)..\bin$(ConfigurationYear)$(Configuration)Module\bin\System.Data.SQLite.netmodule $(INTEROP_LIBRARY_DEPENDENCIES);%(AdditionalDependencies)</AdditionalDependencies>
  <Version>$(INTEROP_LINKER_VERSION)</Version>
  <GenerateDebugInformation>true</GenerateDebugInformation>
  <GenerateMapFile>true</GenerateMapFile>
  <MapExports>true</MapExports>
  <SubSystem>Windows</SubSystem>
  <OptimizeReferences>true</OptimizeReferences>
  <EnableCOMDATFolding>true</EnableCOMDATFolding>
  <LinkTimeCodeGeneration>UseLinkTimeCodeGeneration</LinkTimeCodeGeneration>
  <TargetMachine>MachineX64</TargetMachine>
  <CLRUnmanagedCodeCheck>true</CLRUnmanagedCodeCheck>
  <KeyFile>$(INTEROP_KEY_FILE)</KeyFile>
  <DelaySign>true</DelaySign>
</Link>