When a 3rd-party component is offered in both "mixed-mode assembly" and "separate interop dll" versions, what are the pros and cons of each?
A good example is System.Data.SQLite.
The above link has this to say:
[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.
But WHY? The mixed-mode assembly seems to work just fine in my projects, with no GAC installation (just xcopy to the application's exe directory). It's nice to have one less DLL. It feels tidier. So what are the cons?
Vice-versa, why would/should one ever favor the two-DLL "native-dll + interop-dll" version?
Interop assemblies are . NET Framework assemblies that bridge between managed and unmanaged code, mapping COM object members to equivalent . NET Framework managed members. Interop assemblies created by Visual Basic .
Mixed mode means that the assembly can run managed and unmanaged code.
The Illumina InterOp libraries are a set of common routines used for reading and writing InterOp metric files. These metric files are binary files produced during a run providing detailed statistics about a run.
Disclaimer: For a definite answer, you'd have to ask someone from the development team, but here's my best guess.
In the standard configuration, the managed assembly will try to locate and load the native DLL it needs. It will search in the platform-dependent directories (x86
or x64
). It will then load the DLL it found there and proceed to throw P/Invoke interop at it.
This is a fairly standard procedure for interoperating with native libraries in .NET - the only custom System.Data.SQLite code is the one that tries to locate the DLL and loads the right version. The rest is plain P/Invoke. But even this is commonplace practice when you deal with libraries.
The major advantage of this approach is that the library user can build his project for the AnyCPU platform, and the processor architecture will be resolved at runtime - everything will work as expected, should you run on x86 or x64, provided both native DLLs are available. And the library author gets less support requests.
Let's compare it with the mixed-mode approach. A mixed-mode DLL has a few disadvantages, the major one being it must be platform-specific. So if you choose this approach, you'll have to tie your application to a particular platform. If you want to support both x86 and x64, you'll have to build separate versions, each one linking to the correct version of System.Data.SQLite.
If you don't get this perfectly right, then boom. Worse, if you build it for the AnyCPU platform on your x64 development machine, it will seem to work alright at first glance, but it will implode on your customer's old x86 box. Having to deal with this kind of issues isn't nice, and the simple solution of using separate DLLs solves the problem entirely.
The other disadvantage I can think of is the inability to load the assembly from memory, but this should be at most a minor inconvenience, since this applies to native DLLs too.
As for the GAC, my guess would be that in the case of separate native assemblies, the code which searches for them could fail to locate them in some cases, or it could locate a different version of the DLL. The code in System.Data.SQLite tries really hard to locate the native DLL, but there's no such problem with the mixed-mode DLL in the first place, so failure is not an option.
Nevertheless, you say:
It feels tidier.
Let's take a closer look at this. :)
System.Data.SQLite has quite an unusual approach to mixed-mode interop. Usually, you'd use C++/CLI to build a mixed-mode assembly. This lets you combine managed and native code from the same C++/CLI project into a single DLL and uses the so-called C++ Interop to deal with calls from the one to the other side of the managed/unmanaged barrier. The advantage of this is that it's lighter and faster than P/Invoke, as it can avoid most of the marshaling.
System.Data.SQLite does something different: it builds its C# code into a netmodule, and then uses the C++ linker to link the netmodule with the native SQLite code. This results in a mixed-mode assembly.
The interesting thing is that, unline C++/CLI, C# has no direct mechanism to invoke native code in the same mixed-mode assembly, as C# wasn't really intended to be used in mixed-mode assemblies in the first place. So, the C# code from this final assembly will simply P/Invoke itself. Yes, you read that right. Does this still feel as tidy? Nevertheless, it's a clever hack. :)
Take a look at the code in 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
If you'd like to see the build process, take a look at the C++ MSBuild projects. Here are some linker options showing the use of netmodules (in <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>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With