Targeting both 32bit and 64bit with Visual Studio in same solution/project
I have a little dilemma on how to set up my visual studio builds for multi-targeting.
Background: c# .NET v2.0 with p/invoking into 3rd party 32 bit DLL's, SQL compact v3.5 SP1, with a Setup project. Right now, the platform target is set to x86 so it can be run on Windows x64.
The 3rd party company has just released 64 bit versions of their DLL's and I want to build a dedicated 64bit program.
This raises some questions which I haven't got the answers to yet. I want to have the exact same code base. I must build with references to either the 32bit set of DLL's or 64bit DLL's. (Both 3rd party and SQL Server Compact)
Can this be solved with 2 new sets of configurations (Debug64 and Release64) ?
Must I create 2 separate setup projects(std. visual studio projects, no Wix or any other utility), or can this be solved within the same .msi?
Any ideas and/or recommendations would be welcomed.
Solution 1:
Yes, you can target both x86 and x64 with the same code base in the same project. In general, things will Just Work if you create the right solution configurations in VS.NET (although P/Invoke to entirely unmanaged DLLs will most likely require some conditional code): the items that I found to require special attention are:
- References to outside managed assemblies with the same name but their own specific bitness (this also applies to COM interop assemblies)
- The MSI package (which, as has already been noted, will need to target either x86 or x64)
- Any custom .NET Installer Class-based actions in your MSI package
The assembly reference issue can't be solved entirely within VS.NET, as it will only allow you to add a reference with a given name to a project once. To work around this, edit your project file manually (in VS, right-click your project file in the Solution Explorer, select Unload Project, then right-click again and select Edit). After adding a reference to, say, the x86 version of an assembly, your project file will contain something like:
<Reference Include="Filename, ..., processorArchitecture=x86">
<HintPath>C:\path\to\x86\DLL</HintPath>
</Reference>
Wrap that Reference tag inside an ItemGroup tag indicating the solution configuration it applies to, e.g:
<ItemGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
<Reference ...>....</Reference>
</ItemGroup>
Then, copy and paste the entire ItemGroup tag, and edit it to contain the details of your 64-bit DLL, e.g.:
<ItemGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x64' ">
<Reference Include="Filename, ..., processorArchitecture=AMD64">
<HintPath>C:\path\to\x64\DLL</HintPath>
</Reference>
</ItemGroup>
After reloading your project in VS.NET, the Assembly Reference dialog will be a bit confused by these changes, and you may encounter some warnings about assemblies with the wrong target processor, but all your builds will work just fine.
Solving the MSI issue is up next, and unfortunately this will require a non-VS.NET tool: I prefer Caphyon's Advanced Installer for that purpose, as it pulls off the basic trick involved (create a common MSI, as well as 32-bit and 64-bit specific MSIs, and use an .EXE setup launcher to extract the right version and do the required fixups at runtime) very, very well.
You can probably achieve the same results using other tools or the Windows Installer XML (WiX) toolset, but Advanced Installer makes things so easy (and is quite affordable at that) that I've never really looked at alternatives.
One thing you may still require WiX for though, even when using Advanced Installer, is for your .NET Installer Class custom actions. Although it's trivial to specify certain actions that should only run on certain platforms (using the VersionNT64 and NOT VersionNT64 execution conditions, respectively), the built-in AI custom actions will be executed using the 32-bit Framework, even on 64-bit machines.
This may be fixed in a future release, but for now (or when using a different tool to create your MSIs that has the same issue), you can use WiX 3.0's managed custom action support to create action DLLs with the proper bitness that will be executed using the corresponding Framework.
Edit: as of version 8.1.2, Advanced Installer correctly supports 64-bit custom actions. Since my original answer, its price has increased quite a bit, unfortunately, even though it's still extremely good value when compared to InstallShield and its ilk...
Edit: If your DLLs are registered in the GAC, you can also use the standard reference tags this way (SQLite as an example):
<ItemGroup Condition="'$(Platform)' == 'x86'">
<Reference Include="System.Data.SQLite, Version=1.0.80.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139, processorArchitecture=x86" />
</ItemGroup>
<ItemGroup Condition="'$(Platform)' == 'x64'">
<Reference Include="System.Data.SQLite, Version=1.0.80.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139, processorArchitecture=AMD64" />
</ItemGroup>
The condition is also reduced down to all build types, release or debug, and just specifies the processor architecture.
Solution 2:
Let's say you have the DLLs build for both platforms, and they are in the following location:
C:\whatever\x86\whatever.dll
C:\whatever\x64\whatever.dll
You simply need to edit your .csproj file from this:
<HintPath>C:\whatever\x86\whatever.dll</HintPath>
To this:
<HintPath>C:\whatever\$(Platform)\whatever.dll</HintPath>
You should then be able to build your project targeting both platforms, and MSBuild will look in the correct directory for the chosen platform.
Solution 3:
Not sure of the total answer to your question - but thought I would point out a comment in the Additional Information section of the SQL Compact 3.5 SP1 download page seeing you are looking at x64 - hope it helps.
Due to changes in SQL Server Compact SP1 and additional 64-bit version support, centrally installed and mixed mode environments of 32-bit version of SQL Server Compact 3.5 and 64-bit version of SQL Server Compact 3.5 SP1 can create what appear to be intermittent problems. To minimize the potential for conflicts, and to enable platform neutral deployment of managed client applications, centrally installing the 64-bit version of SQL Server Compact 3.5 SP1 using the Windows Installer (MSI) file also requires installing the 32-bit version of SQL Server Compact 3.5 SP1 MSI file. For applications that only require native 64-bit, private deployment of the 64-bit version of SQL Server Compact 3.5 SP1 can be utilized.
I read this as "include the 32bit SQLCE files as well as the 64bit files" if distributing for 64bit clients.
Makes life interesting I guess.. must say that I love the "what appears to be intermittent problems" line... sounds a bit like "you are imagining things, but just in case, do this..."
Solution 4:
One .Net build with x86/x64 Dependencies
While all other answers give you a solution to make different Builds according to the platform, I give you an option to only have the "AnyCPU" configuration and make a build that works with your x86 and x64 dlls.
You have to write some plumbing code for this.
Resolution of correct x86/x64-dlls at runtime
Steps:
- Use AnyCPU in csproj
- Decide if you only reference the x86 or the x64 dlls in your csprojs. Adapt the UnitTests settings to the architecture settings you have chosen. It's important for debugging/running the tests inside VisualStudio.
- On Reference-Properties set Copy Local & Specific Version to false
- Get rid of the architecture warnings by adding this line to the first PropertyGroup in all of your csproj files where you reference x86/x64:
<ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch>None</ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch>
-
Add this postbuild script to your startup project, use and modify the paths of this script sp that it copies all your x86/x64 dlls in corresponding subfolders of your build bin\x86\ bin\x64\
xcopy /E /H /R /Y /I /D $(SolutionDir)\YourPathToX86Dlls $(TargetDir)\x86 xcopy /E /H /R /Y /I /D $(SolutionDir)\YourPathToX64Dlls $(TargetDir)\x64
--> When you would start application now, you get an exception that the assembly could not be found.
-
Register the AssemblyResolve event right at the beginning of your application entry point
AppDomain.CurrentDomain.AssemblyResolve += TryResolveArchitectureDependency;
withthis method:
/// <summary> /// Event Handler for AppDomain.CurrentDomain.AssemblyResolve /// </summary> /// <param name="sender">The app domain</param> /// <param name="resolveEventArgs">The resolve event args</param> /// <returns>The architecture dependent assembly</returns> public static Assembly TryResolveArchitectureDependency(object sender, ResolveEventArgs resolveEventArgs) { var dllName = resolveEventArgs.Name.Substring(0, resolveEventArgs.Name.IndexOf(",")); var anyCpuAssemblyPath = $".\\{dllName}.dll"; var architectureName = System.Environment.Is64BitProcess ? "x64" : "x86"; var assemblyPath = $".\\{architectureName}\\{dllName}.dll"; if (File.Exists(assemblyPath)) { return Assembly.LoadFrom(assemblyPath); } return null; }
- If you have unit tests make a TestClass with a Method that has an AssemblyInitializeAttribute and also register the above TryResolveArchitectureDependency-Handler there. (This won't be executed sometimes if you run single tests inside visual studio, the references will be resolved not from the UnitTest bin. Therefore the decision in step 2 is important.)
Benefits:
- One Installation/Build for both platforms
Drawbacks: - No errors at compile time when x86/x64 dlls do not match. - You should still run test in both modes!
Optionally create a second executable that is exclusive for x64 architecture with Corflags.exe in postbuild script
Other Variants to try out: - You don't need the AssemblyResolve event handler if you assure that the right dlls are copied to your binary folder at start (Evaluate Process architecture -> move corresponding dlls from x64/x86 to bin folder and back.) - In Installer evaluate architecture and delete binaries for wrong architecture and move the right ones to the bin folder.