哪种平台适合您的 .NET 应用程序?






4.81/5 (22投票s)
对该主题进行了深入研究。构建了一个便捷的实用工具,用于检查程序集是托管还是非托管,是为 32 位还是 64 位 Windows 构建的。
引言
表面上看,这个问题可能很简单,只需在 Visual Studio 2012 到 2015 的项目属性 => 生成选项卡中单击一个按钮选择“为任何 CPU 生成”。实际上,事情并非如此简单。经过深入研究,我在这里与您分享我的理解。
除了理论结果,我还将互联网上零散的代码片段整合到一个简单而有用的工具中。它可以告知程序集是托管还是非托管;是为 32 位还是 64 位平台构建的。
当我们构建软件并准备将软件部署到客户机时,会有一个问题出现:客户机是 32 位 Windows 还是 64 位 Windows?我们应该将应用程序构建为目标为任何 CPU 吗?我希望这篇帖子能帮助您解决这个问题。
其他参考请参见[8]-[14]。
免责声明:我没有编写和拥有这些逻辑。我只是将这些代码片段整合到一个有用的工具供您使用。这些代码片段来自参考 [2]-[7]。特别是,CorFlagsReader.cs 在 Apache 许可证下。
Visual Studio 2015 默认生成配置
当 .NET 应用程序在 Visual Studio 2012 及更高版本中构建时,默认设置为 AnyCPU,并带有附加子类型“Any CPU 32 位优先”。[20] 这意味着:
- 如果进程在 32 位 Windows 系统上运行,它将作为 32 位进程运行。IL 被编译为 x86 机器码。
- 如果进程在 64 位 Windows 系统上运行,它将作为 32 位进程运行。IL 被编译为 x86 机器码。
- 如果进程在 ARM Windows 系统上运行,它将作为 32 位进程运行。IL 被编译为 ARM 机器码。
另一个微妙的观察来自 [21]。
案例研究
如果托管应用程序是为 32 位操作系统构建的,那么它将在 32 位操作系统上运行。如果此应用程序是为 64 位操作系统构建的,并且它引用了 32 位非托管程序集,那么它将无法在 64 位操作系统上运行。
SQLite ADO.NET 安装就是这样一个真实的例子。如果要构建一个 C# 应用程序来针对 64 位 Windows,您需要安装 SQLite ADO.NET 64 位 DLL。
有关更多信息,请参阅 [23]。
.NET 应用程序目标平台概述
当我们使用 Visual Studio 构建 .NET 应用程序时,我们需要选择目标为特定体系结构。
“如果您拥有 100% 类型安全的托管代码,那么您确实可以将其复制到 64 位平台并在 64 位 CLR 下成功运行。”
根据 [18],我们得知以下信息:
考虑一个 100% 类型安全的 .NET 应用程序。在这种情况下,您可以将您在 32 位计算机上运行的 .NET 可执行文件移动到 64 位系统上并成功运行。为什么会这样?由于程序集是 100% 类型安全的,我们知道它没有任何对本机代码或 COM 对象的依赖,也没有“不安全”的代码,这意味着该应用程序完全在 CLR 的控制下运行。CLR 保证,尽管即时 (JIT) 编译生成的机器码在 32 位和 64 位之间会有所不同,但执行的代码在语义上将是相同的。
因此,如果我们的应用程序是 100% 类型安全的托管代码,那么我们有以下场景:
如果我们选择目标为 x86 体系结构,我们的应用程序将在支持目标 .NET 框架版本的任何基于 Windows 的计算机上运行。更具体地说,即使在 64 位操作系统上,它也将始终在 32 位进程中运行。
如果我们选择目标为 x64 体系结构,我们的应用程序将需要一个 64 位进程,并且无法在 32 位操作系统上运行。
x86 和 x64 选项都可以在我们使用第三方 DLL 时很有用,特别是那些使用非托管代码的 DLL,这些 DLL 需要 32 位或 64 位进程。例如,当第三方 DLL 是为 x86 编译的时,64 位进程无法使用它。
如果我们选择目标为“任何 CPU”,我们的应用程序将在任何支持的操作系统上执行,并且进程将使用最适合的体系结构选项。具体来说,在 32 位操作系统上,它将以 32 位模式运行。在 64 位操作系统上,它将在 64 位进程中执行。可能会出现我们的应用程序需要根据其进程类型采取不同行为的情况。然后,我们需要在运行时检测进程类型。
参考 [1] 值得一读。
潜在障碍
如果我们的托管应用程序出现以下情况,那么它们将阻止应用程序成功针对 x64:
- 通过 p/invoke 调用平台 API
- 调用 COM 对象
- 利用共享信息的机制
- 使用序列化作为不安全代码的方式(请注意:这在用途上是次要的。)
- 使用封送 (marshalling) 作为持久化状态的方式
无论我们的应用程序在做什么,我们都需要了解应用程序源代码及其依赖的程序集,它们是托管还是非托管,32 位还是 64 位?我们可以有以下选择:
- 迁移代码,不作任何更改
- 修改代码以正确处理 64 位指针
- 与其他供应商合作等,提供其产品的 64 位版本
- 修改逻辑以处理封送和/或序列化
在我的项目中,我只关心不安全的代码。但根据 [18],我们看到:在托管应用程序中使用不安全的代码并不意味着迁移到 64 位平台将不可能。也不意味着会有问题。这意味着您必须审查您的托管应用程序中的所有不安全代码,并确定是否存在任何问题。
CorFlag 值及其应用
要了解 .NET 程序集是为哪个平台构建的,我们可以使用 CorFlag
值。参考 [19] 是 PE 文件格式的规范。参考 [3] 是一个有用的代码,可以获取所有 CorFlag
值。下表包含有关程序集的所有相关信息的规则。最新的信息已在 [25] 中更新,针对 .NET Framework 4.5。
CorFlag 信息
CLR 头 | 它所针对的 .NET Framework |
PE |
PE 头类型 PE32 = 32 位 PE32+ = 64 位 |
CorFlags | 各种标志,如端类型、ILONLY 等。 |
ILONLY | 文件仅包含 IL(即,不包含不安全代码) |
32BIT |
1=x86 目标 0=任何 CPU |
Signed |
1=程序集已签名 0=程序集未签名 |
决定平台的规则
Any CPU | PE32 和 32BIT=0 |
x86 | PE32 和 32BIT=1 |
x64/Itanium (IA-64) | PE32+ 和 32BIT=0 |
.NET Framework 4.5.1 的改进规则
CPU 架构 | PE | 32BITREQ | 32BITPREF |
x86 (32 位) | PE32 | 1 | 0 |
x64 (64 位) | PE32+ | 0 | 0 |
任何 CPU | PE32 | 0 | 0 |
任何 CPU (32 位优先) | PE32 | 0 | 1 |
代码亮点
感谢 Kirill Osenkov 在参考 [25] 中提供的关于 CorFlags
值的宝贵细节
[Flags]
public enum CorFlags
{
ILOnly = 0x00000001,
Requires32Bit = 0x00000002,
ILLibrary = 0x00000004,
StrongNameSigned = 0x00000008,
NativeEntryPoint = 0x00000010,
TrackDebugData = 0x00010000,
Prefers32Bit = 0x00020000,
}
基于这些细节,我在 CorFlagsReader.cs 文件中的 CorFlagsReader
类中添加了以下属性:
public bool Is32BITREQ
{
get
{
return (corflags & CorFlags.Requires32Bit) == CorFlags.Requires32Bit;
}
}
public bool Is32BITPREF
{
get
{
return (corflags & CorFlags.Prefers32Bit) == CorFlags.Prefers32Bit;
}
}
public bool IsPE32
{
get
{
return (peFormat == PEFormat.PE32);
}
}
public bool IsPE32Plus
{
get
{
return (peFormat == PEFormat.PE32Plus);
}
}
对于托管程序集,我在 openAnAssemblyToolStripMenuItem_Click
事件中编写了以下规则:
if(isManagedAssembly)
{
textBox1.Text = fileName +" is a managed assembly.\n";
CorFlagsReader corFlags= CorFlagsReader.ReadAssemblyMetadata(fileName);
if (corFlags.IsPE32 & corFlags.Is32BITREQ & (!corFlags.Is32BITPREF))
{
textBox1.Text += "Target CPU architecture: x86";
}
if (corFlags.IsPE32 & (!corFlags.Is32BITREQ) & (!corFlags.Is32BITPREF))
{
textBox1.Text += "Target CPU architecture: anyCPU";
}
if ((corFlags.IsPE32 & (!corFlags.Is32BITREQ) & corFlags.Is32BITPREF) ||
(corFlags.IsPE32 & corFlags.Is32BITREQ & corFlags.Is32BITPREF))
{
textBox1.Text += "Target CPU architecture: anyCPU 32-bit preferred";
}
if (corFlags.IsPE32Plus & (!corFlags.Is32BITREQ) & (!corFlags.Is32BITPREF))
{
textBox1.Text += "Target CPU archtecture:x64";
}
}
程序集检查器
这个程序集检查器可以用来检查任何程序集类型。它有一个简单的图形用户界面。
请将任何 DLL 或 EXE 文件加载到其中,如果您有任何问题,请告诉我。
源代码文件已在此处上传。可执行文件已准备好供您使用。
.NET 程序集是否包含非托管代码?
[26] 中有一个很好的讨论值得一读。Microsoft 的 corflags
工具可以告诉它是否纯 IL 或包含混合非托管代码,但 PEVerify 工具可以告诉它是否 100% 类型安全。
反馈
如果您有好的想法和见解,请提供宝贵的反馈。我希望这个工具能让您更省时省力。
参考文献
- 回归基础:x86 和 x64 以及 .NET Framework 和 CLR 周围的 32 位和 64 位混淆
- 如何确定 DLL 是托管程序集还是本机程序集(防止加载本机 DLL)?
- CorFlagsReader.cs 内容
- 检查非托管 DLL 是 32 位还是 64 位?
- 如何查找本机 DLL 文件是编译为 x64 还是 x86?
- 如何确定 .NET 程序集是为 x86 还是 x64 构建的?
- 如何判断 .NET 应用程序是 32 位还是 64 位?
- 如何判断我的应用程序是作为 32 位还是 64 位应用程序运行?
- 学习 Windows 程序是 64 位还是 32 位的三种方法
- 如何使用 dumpbin 检查二进制文件是 32 位还是 64 位?
- 快速判断已安装的应用程序是 64 位还是 32 位
- 如何使用 dumpbin 确定 .exe 文件是 32 位还是 64 位应用程序
- 将 32 位托管代码迁移到 64 位
- 确定已安装的 .NET Framework 版本
- 如何在 64 位服务器上判断我的 .NET 应用程序是否正在 64 位下运行?
- 检查当前进程是否为 64 位
- 使用 .NET 进行 x64 开发
- 将 32 位托管代码迁移到 64 位
- Microsoft PE 和 COFF 规范
- .NET 4.5 和 Visual Studio 11 中 AnyCPU 的真正含义
- 为什么在 .NET 4.5 下,“任何 CPU(优先 32 位)”允许我分配比 x86 更多的内存?
- Vista64 下 IIS7 和 ASP.NET 上 32 位与 64 位以及迁移 DasBlog
- 面向任何 CPU 的 C# 应用程序的 SQLite 配置 - 第一部分
- 从 .NET Framework 上的 32 位到 64 位应用程序开发
- 如何解释 CorFlags 标志?
- 如何查找 .NET 程序集是否包含非托管代码?
历史
- 07/05/16: 初始化本文