Conditionally use 32/64 bit reference when building in Visual Studio

I have a project that builds in 32/64-bit and has corresponding 32/64-bit dependencies. I want to be able to switch configurations and have the correct reference used, but I don't know how to tell Visual Studio to use the architecture-appropriate dependency.

Maybe I'm going about this the wrong way, but I want to be able to switch between x86 and x64 in the configuration dropdown, and have the referenced DLL be the right bitness.

72089 次浏览

AFAIK, if your project requires references that are 32-bit or 64-bit specific (i.e. COM-interop assemblies), and you have no interest in manually editing the .csproj file, then you'll have to create separate 32-bit and 64-bit projects.

I should note that the following solution is untested, but should work. If you are willing to manually edit the .csproj file, then you should be able to achieve the desired result with a single project. The .csproj file is just an MSBuild script, so for a full reference, look here. Once you open the .csproj file in an editor, locate the <Reference> elements. You should be able to split these elements out in to 3 distinct item groups: references that aren't platform specific, x86-specific references, and x64-specific references.

Here is an example that assumes your project is configured with target platforms named "x86" and "x64"

<!-- this group contains references that are not platform specific -->
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<!-- any other references that aren't platform specific -->
</ItemGroup>


<!-- x86 specific references -->
<ItemGroup Condition=" '$(Platform)' == 'x86' ">
<Reference Include="MyComAssembly.Interop">
<HintPath>..\..\lib\x86\MyComAssembly.Interop.dll</HintPath>
</Reference>


<!-- any additional x86 specific references -->
</ItemGroup>


<!-- x64 specific referneces -->
<ItemGroup Condition=" '$(Platform)' == 'x64' ">
<Reference Include="MyComAssembly.Interop">
<HintPath>..\..\lib\x64\MyComAssembly.Interop.dll</HintPath>
</Reference>


<!-- any additional x64 specific references -->
</ItemGroup>

Now, when you set your project/solution build configuration to target the x86 or x64 platform, it should include the proper references in each case. Of course, you'll need to play around with the <Reference> elements. You could even setup dummy projects where you add the x86 and x64 references, and then just copy the necessary <Reference> elements from those dummy project files to your "real" project file.


Edit 1
Here's a link to the common MSBuild project items, which I accidentally left out from the original post: http://msdn.microsoft.com/en-us/library/bb629388.aspx

Here is what I've done in a previous project, which will require the manual edition of the .csproj file(s). You also need separate directories for the different binaries, ideally siblings of each other, and with the same name as the platform you are targeting.

After adding a single platform's references to the project, open the .csproj in a text editor. Before the first <ItemGroup> element within the <Project> element, add the following code, which will help determine which platform you're running (and building) on.

<!-- Properties group for Determining 64bit Architecture -->
<PropertyGroup>
<CurrentPlatform>x86</CurrentPlatform>
<CurrentPlatform Condition="'$(PROCESSOR_ARCHITECTURE)'=='AMD64' or '$(PROCESSOR_ARCHITEW6432)'=='AMD64'">AMD64</CurrentPlatform>
</PropertyGroup>

Then, for your platform specific references, you make changes such as the following:

<ItemGroup>
<Reference Include="Leadtools, Version=16.5.0.0, Culture=neutral, PublicKeyToken=9cf889f53ea9b907, processorArchitecture=x86">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\Lib\Leadtools\$(CurrentPlatform)\Leadtools.dll</HintPath>
</Reference>
<Reference Include="Leadtools.Codecs, Version=16.5.0.0, Culture=neutral, PublicKeyToken=9cf889f53ea9b907, processorArchitecture=x86">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\Lib\Leadtools\$(CurrentPlatform)\Leadtools.Codecs.dll</HintPath>
</Reference>
<Reference Include="Leadtools.ImageProcessing.Core, Version=16.5.0.0, Culture=neutral, PublicKeyToken=9cf889f53ea9b907, processorArchitecture=x86">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\Lib\Leadtools\$(CurrentPlatform)\Leadtools.ImageProcessing.Core.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Data.Entity" />
<!--  Other project references -->
</ItemGroup>

Note the use of the $(CurrentPlatform) property, which we defined above. You could, instead, use conditionals for which assemblies to include for which platform. You could also need to:

  • Replace the $(PROCESSOR_ARCHITEW6432) and $(PROCESSOR_ARCHITECTURE) with $(Platform) to consider ONLY the target platform of the projects
  • Alter the platform determination logic in order to be appropriate to the current machine, so that you're not building/referencing a 64 bit binary to execute on a 32 bit platform.

I had this written up originally for an internal Wiki at work, however, I've modified it and posted the full process to my blog, if you are interested in the detailed step-by-step instructions.

I'm referencing the x86 DLLs, located in e.g. \component\v3_NET4, in my project. Specific DLLs for x86/x64 are located in sub-folders named "x86" and "x64" resp.

Then I'm using a pre-build script that copies apropriate DLLs (x86/x64) into the referenced folder, based on $(PlatformName).

xcopy /s /e /y "$(SolutionDir)..\component\v3_NET4\$(PlatformName)\*" "$(SolutionDir)..\component\v3_NET4"

Works for me.

You can use a condition to an ItemGroup for the dll references in the project file.
This will cause visual studio to recheck the condition and references whenever you change the active configuration.
Just add a condition for each configuration.

Example:

 <ItemGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
<Reference Include="DLLName">
<HintPath>..\DLLName.dll</HintPath>
</Reference>
<ProjectReference Include="..\MyOtherProject.vcxproj">
<Project>{AAAAAA-000000-BBBB-CCCC-TTTTTTTTTT}</Project>
<Name>MyOtherProject</Name>
</ProjectReference>
</ItemGroup>

I faced the same problem and spent quite a while searching for a decent solution. Most people offer manual editing of Visual Studio solution files, which is quite tedious, error prone and confusing when exploring these edited files in Visual Studio GUI afterwards. When I already gave up, the solution came up itself. It is very similar to what Micke recommends in his answer above.

In account manager I created two separate build targets for x86 and x64 platforms, as usual. Next, I added a reference to x86 assembly to my project. On this point, I believed that the project is configured for x86 build only and will never build for x64 configuration, unless I will make manual editing of it as suggested by Hugo above.

After a while, I eventually forgot the limitation and accidentally started x64 build. Of course, the build failed. But important was the error message I received. Error message told that assembly named exactly as my referenced x86 assembly is missing in the folder intended as x64 build target for my solution.

Having noticed this, I have manually copied proper x64 assembly into this directory. Glory! My x64 build miraculously succeeded with proper assembly found and linked implicitly. It was matter of minutes to modify my solution to set a build target directory for x64 assembly to this folder. After these steps solution builds automatically for both x86 and x64 without any manual editing of MSBuild files.

To sum up:

  1. Create x86 and x64 targets in a single project
  2. Add all proper project references to x86 assemblies
  3. Set one common build target directory for all x64 assemblies
  4. In case you have ready x64 assemblies, just copy them once into your x64 build target directory

After completion of these steps your solution will properly build for both x86 and x64 configurations.

This worked for me on Visual Studio 2010 .NET 4.0 C# project. Evidently, this is a sort of undocumented internal behavior of Visual Studio, which might be subject of change in 2012, 2013 and 2015 versions. If somebody will try on other versions, please share your experience.

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.

Resolution of correct x86/x64-dlls at runtime

Steps:

  1. Use AnyCPU in csproj
  2. 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.
  3. On Reference-Properties set Copy Local & Specific Version to false
  4. 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>
  5. Add this postbuild script to your startup project, use and modify the paths of this script so 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.

  6. 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;
    }
    
  7. 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 would not need the AssemblyResolve event handler if you assure otherwise that the dlls get copied in 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.

I ended up using what I consider an easier solution that is sort of an inversion of Micke's. The project is a C# forms app, Visual Studio 2015, with x86 and x64 targets. I referenced one of the .NET assemblies, I used the 32 bit one. In the reference properties, I set "Copy Local" to false. Then I just manually put the appropriate (32 or 64 bit) .Net assembly in each target directory. The actual reference bitness is irrelevant, assuming they have the same capabilities, since it's just defining the external interface. You could also put a post build copy step if you wanted to get fancy. Note this project also had a COM reference, same thing works. The reference defines the objects/interfaces so the bitness of the reference DLL is irrelevant. If both 32 bit and 64 bit COM DLLs are registered, the app will look in the appropriate place in the registry and create the correct 32 or 64 bit COM object. Works for me!