Trying to not need two separate solutions for x86 and x64 program
I have a program which needs to function in both an x86 and an x64 environment. It is using Oracle's ODBC drivers. I have a reference to Oracle.DataAccess.DLL. This DLL is different depending on whether the system is x64 or x86, though.
Currently, I have two separate solutions and I am maintaining the code on both. This is atrocious. I was wondering what the proper solution is?
I have my p开发者_Python百科latform set to "Any CPU." and it is my understanding that VS should compile the DLL to an intermediary language such that it should not matter if I use the x86 or x64 version. Yet, if I attempt to use the x64 DLL I receive the error "Could not load file or assembly 'Oracle.DataAccess, Version=2.102.3.2, Culture=neutral, PublicKeyToken=89b483f429c47342' or one of its dependencies. An attempt was made to load a program with an incorrect format."
I am running on a 32 bit machine, so the error message makes sense, but it leaves me wondering how I am supposed to efficiently develop this program when it needs to work on x64.
Thanks.
This is purely a deployment problem, you should never have to maintain different projects. It is an awkward one though, and boo on Oracle for not taking care of this themselves. Another consideration is that this assembly really should be ngen-ed on the target machine. Some options
- Create two installers, one for x64 and one for x86. The customer picks the right one, based on the operating system she uses. Simple enough, you just copy the right file.
- Deploy both assemblies to the GAC. Now it is automatic, .NET picks the right one on either type of machine. Big companies should almost always use the GAC so they can deploy security updates, not sure why Oracle doesn't do this.
- Deploy the assemblies to a x86 and x64 subdirectory of the install directory. You'll need to write an AppDomain.AssemblyResolve event handler that, based on the value of IntPtr.Size, picks the right directory.
- Change the target platform on your EXE project to x86. Given that your code needs to work on a 32-bit machine as well as on a 64-bit machine, there isn't/shouldn't be a reason to build for AnyCPU.
This is a working solution for your problem:
Add the 2 DLL's (x86 and x64) to your solution in a subfolder. Make them "Copy if newer"
Reference the correct DLL you use for development for debugging from the 2 DLL's you added. Make it Copy Local=false.
What this does is that when you app starts the DLL is not autoloaded. It will not be loaded until you use a Type from that assembly. Once that happens an event will be triggered in .Net that asks where it can find your assembly.
So sometime before the first use of that assembly make sure you attach yourself to that event.
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
In the content of the handler make sure you load the DLL (x86 or x64) when it asks for it.
static System.Reflection.Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args) {
if (args.Name.Equals("MyFullAssemblyName")) {
var path = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
if (IntPtr.Size > 4) {
var dll = System.IO.Path.Combine(path, @"MySubDir\MyDLL_x64.dll");
return System.Reflection.Assembly.LoadFile(dll);
}
else {
var dll = System.IO.Path.Combine(path, @"MySubDir\MyDLL.dll");
return System.Reflection.Assembly.LoadFile(dll);
}
}
return null;
}
Voila. You can now run your app as both 32 bit and 64 bit.
Alternatively to adding the DLLs in a subfolder, you can make them as Embedded Resources, and then load them like this:
static System.Reflection.Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args) {
if (args.Name.Equals("MyFullAssemblyName")) {
var ass = Assembly.GetExecutingAssembly();
if (IntPtr.Size > 4) {
var strm = ass.GetManifestResourceStream("the.resource.name.for.MyDLL_x64.dll");
var data = new byte[strm.Length];
strm.Read(data, 0, data.Length);
return Assembly.Load(data);
}
else {
var strm = ass.GetManifestResourceStream("the.resource.name.for.MyDLL.dll");
var data = new byte[strm.Length];
strm.Read(data, 0, data.Length);
return Assembly.Load(data);
}
}
return null;
}
This does not work for all assemblies. Some "hybrid" assemblies tends to fail unless they are loaded from disk (can be solved by writing them to disk just before loading).
If you're running on a 32-bit machine, then you have to load the 32-bit version of the Oracle DLL. A 32-bit program can't reference a 64-bit DLL. And, a 64-bit program can't reference a 32-bit DLL.
"Any CPU" is the correct target if you have multiple versions of the external DLL. The trick is making sure that the proper Oracle DLL is located and loaded. Your best bet is to locate the 64-bit version of the DLL on your 32-bit system and rename it so that the runtime can't find it.
Using AnyCPU with native early bindings is just not going to work, for that you need two separate solutions and builds as you saw. You have to get hold of a 64-bit system to develop or at least test x64 compiled dlls on.
However, with late binding, you can use AnyCPU and System properties to figure out what architecture you're running as and link to the correct dll, if you keep the named like Oracle.DataAccess.x86.dll. If they're installed into the GAC, it's even easier, you can bind without even bothering to test for architecture first, but I believe you still have to late bind.
Note that VMware can run a 64-bit guest on a 32-bit host, if you really can't be bothered to reinstall Windows.
You shold be able to configure the same solution to build x86/x64 versions separately. You may also need to add post build steps to copy correct version of DLL to corresponding output folders...
At least if you have to build 2 solutions - use the same source (add files as refernce to second solution, not copy into second solution).
精彩评论