Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Anycpu nuget package the requires either 32 bit or 64 bit package

I have a (anycpu) nuget package "my_shared_library" that must reference a nuget package "non_anycpu_dependency" that comes as either 32 bit or 64 bit (it is database access dll).

I want both 32 bit and 64 bit applications to be able to use "my_shared_library".

How can I have both 32 bit and 64 bit programs use "my_shared_library"?

I was thinking I would either have to have 2 versions of "my_shared_library" or maybe there is some way at run-time to pick the correct 32bit/64bit nuget package of "non_anycpi_dependency" based on the run-time bit-ness.

Has anyone solved this problem? Since most database dlls are not anycpu I would think this is a common problem.

Thanks in advance,

like image 399
anthonybell Avatar asked Feb 17 '15 19:02

anthonybell


2 Answers

Has anyone solved this problem?

Yes, Microsoft has. They needed to solve this exact same problem for their SQL Server Compact package. It has a single managed assembly, System.Data.SqlServerCe.dll, acting like the adapter to a managed program, and a bunch of native DLLs that implement the actual database engine. The way they bind to the native DLL entrypoints is not something I ever recommend, I'll work from the assumption that you use pinvoke. I recommend you use this technique to ensure that the client program can run AnyCPU.

Repeating the essence of that post, you want to execute this code somewhere in an initialization method or a static constructor:

public static void SetupDatabaseBinaries() {
    var path = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
    path = Path.Combine(path, IntPtr.Size == 8 ? "amd64" : "x86");
    bool ok = SetDllDirectory(path);
    if (!ok) throw new System.ComponentModel.Win32Exception();
}

[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern bool SetDllDirectory(string path);

The only non-trivial step is to get the user's project to copy the native DLL(s) into the project's amd64/x86 directories. This requires a post-build step, just the way the Compact package does it. You'll want to download the Nuget package to see how they did this, it isn't very trivial. Run VS elevated, create a dummy Console mode project and retrieve the Nuget package for Sql Server Compact. After it is installed, use Project + Properties, Build events tab and note how it added this post build event:

if not exist "$(TargetDir)x86" md "$(TargetDir)x86"
xcopy /s /y "$(SolutionDir)packages\Microsoft.SqlServer.Compact.4.0.8876.1\NativeBinaries\x86\*.*" "$(TargetDir)x86"
if not exist "$(TargetDir)amd64" md "$(TargetDir)amd64"
xcopy /s /y "$(SolutionDir)packages\Microsoft.SqlServer.Compact.4.0.8876.1\NativeBinaries\amd64\*.*" "$(TargetDir)amd64"

Nothing much to it, it doesn't do anything more than creating the x86 and amd64 subdirectories and copies the DLLs into them. The tricky part is having your Nuget package add this build event. Have a look-see at the package folder for the magic that accomplished this. You probably want to follow the example as closely as possible to avoid mistakes. Look at the packages\Microsoft.SqlServer.Compact.4.0.8876.1\tools directory, it contains two PowerShell script files that do this. Install.ps1 runs at the end of the install. The function that creates the post-build step is in VS.psm1, Add-PostBuildEvent. Good luck with it.

like image 154
Hans Passant Avatar answered Nov 15 '22 11:11

Hans Passant


Once you've referenced a package that is targeted for a specific bitness, you are stuck. You might be able to get enough indirection to compile, but you'll end up with a BadImageFormatException at runtime.

What you can do is create an AnyCPU assembly that contains interfaces that will match the concrete implementations found in the specific assemblies that you have. Then all of your AnyCPU assemblies should only reference the interfaces.

In your EXE that is going to drive the application, you must choose whether it will be 64 or 32 bit. In this EXE assembly, you reference the original 32 or 64 bit assembly (or nuget package) as appropriate.

To use this solution, a dependency injection framework like Autofac, StructureMap, or Unity will assist in reducing the pain of managing providing the concrete implementations to your classes.

like image 30
Steve Mitcham Avatar answered Nov 15 '22 11:11

Steve Mitcham