【问题标题】:Understanding 32-bit vs 64-bit in .Net了解 .Net 中的 32 位与 64 位
【发布时间】:2018-11-10 04:40:37
【问题描述】:

我总是绕开构建 64 位桌面应用程序的问题,因为我不知何故认为这不是一件小事,而且我想我并没有真正理解解决方案“配置” manager”对话框、平台和配置。

无论如何,我刚刚尝试通过简单地将所有解决方案项目的平台更改为 x64 来转换现有应用程序,并且成功了。我遇到的唯一问题是其中一个项目引用的 C++ DLL,它需要重建为 x64。

所以我的问题(最后)是:为什么我只有 C++ DLL 的问题,但我的解决方案中的项目引用的许多 .Net 程序集 DLL 都很好?

究竟是什么决定了我的应用程序是否构建为 64 位?我将解决方案的所有项目都更改为“x64”,但如果将它们保留为“任何 CPU”,并且我只将 WPF(启动)项目更改为“x64”,它仍然可以工作吗?

编辑

所以最后看来我只是能够将所有项目平台设置为“任何 CPU”而不是“x64”(TFS 服务器无法使用后者运行单元测试)。即使是引用非托管 64 位 DLL 的项目也对此感到满意(不知道为什么)。

诀窍是取消选中 WPF(启动)项目属性中的 Prefer 32-bit 选项。构建的应用程序现在作为 64 位应用程序运行,并且 TFS/单元测试运行良好。

【问题讨论】:

  • C# 编译器生成的 IL 代码并不关心甚至不知道(通常)它是以 32 位还是 64 位运行。 JIT 编译器会处理这个问题。因此托管代码可以轻松地在任一平台上运行。
  • 您可以将所有不调用非托管代码(并且不包含对体系结构特定库的引用)的类库项目配置为“任何 CPU”。
  • 嗯,您在 C++ 项目中所要做的就是更改平台。而您在 C# 项目中所要做的就是更改平台。但它是不同的设置,不是由解决方案平台选择的。右键单击您的 EXE 项目 > 属性 > 构建选项卡。请务必为 Debug 和 Release 版本更改它。
  • @C.Evenhuis 我建议删除“首选 32 位”选项,否则程序将在 intel/amd 处理器上以 32 位运行(但在不支持 32 位的平台上运行 64 位)(技术上更复杂。如果支持 32 位,它将运行在 32 位,否则将运行在 64 位。在接下来的几年中,一些操作系统将放弃对 32 位的支持)
  • 编译器不会针对非托管 dll 的目标架构验证这一点,正如 MatthewWatson 所提到的,无论如何,结果 IL 都是相同的。您必须手动指定目标平台,以通知您的库的用户它(部分)仅在该平台中(正确)运行。

标签: c# .net 32bit-64bit


【解决方案1】:

TL;DR:.Net 程序集永远不会组装成机器特定的指令,而是编译成在 .Net 虚拟机下运行的 MSIL,在运行时 VM 使用 JITer 生成CPU可以执行的机器指令。


32bit64bit 之间“中断”目标文件的区别在于:指针大小内存模型,最重要的是指令集架构 (ISA)。让我们分解每个点,看看为什么.Net 程序集大多不受影响。

点大小(或长度)

32bit 执行环境中,指针是 32 位长,而正如您所期望的 int 64 位,它们是 64 位长。这一事实可能会以两种方式之一破坏您的装配:

  1. 如果需要更多(或更少)字节来存储指针,则包含指针的结构的内部内存结构将发生变化。
  2. 指针算法 - 一些聪明的开发人员喜欢玩弄指针、屏蔽指针、添加指针等等。在某些情况下,指针的大小可能是这些计算的一部分,以产生正确的结果。

为什么 .Net 程序集不受影响

因为 .Net 语言是 safe - 你不能玩指针(通常,除非你处于不安全的上下文中)。没有选择使用指针解决了第二点。至于第一点 - 这就是你可以获得类的大小(使用sizeof)而不是包含类引用的结构的大小的原因。

内存模型

旧的32bit 处理器曾经有一个称为Memory segmentation 的功能,它允许OSSupervisor program 声明使用称为Segment registers 的特殊CPU registers 访问的内存区域。在64bit 中,此功能大部分被禁用。所以编译到内存分段下工作的程序在非分段环境下工作可能会出现问题。

为什么 .Net 程序集不受影响

通常我们不会在这个低级别处理内存,这是可能的,因为.Net virtual machine 将在下一点解释。

指令集架构

32bit64bit (x86AMD64) 相似但完全不同 ISAs 这意味着在一个下运行的代码将不会在另一个下运行。因此,与其他两点不同,无论您在其中写了什么,这一点都会破坏您的程序集。

为什么 .Net 程序集不受影响

那么.Net 程序集怎么可能编译为Any CPU?诀窍在于,当您编译一种 .Net 语言时,您永远不会组装它。这意味着当您按下compile 时,您只会将您的代码编译成一个未组装(即未通过汇编程序运行)的中间对象(称为 .Net 程序集)。

通常,当您在C/++ 中编译代码时,您首先通过编译运行它,然后生成一些assembly instructions,然后将这些指令传递给assembler,后者生成machine instructions。 CPU 只能执行这些machine instructions

.Net 语言是不同的,如上所述,当您编译 .Net 语言时,您通过编译器运行它并停在那里,不涉及汇编程序。这里的编译也会生成一些assembler instructions,但与c/c++ 编译生成的指令不同,这些指令与机器无关,它们是用称为MSIL - Microsoft intermediate language 的语言编写的。没有 CPU 知道如何执行这些指令,因为就像普通的汇编指令一样,它们也必须汇编成机器指令。

诀窍是这一切都发生在运行时,当用户打开您的程序时,他会启动您的程序运行的.Net runtime 实例,您的程序永远不会直接针对本机机器本身运行。当您的程序需要调用方法时,.Net 虚拟机中称为 JITer - just in time compiler 的特殊组件负责将此方法从 MSIL 组装成特定于安装 .Net 框架的机器的机器指令。

【讨论】:

  • 我注意到在 32 位中,当您捕获异常并且您位于 catch 块内的断点中时,您可以将光标(设置下一条语句)拖出 catch 块。而在 64 位中,Visual Studio 中会出现错误(3 个可能的错误,其中一个与堆栈展开有关)。任何人都可以阐明这种行为吗?
  • x86 上的低级异常处理细节与 x64 有很大不同(与 arm 和 arm64 不同——x86 是奇怪的情况)。在 x86 上,发出的 catch 代码与方法的其余部分位于同一块代码生成中,并且它们共享一个公共堆栈框架。在其他架构中,catch 代码被拆分为 funclet,这些 funclet 具有与 main 方法分离的代码和堆栈,这种分离使得调试器很难像在 x86 上那样重新指向 x64 上的控制。
【解决方案2】:

从正确性的角度来看,32/64 仅在您与本机代码互操作、使用低级不安全功能或分配大量 (> 4GB) 内存时才重要。如果没有这些考虑,您不妨只使用 AnyCpu。 AnyCpu 代码将默认为您的主机操作系统位数(尽管可以更改该默认值),因此对于现在大多数人来说,它最终将在 64 位进程中运行。

有时出于性能原因明确首选 32 位或 64 位。 32 位应用程序通常对缓存更友好。当以 64 位进程运行时,受计算约束的应用程序应该会表现得更好(尽管并非总是如此)。

【讨论】:

  • 这不是也意味着 64 位版本的 CLR 可以运行 64 位吗?并且 JIT 将其编译为 64 位程序集/指令集?
  • @Konrad 是的,这是正确的,但一般来说,您不需要关心 (m) 任何差异。
  • 另一种情况:调用 32 位进程内 COM 组件。在我工作的地方,每个人都安装了 32 位 MS Office,所以要在 Outlook 中启动电子邮件,我必须强制我的构建为 32 位。
猜你喜欢
  • 2011-01-28
  • 1970-01-01
  • 2010-12-11
  • 2013-06-21
  • 1970-01-01
  • 1970-01-01
  • 2014-01-04
  • 2011-09-24
相关资源
最近更新 更多