在同一解决方案/项目中使用Visual Studio同时针对32位和64位进行开发

114

我在如何设置Visual Studio多目标构建方面遇到了一些困境。

背景: 使用c# .NET v2.0,使用第三方32位DLL的P/Invoke,SQL Compact v3.5 SP1和一个设置项目。 现在,平台目标设置为x86,以便在Windows x64上运行。

第三方公司刚刚发布了64位版本的它们的DLL,我想构建一个专用的64位程序。

这引起了一些问题,我还没有找到答案。 我希望有完全相同的代码库。 我必须使用对32位DLL或64位DLL的引用进行构建。 (都是第三方的和SQL Server Compact)

可以通过创建两个新的配置集(Debug64和Release64)来解决吗?

我必须创建2个单独的设置项目(标准的Visual Studio项目,没有Wix或任何其他工具),还是可以在同一个.msi中解决?

欢迎任何想法和/或建议。


@Magnus Johansson:你可以使用两个配置来实现你一半的目标。MSI稍微有点难度。 - user7116
8个回答

86

是的,您可以在同一项目中使用相同的代码库针对x86和x64进行目标设置。通常情况下,在VS.NET中创建正确的解决方案配置即可实现自动适配(尽管对于完全非托管的DLL的P/Invoke可能需要一些条件代码):我发现需要特别注意的项目包括:

  • 引用外部托管程序集,名称相同但具有特定位数(这也适用于COM互操作程序集)
  • MSI包(如已经注意到的那样,将需要针对x86或x64进行目标设置)
  • 任何基于.NET Installer类的自定义操作

程序集引用问题无法完全在VS.NET中解决,因为它只允许您向项目添加给定名称的引用一次。要解决此问题,请手动编辑您的项目文件(在VS中,右键单击解决方案资源管理器中的项目文件,选择卸载项目,然后再次右键单击并选择编辑)。添加对x86版本程序集的引用后,您的项目文件将包含以下内容:

<Reference Include="Filename, ..., processorArchitecture=x86">
  <HintPath>C:\path\to\x86\DLL</HintPath>
</Reference>

将该引用标签包含在一个ItemGroup标签中,指示它适用于的解决方案配置,例如:

<ItemGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
   <Reference ...>....</Reference>
</ItemGroup>

然后,复制并粘贴整个ItemGroup标签,并编辑它以包含您的64位DLL的详细信息,例如:

<ItemGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x64' ">
  <Reference Include="Filename, ..., processorArchitecture=AMD64">
     <HintPath>C:\path\to\x64\DLL</HintPath>
   </Reference>
</ItemGroup>

在VS.NET中重新加载项目后,程序集引用对话框会因这些更改而有些混乱,你可能会遇到一些关于目标处理器错误的警告,但是你的所有构建都将正常工作。
解决MSI问题是接下来要做的事情,不幸的是,这需要一个非VS.NET工具:我更喜欢使用Caphyon的高级安装程序,因为它非常出色地完成了所涉及的基本技巧(创建一个共同的MSI,以及32位和64位特定的MSI,并使用.EXE安装程序提取正确的版本并在运行时执行所需的修复)。您可能可以使用其他工具或Windows Installer XML (WiX) 工具集来实现相同的结果,但Advanced Installer使事情变得如此容易(而且价格也相当实惠),以至于我从未真正看过其他选择。
然而,在使用高级安装程序时,您仍然可能需要WiX来进行.NET安装程序类自定义操作。尽管使用VersionNT64和NOT VersionNT64执行条件指定某些仅在某些平台上运行的操作是微不足道的,但内置AI自定义操作将在64位机器上使用32位框架执行。
这可能会在未来的版本中得到修复,但是现在(或者使用具有相同问题的不同工具创建您的MSI时),您可以使用WiX 3.0的托管自定义操作支持来创建具有正确位数的操作DLL,这将使用相应的框架执行。
编辑:从版本8.1.2开始,高级安装程序正确支持64位自定义操作。不幸的是,自我的原始答案以来,它的价格已经大幅上涨,尽管与InstallShield及其同类产品相比,仍然具有极高的性价比...

编辑:如果您的DLL已在全局程序集缓存中注册,您也可以使用标准的引用标记来实现此目的(以SQLite为例):

<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>

该条件也适用于所有构建类型,包括发布版和调试版,并仅指定处理器架构。

在Visual Studio 2008中,我发现<ItemGroup>不能嵌套。一旦我将新的<ItemGroup>放在其余<Reference>组下面,这个解决方案就可以正常工作了。我还不得不将x86更改为AnyCPU,这可能与我的特定项目历史有关。 - Oliver Bock
那个高级安装程序看起来非常棒。 - Pat
这可能是一个愚蠢的问题,但你如何手动获取文件以进行编辑? - hrh
2
要在VS中编辑文件,请右键单击解决方案资源管理器中的项目,然后找到“卸载项目”。一旦项目被卸载,再次右键单击它,然后单击“编辑<项目文件名>”。编辑项目文件后,请保存并再次右键单击项目文件并加载它。如果没有拼写错误或错误,它将再次加载。如果有问题,VS会告诉您文件的问题所在。希望这可以帮助您! - John Baughman

29
假设您已经为两个平台构建了DLL,并且它们位于以下位置:
C:\whatever\x86\whatever.dll
C:\whatever\x64\whatever.dll

您只需编辑.csproj文件,如下所示:

<HintPath>C:\whatever\x86\whatever.dll</HintPath>

转为:

<HintPath>C:\whatever\$(Platform)\whatever.dll</HintPath>

然后,您应该能够构建针对两个平台的项目,MSBuild将在所选择的平台的正确目录中查找。


1
这个如果能够正常工作就太棒了,但是它并没有。至少对我来说不行。 - John Sheehan
11
那不应该是这样的吗: <HintPath>C:\whatever$(Platform)\whatever.dll</HintPath> - Andreas
对我来说,在Visual Studio 2008上工作得很好,但是它不会像普通的<Reference>一样自动将DLL复制到构建目标目录。mdb的解决方案对我更有效。 - Oliver Bock

3
您可以在项目文件中的中使用条件来处理dll引用。
这将使Visual Studio在更改活动配置时重新检查条件和引用。
只需为每个配置添加一个条件即可。
例如:
 <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>

这是一个很好的答案,基本上是mdb答案的tl;dr(这并不是坏事)。 - Steve Smith

2

使用x86/x64依赖项构建一个.Net应用程序

虽然其他答案提供了根据平台制作不同的构建解决方案,但我为您提供了一种只需使用“AnyCPU”配置就可以制作适用于x86和x64 dll的构建的选项。

您需要编写一些连接代码来实现这一点。

在运行时正确解析x86/x64-dlls的方法

步骤:

  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 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.

  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.)

优点:

  • 一个安装/构建适用于两个平台

缺点: - 当x86 / x64 dll不匹配时,在编译时没有错误。 - 您仍应在两种模式下运行测试!

可选地,在postbuild脚本中使用Corflags.exe创建一个专门用于x64架构的第二个可执行文件

其他变体尝试: - 如果您确保正确的dll已复制到启动时的二进制文件夹中(评估进程体系结构->将相应的dll从x64 / x86移动到bin文件夹中并返回),则无需使用AssemblyResolve事件处理程序。 - 在安装程序中评估体系结构,并删除错误体系结构的二进制文件并将正确的二进制文件移动到bin文件夹中。


1

我不确定你问题的完整答案,但我想指出SQL Compact 3.5 SP1下载页面的附加信息部分中的一条评论,因为你正在查看x64 - 希望它有所帮助。

由于 SQL Server Compact SP1 的更改和额外的 64 位版本支持,32 位版本的 SQL Server Compact 3.5 和 64 位版本的 SQL Server Compact 3.5 SP1 的集中安装和混合模式环境可能会出现间歇性问题。为了最大程度地减少冲突的可能性,并实现托管客户端应用程序的平台中立部署,使用 Windows Installer(MSI)文件集中安装 SQL Server Compact 3.5 SP1 的 64 位版本还需要安装 SQL Server Compact 3.5 SP1 MSI 文件的 32 位版本。对于仅需要本机 64 位的应用程序,可以使用私有部署的 64 位版本的 SQL Server Compact 3.5 SP1。
如果为 64 位客户端分发,则需要“同时包含 32 位 SQLCE 文件和 64 位文件”。这让生活变得有趣了,我想说我喜欢“看起来是间歇性问题”的说法...听起来有点像“你在想象事情,但以防万一,做这个...”

0

你可以生成两个不同的解决方案,然后将它们合并在一起!我在VS 2010中做到了这一点,并且它是有效的。我使用CMake生成了2个不同的解决方案,然后将它们合并在一起。


0

0
关于您上次的问题。很可能您无法在单个MSI内解决这个问题。 如果您正在使用注册表/系统文件夹或任何相关内容,则MSI本身必须知道此事,并且您必须准备一个64位MSI以便在32位机器上正确安装。

有可能您可以将产品安装为32位应用程序,但仍然能够使其作为64位运行,但我认为这可能有些难以实现。

话虽如此,我认为您应该能够为所有内容保留单个代码库。在我的当前工作场所,我们已经成功做到了这一点。(但是需要一些技巧才能让所有东西协同工作)

希望这可以帮助您。 以下是一些与32/64位问题相关的信息链接: http://blog.typemock.com/2008/07/registry-on-windows-64-bit-double-your.html


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接