在32位和64位C#环境中使用System.Data.SQLite的选项

22

我明白为什么System.Data.SQLite.dll提供32位和64位版本,我们不需要再深入讨论这个问题。 :)

然而,这种做法似乎让纯C#开发变得更加困难,需要做出3个选择。

  1. 仅支持32位,并强制管理程序集编译为x86,在32位或64位环境下运行并失去在64位环境下的优势。

  2. 仅支持64位,强制管理程序集编译为x64,在32位环境下无法运行,但能获得64位环境下的所有优势。

  3. 创建两个版本的程序集,一个编译为x86并使用32位SQLite,另一个编译为x64并使用64位SQLite。它防止使用"ANY"作为编译选项,并且不能轻松地将单个构建部署到任一类型。从开发角度来看,这不是太可怕,因为我们只需要两个项目。只有一个是C#代码的官方版本,另一个将使用“链接”到另一个中的代码。这仅用于编译目的。这样做之后,我们仍然需要管理两个输出进行部署。

总之,我只是想确认上述是唯一的正确选择。

如果有其他选择,请告诉我。特别是是否存在一种方法可以获得单个C# DLL,可以编译为“ANY”,这样它可以根据运行环境的不同利用32位或64位,并且仍然使用System.Data.SQLite.dll。


从评论中看来,目前确认没有一种DLL选项可以支持任何CPU平台的解决方案,并且SQLite作为依赖项。 - Rodney S. Foley
你应该从NuGet安装它。请参考这个答案 - SepehrM
NuGet并没有真正解决2011年8月已经得到答案的这个非常古老的问题。而且,在所有开发商店中,NuGet都不是一个可行的选择,因此即使它是解决问题的最佳方案,也并不总是一个选项。多年来,我已经发展出了更好的解决方案,允许使用任何CPU的包装器程序集,并在运行时动态地支持32位和64位SQLite版本。然而,这是一个旧的已关闭的问题,所以人们应该转向更近期的问答,因为软件很快就会过时。 - Rodney S. Foley
你说的关于某些项目中没有使用NuGet是正确的,但我不理解你的第一句话:“NuGet并没有真正解决手头的问题”为什么呢?既然这个问题在谷歌搜索的第一页出现了,你能分享一下你的“Any CPU wrapper”解决方案吗? - SepehrM
NuGet是一个包管理器,它无法解决部署或运行时问题。我不能分享我创建的ANY CPU包装器解决方案,因为它是我所在公司内部版权源代码库的一部分。虽然那个答案中的实现可能与我的不同,但如果它是一个ANY CPU包装器,就像那个答案声称的那样,它应该可以在使用NuGet或不使用NuGet的情况下工作。你应该将那个答案的链接作为评论添加到选定的答案中,以获得最大的可见性,因为大多数人会浏览问题并阅读答案和答案的评论。 - Rodney S. Foley
8个回答

21

这是对Springy76答案的详细解释。请按照以下步骤操作:

public class AssemblyResolver
{
    public static void HandleUnresovledAssemblies()
    {
        AppDomain currentDomain = AppDomain.CurrentDomain;
        currentDomain.AssemblyResolve += currentDomain_AssemblyResolve;
    }

    private static Assembly currentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
    {
        if (args.Name == "System.Data.SQLite")
        {
            var path = Path.Combine(pathToWhereYourNativeFolderLives, "Native");

            if (IntPtr.Size == 8) // or for .NET4 use Environment.Is64BitProcess
            {
                path = Path.Combine(path, "64");
            }
            else
            {
                path = Path.Combine(path, "32");
            }

            path = Path.Combine(path, "System.Data.SQLite.DLL");

            Assembly assembly = Assembly.LoadFrom(path);
            return assembly;
        }

        return null;
    }
}

确保生成的路径指向正确的位置,可以是32位或64位SQLite DLLS。我个人在这个NuGet软件包中使用了这些DLLS并获得了良好的结果:http://www.nuget.org/packages/SQLitex64

(您只需要使用NuGet软件包获取已编译的SQLite DLLS。一旦获取到它们,请从NuGet创建的项目中删除对SQLite的引用以及NuGet软件包本身。实际上,保留引用可能会干扰此解决方案,因为SQLite永远不会被识别为未解决的程序集。)

尽早调用“HandleUnresolvedAssemblies()”,最好是在任何启动期间。


但是如果删除了对dll的引用,代码就找不到该类的命名空间和方法,也无法编译。你该如何解决这个问题? - DefenestrationDay
这个解决方案适用于你正在使用其他库来处理与SQLite通信的情况。nHibernate与SQLite的使用就是一个例子。 如果你想直接访问类方法,那么你需要寻找另一个解决方案,比如在另一个答案中发布的条件构建技巧。 - Holf

14

有两种常见的解决方案可以保持您的主应用程序为AnyCPU:

  • 将x86和x64程序集都安装到GAC中:它们可以(应该!)具有相同的程序集名称,GAC将自动决定使用x86还是x64版本。

  • 通过Hook AppDomain.AssemblyResolve并使用Assembly.LoadFrom从子目录中提供正确的程序集


你可以从NuGet安装它。请参考:https://dev59.com/8HjZa4cB1Zd3GeqPeHTt#19623876 - SepehrM

6
类似的问题也存在于Oracle的ODP.NET与本机32/64位OCI DLLs之间的关系中。
我们通过为项目创建“x86”和“x64”平台,并手动编辑项目文件来使用条件引用来解决这个问题。
<Choose>
<When Condition="'$(Platform)' == 'x64'">
  <ItemGroup>
    <Reference Include="Oracle.DataAccess, processorArchitecture=x64">
      <SpecificVersion>False</SpecificVersion>
      <HintPath>..\ThirdParty\ODP.NET\x64\Oracle.DataAccess.dll</HintPath>
    </Reference>
    <Content Include="..\ThirdParty\ODP.NET\x64\oci.dll">
      <Link>oci.dll</Link>
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </Content>
    <Content Include="..\ThirdParty\ODP.NET\x64\orannzsbb11.dll">
      <Link>orannzsbb11.dll</Link>
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </Content>
    <Content Include="..\ThirdParty\ODP.NET\x64\oraociei11.dll">
      <Link>oraociei11.dll</Link>
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </Content>
    <Content Include="..\ThirdParty\ODP.NET\x64\OraOps11w.dll">
      <Link>OraOps11w.dll</Link>
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </Content>
  </ItemGroup>
</When>
<When Condition="'$(Platform)' == 'x86'">
  <ItemGroup>
    <Reference Include="Oracle.DataAccess, processorArchitecture=x86">
      <SpecificVersion>False</SpecificVersion>
      <HintPath>..\ThirdParty\ODP.NET\x86\Oracle.DataAccess.dll</HintPath>
    </Reference>
    <Content Include="..\ThirdParty\ODP.NET\x86\oci.dll">
      <Link>oci.dll</Link>
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </Content>
    <Content Include="..\ThirdParty\ODP.NET\x86\orannzsbb11.dll">
      <Link>orannzsbb11.dll</Link>
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </Content>
    <Content Include="..\ThirdParty\ODP.NET\x86\oraociei11.dll">
      <Link>oraociei11.dll</Link>
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </Content>
    <Content Include="..\ThirdParty\ODP.NET\x86\OraOps11w.dll">
      <Link>OraOps11w.dll</Link>
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </Content>
  </ItemGroup>
</When>
</Choose>

这使我们避免使用两个不同的项目。我相信你可以为SQLite做类似的事情。


是的,手动修改项目文件会创建一些隐藏信息,不是所有开发人员都能意识到,这使得维护变得更加困难。因此,我们选择了双重项目来完成这个任务,而不是修改文件。同时,这种方法仍然需要管理两个DLL文件作为输出。 - Rodney S. Foley
这只是Option 3的一个变体,具有相同的结果。问题的目标是寻找一种新的选项和新的结果,使您可以构建一个针对任何CPU平台的依赖于SQLite的项目。 - Rodney S. Foley
4
这个解决方案的目标是避免使用两个项目,从这个意义上说,它是“新的”。我从未声称它能够实现“任何CPU”。顺便说一句,这并不是真正的“黑客行为”——这是经过充分记录的MSBuild语法。如果您的项目位于项目层次结构的根目录下,那么使用两个项目可能是更清晰的解决方案。然而,如果它在10个左右的依赖级别之下(就像我们的情况一样),那么我相信避免复制所有依赖项是非常值得的。 - Branko Dimitrijevic

3

从开发角度来看,管理起来并不是那么可怕,因为我们将需要两个项目。

这并不正确 - 您可以在同一个项目中使用两个构建配置。 在部署时,您需要为x86和x64构建,但代码库和项目可以相同。

我目前在一个大型生产项目中执行此操作,这是由于SQLite以及包括x64和x86变体的其他本地/Interop库。


这基本上就是我之前所说的手动修改项目文件来实现的相同操作。最终你仍然会得到两个托管 DLL,一个是32位的,另一个是64位的。 - Rodney S. Foley
这只是选项3的一个变体,结果相同。问题的目标是找到一种新的选项,具有新的结果,可以获得任何CPU平台目标构建,并且可以与SQLite作为依赖项一起使用。 - Rodney S. Foley
3
@Rodney:是的,这是选项3的一种变体,但它消除了选项3的主要痛点之一,那就是您不需要维护两个项目... - Reed Copsey

2

Branko Dimitrijevic说:“我相信你可以为SQLite做类似的事情。”这是正确的。:)

遇到同样的问题,我发现了Rodney的问题和Branko的答案,然后自己尝试了一下。对于任何想要查看我的工作中SQLite实现的人,这里提供给您:

<Choose>
  <When Condition="'$(Platform)' == 'x64'">
    <ItemGroup>
      <Reference Include="System.Data.SQLite, Version=1.0.88.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139, processorArchitecture=AMD64">
        <SpecificVersion>False</SpecificVersion>
        <HintPath>System.Data.SQLite\x64\System.Data.SQLite.dll</HintPath>
      </Reference>
    </ItemGroup>
  </When>
  <When Condition="'$(Platform)' == 'x86'">
    <ItemGroup>
      <Reference Include="System.Data.SQLite, Version=1.0.88.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139, processorArchitecture=AMD64">
        <SpecificVersion>False</SpecificVersion>
        <HintPath>System.Data.SQLite\x86\System.Data.SQLite.dll</HintPath>
      </Reference>
    </ItemGroup>
  </When>
</Choose>

<ItemGroup>
  <Reference Include="Microsoft.CSharp" />
  <Reference Include="System" />
  <Reference Include="System.configuration" />
  <Reference Include="System.Core" />
  <Reference Include="System.Windows.Forms" />
  <Reference Include="System.Xml.Linq" />
  <Reference Include="System.Data.DataSetExtensions" />
  <Reference Include="System.Data" />
  <Reference Include="System.Xml" />
</ItemGroup>

当然,你可以将“HintPath”命名为任何你喜欢的名称。
我发现这是完美的解决方案:我可以维护一个单一的项目,并在需要时快速切换目标平台。唯一的潜在缺点是我看不到解决方案资源管理器中的参考。但这只是为了整体功能而付出的小代价。

2
通常情况下,除非您的应用程序需要超过4GB的内存,否则选项一是最好的选择。请记住,在64位操作系统上运行的32位应用程序具有大多数64位的优点,而没有许多缺点。这就是为什么x86是VS 2010中.exe应用程序的默认目标的原因。

1
是的,它不仅具有内存优势,但那是最明显的一个。从32位世界访问OS的64位区域存在问题,如果你使用它,你必须知道如何在其中操纵,而通常在任何构建中你不必担心这些额外的事情。 - Rodney S. Foley

0

您也可以通过更改Visual Studio中的编译选项来解决此问题:

更改Visual Studio中的编译设置:

  1. 转到程序的启动项目。
  2. 打开属性窗口。
  3. 单击编译选项卡。
  4. 单击高级编译选项。
  5. 将目标CPU选项更改为x86。

现在,您的程序将始终在32位模式下运行,即使在64位机器上运行。

您还可以提供两个发行版,一个用于每个环境,如上所述。虽然这将成为未来的标准,但对于我的当前项目而言,这是最好和最简单的选择。


0
这是一个老问题,但我在一个自2017年以来一直使用SQLite的项目中遇到了同样的问题。
我首先升级到了最新版本的System.Data.Sqlite.Core,即1.0.118。它有两个互操作的shims,一个用于x86,另一个用于x64。升级是通过Nuget完成的。
将我们的项目更改为混合模式构建,在32位和64位Windows上对Sqlite都可以正常工作。除此之外,没有其他需要更改的地方,只是我们的安装包现在添加了这两个文件。

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