返璞归真:关于x86和x64以及.NET Framework和CLR的32位和64位混淆

I'm running 64-bit operating systems on every machine I have that is capable. I'm running Vista 64 on my quad-proc machine with 8 gigs of RAM, and Windows 7 Beta 64-bit on my laptop with 4 gigs.

我在有能力的每台机器上都运行64位操作系统。 我在具有8 gig RAM的四线程计算机上运行Vista 64,并在具有4 gig的笔记本电脑上运行Windows 7 Beta 64位。

Writing managed code is a pretty good way to not have to worry about any x86 (32-bit) vs. x64 (64-bit) details. Write managed code, compile, and it'll work everywhere.

编写托管代码是一种无需担心x86(32位)与x64(64位)细节的好方法。 编写托管代码,进行编译,它可在所有地方使用。

The most important take away, from MSDN:

最重要的收获,来自MSDN:

"If you have 100% type safe managed code then you really can just copy it to the 64-bit platform and run it successfully under the 64-bit CLR."

“如果您拥有100%类型的安全托管代码,那么您真的可以将其复制到64位平台上,并在64位CLR下成功运行。”

If you do that, you're golden. Moving right along.

如果您这样做的话,那就太棒了。 向右移动。

WARNING: This is obscure and you probably don't care. But, that's this blog.

警告:这是晦涩的,您可能不在乎。 但是,那是这个博客。

回到32位和64位.NET基础 (Back to 32-bit vs. 64-bit .NET Basics)

I promote 64-bit a lot because I personally think my 64-bit machine is snappier and more stable. I also have buttloads of RAM which is nice.

我之所以提倡64位,是因为我个人认为我的64位计算机更敏捷,更稳定。 我也有很多RAM,这很不错。

The first question developer ask me when I'm pushing 64-bit is "Can I still run Visual Studio the same? Can I make apps that run everywhere?" Yes, totally. I run VS2008 all day on x64 machines writing ASP.NET apps, WPF apps, and Console apps and don't give it a thought. When I'm developing I usually don't sweat any of this. Visual Studio 2008 installs and runs just fine.

当我推动64位时,开发人员会问的第一个问题是“我是否仍可以运行Visual Studio?我可以制作可在任何地方运行的应用程序吗?” 是的,完全是。 我整天都在x64机器上运行VS2008,该机器编写ASP.NET应用程序,WPF应用程序和控制台应用程序,但请不要犹豫。 当我开发时,我通常不会流汗。 Visual Studio 2008可以安装并正常运行。

If you care about details, when you install .NET on a 64-bit machine the package is bigger because you're getting BOTH 32-bit and 64-bit versions of stuff. Some of the things that are 64-bit specific are:

如果您关心细节,则在64位计算机上安装.NET时,程序包会更大,因为您同时获得了32位和64位版本。 一些特定于64位的东西是:

  • Base class libraries (System.*)

    基类库( System。* )

  • Just-In-Time compiler

    即时编译器
  • Debugging support

    调试支持
  • .NET Framework SDK

    .NET Framework SDK

For example, I have a C:\Windows\Microsoft.NET\Framework and a C:\Windows\Microsoft.NET\Framework64 folder.

例如,我有一个C:\ Windows \ Microsoft.NET \ Framework和一个C:\ Windows \ Microsoft.NET \ Framework64文件夹。

If I File|New Project and make a console app, and run it, this is what I'll see in the Task Manager:

如果我归档|新建项目并制作一个控制台应用程序并运行它,这将在任务管理器中看到:

64-bit app running in Task Manager

Notice that a bunch of processes have *32 by their names, including devenv.exe? Those are all 32-bit processes. However, my ConsoleApplication1.exe doesn't have that. It's a 64-bit process and it can access a ridiculous amount of memory (if you've got it...like 16TB, although I suspect the GC would be freaking out at that point.)

注意,一堆进程的名称带有* 32,包括devenv.exe吗? 这些都是32位进程。 但是,我的ConsoleApplication1.exe没有该文件。 这是一个64位进程,它可以访问大量的内存(如果有的话……就像16TB,尽管我怀疑那时候GC会崩溃了。)

That 64-bit process is also having its code JIT compiled to use not the x86 instruction set we're used to, but the AMD64 instruction set. This is important to note: It doesn't matter if you have an AMD or an Intel processor, if you're 64-bit you are using the AMD64 instruction set. The short story is - Intel lost. For us, it doesn't really matter.

该64位进程还对其代码JIT进行了编译,以不使用我们惯常使用的x86指令集,而是使用AMD64指令集。 需要特别注意的是:不管您使用的是AMD还是Intel处理器,如果您使用的是64位,则使用的是AMD64指令集。 简短的故事是-英特尔输了。 对于我们来说,这并不重要。

Now, if I right click on the Properties dialog for this Project in Visual Studio I can select the Platform Target from the Build Tab:

现在,如果我在Visual Studio中右键单击该项目的“属性”对话框,则可以从“构建”选项卡中选择“平台目标”:

By default, the Platform Target is "Any CPU." Remember that our C# or VB compiles to IL, and that IL is basically processor agnostic. It's the JIT that makes the decision at the last minute.

默认情况下,平台目标为“任何CPU”。 请记住,我们的C#或VB可以编译为IL,并且IL基本上与处理器无关。 在最后一刻做出决定的是JIT。

In my case, I am running 64-bit and it was set to Any CPU so it was 64-bit at runtime. But, to repeat (I'll do it a few times, so forgive me) the most important take away, from MSDN:

就我而言,我正在运行64位,并且将其设置为Any CPU,因此在运行时为64位。 但是,重复一遍(我会做几次,所以请原谅),最重要的是从MSDN上摘下来的:

"If you have 100% type safe managed code then you really can just copy it to the 64-bit platform and run it successfully under the 64-bit CLR."

“如果您拥有100%类型的安全托管代码,那么您真的可以将其复制到64位平台上,并在64位CLR下成功运行。”

Let me switch it to x86 and look at Task Manager and we see my app is now 32-bit.

让我将其切换到x86并查看“任务管理器”,我们看到我的应用程序现在是32位。

image

Cool, so...

太好了

32位与64位-为什么要关心?(32-bit vs. 64-bit - Why Should I Care?)

Everyone once in a while you'll need to call from managed code into unmanaged and you'll need to give some thought to 64-bit vs. 32-bit. Unmanaged code cares DEEPLY about bitness, it exists in a processor specific world.

偶尔,每个人都需要从托管代码调用到非托管代码,并且需要考虑一下64位和32位。 非托管代码深深地关心位数,它存在于特定于处理器的世界中。

Here's a great bit from an older MSDN article that explains part of it with emphasis mine:

这是旧的MSDN文章中的大部分内容,其中重点介绍了部分内容:

In 64-bit Microsoft Windows, this assumption of parity in data type sizes is invalid. Making all data types 64 bits in length would waste space, because most applications do not need the increased size. However, applications do need pointers to 64-bit data, and they need the ability to have 64-bit data types in selected cases. These considerations led the Windows team to select an abstract data model called LLP64 (or P64). In the LLP64 data model, only pointers expand to 64 bits; all other basic data types (integer and long) remain 32 bits in length.

在64位Microsoft Windows中,这种对数据类型大小进行奇偶校验的假设是无效的。 将所有数据类型的长度设置为64位会浪费空间,因为大多数应用程序不需要增加大小。 但是,应用程序确实需要指向64位数据的指针,并且在某些情况下,它们需要具有64位数据类型的能力。 这些考虑导致Windows团队选择了称为LLP64(或P64)的抽象数据模型。 在LLP64数据模型中,仅指针扩展为64位;只有指针扩展为64位。 所有其他基本数据类型(整数和长整数)的长度保持32位。

The .NET CLR for 64-bit platforms uses the same LLP64 abstract data model. In .NET there is an integral data type, not widely known, that is specifically designated to hold 'pointer' information: IntPtr whose size is dependent on the platform (e.g., 32-bit or 64-bit) it is running on. Consider the following code snippet:

用于64位平台的.NET CLR使用相同的LLP64抽象数据模型。 在.NET中,有一个整数数据类型,尚未广为人知,专门用于保存“指针”信息:IntPtr的大小取决于运行它的平台(例如32位或64位)。 考虑以下代码片段:

[C#]
public void SizeOfIntPtr() {
Console.WriteLine( "SizeOf IntPtr is: {0}", IntPtr.Size );
}

  
 [C#]
public void SizeOfIntPtr() {
Console.WriteLine( "SizeOf IntPtr is: {0}", IntPtr.Size );
}

When run on a 32-bit platform you will get the following output on the console:

在32位平台上运行时,您将在控制台上获得以下输出:

SizeOf IntPtr is: 4

  

On a 64-bit platform you will get the following output on the console:

在64位平台上,您将在控制台上获得以下输出:

SizeOf IntPtr is: 8
 SizeOf IntPtr is: 8

Short version: If you're using IntPtrs, you care. If you're aware of what IntPtrs are, you likely know this already.

简短版:如果您使用的是IntPtrs,则需要在意。 如果您知道什么是IntPtr,您可能已经知道了。

If you checked the property System.IntPtr.Size while running as x86, it'll be 4. It'll be 8 while under x64. To be clear, if you are running x86 even on an x64 machine, System.IntPtr.Size will be 4. This isn't a way to tell the bitness of your OS, just the bitness of your running CLR process.

如果您在以x86运行时选中了System.IntPtr.Size属性,它将为4。在x64下,它将为8。 需要明确的是,即使您在x64机器上运行x86,System.IntPtr.Size也将为4。这不是告诉OS位数的方法,而只是告诉您正在运行的CLR进程的位数。

Here's a concrete example. It's a pretty specific edgy thing, but a decent example. Here's an app that runs fine on x86.

这是一个具体的例子。 这是一个非常具体的前卫事物,但却是一个不错的例子。 这是一个可以在x86上正常运行的应用程序。

using System;
using System.Runtime.InteropServices;

namespace TestGetSystemInfo
{
public class WinApi
{
[DllImport("kernel32.dll")]
public static extern void GetSystemInfo([MarshalAs(UnmanagedType.Struct)] ref SYSTEM_INFO lpSystemInfo);

[StructLayout(LayoutKind.Sequential)]
public struct SYSTEM_INFO
{
internal _PROCESSOR_INFO_UNION uProcessorInfo;
public uint dwPageSize;
public IntPtr lpMinimumApplicationAddress;
public int lpMaximumApplicationAddress;
public IntPtr dwActiveProcessorMask;
public uint dwNumberOfProcessors;
public uint dwProcessorType;
public uint dwAllocationGranularity;
public ushort dwProcessorLevel;
public ushort dwProcessorRevision;
}

[StructLayout(LayoutKind.Explicit)]
public struct _PROCESSOR_INFO_UNION
{
[FieldOffset(0)]
internal uint dwOemId;
[FieldOffset(0)]
internal ushort wProcessorArchitecture;
[FieldOffset(2)]
internal ushort wReserved;
}
}

public class Program
{
public static void Main(string[] args)
{
WinApi.SYSTEM_INFO sysinfo = new WinApi.SYSTEM_INFO();
WinApi.GetSystemInfo(ref sysinfo);
Console.WriteLine("dwProcessorType ={0}", sysinfo.dwProcessorType.ToString());
Console.WriteLine("dwPageSize ={0}", sysinfo.dwPageSize.ToString());
Console.WriteLine("lpMaximumApplicationAddress ={0}", sysinfo.lpMaximumApplicationAddress.ToString());
}
}
}

See the mistake? It's not totally obvious. Here's what's printed under x86 (by changing the project properties):

看到错误了吗? 这不是很明显。 这是在x86下打印的内容(通过更改项目属性):

dwProcessorType =586
dwPageSize      =4096
lpMaximumApplicationAddress =2147418111

dwProcessorType = 586 dwPageSize = 4096 lpMaximumApplicationAddress = 2147418111

The bug is subtle because it works under x86. As said in the P/Invoke Wiki:

该错误很细微,因为它可以在x86下运行。 如P / Invoke Wiki中所述:

The use of int will appear to be fine if you only run the code on a 32-bit machine, but will likely cause your application/component to crash as soon as it gets on a 64-bit machine.

如果仅在32位计算机上运行代码,则使用int似乎很好,但是一旦在64位计算机上运行应用程序/组件,它可能会导致崩溃。

In our case, it doesn't crash, but it sure is wrong. Here's what happens under x64 (or AnyCPU and running it on my x64 machine):

在我们的情况下,它不会崩溃,但是肯定是错误的。 这是在x64(或AnyCPU上并在我的x64计算机上运行)下发生的情况:

dwProcessorType =8664
dwPageSize      =4096
lpMaximumApplicationAddress =-65537

dwProcessorType = 8664 dwPageSize = 4096 lpMaximumApplicationAddress = -65537

See that lat number? Total crap. I've used an int as a pointer to lpMaximumApplicationAddress rather than an IntPtr. Remember that IntPtr is smart enough to get bigger and point to the right stuff. When I change it from int to IntPtr:

看到那个经纬度吗? 总废话。 我使用int作为指向lpMaximumApplicationAddress的指针,而不是IntPtr。 请记住,IntPtr非常聪明,可以变大并指向正确的东西。 当我将其从int更改为IntPtr时:

dwProcessorType =8664
dwPageSize      =4096
lpMaximumApplicationAddress =8796092956671

dwProcessorType = 8664 dwPageSize = 4096 lpMaximumApplicationAddress = 8796092956671

That's better. Remember that IntPtr is platform/processor specific.

这样更好请记住,IntPtr是特定于平台/处理器的。

You can still use P/Invokes like this and call into unmanaged code if you're on 64-bit or if you're trying to work on both platforms, you just need to be thoughtful about it.

如果您使用的是64位,或者如果您尝试在两种平台上工作,您仍然可以像这样使用P / Invokes并调用非托管代码,您只需要考虑周全。

陷阱:其他装配体 (Gotchas: Other Assemblies)

You'll also want to think about the assemblies that your application loads. It's possible that when purchasing a vendor's product in binary form or bringing an Open Source project into your project in binary form, that you might consume an x86 compiled .NET assembly. That'll work fine on x86, but break on x64 when your AnyCPU compiled EXE runs as x64 and tries to load an x86 assembly. You'll get a BadImageFormatException as I did in this post.

您还需要考虑应用程序加载的程序集。 当以二进制形式购买供应商的产品或以二进制形式将一个开源项目带入您的项目时,可能会消耗一个x86编译的.NET程序集。 可以在x86上正常工作,但是当AnyCPU编译的EXE以x64运行并尝试加载x86程序集时,则在x64上中断。 正如我在这篇文章中所做的那样,您将获得BadImageFormatException

The MSDN article gets it right when it says:

MSDN文章正确地说:

...it is unrealistic to assume that one can just run 32-bit code in a 64-bit environment and have it run without looking at what you are migrating.

……假设一个人只能在64位环境中运行32位代码而无需查看要迁移的内容就可以运行它是不现实的。

As mentioned earlier, if you have 100% type safe managed code then you really can just copy it to the 64-bit platform and run it successfully under the 64-bit CLR.

如前所述,如果您拥有100%类型的安全托管代码,那么您实际上可以将其复制到64位平台并在64位CLR下成功运行。

But more than likely the managed application will be involved with any or all of the following:

但是,受管应用程序很可能会涉及以下任何或全部:

  • Invoking platform APIs via p/invoke

    通过p / invoke调用平台API

  • Invoking COM objects

    调用COM对象

  • Making use of unsafe code

    使用不安全的代码

  • Using marshaling as a mechanism for sharing information

    使用封送处理作为共享信息的机制

  • Using serialization as a way of persisting state

    使用序列化作为保持状态的一种方式

Regardless of which of these things your application is doing it is going to be important to do your homework and investigate what your code is doing and what dependencies you have. Once you do this homework you will have to look at your choices to do any or all of the following:

无论您的应用程序在执行哪些操作,完成作业并调查代码在做什么以及具有哪些依赖关系都将非常重要。 完成此作业后,您将必须选择执行以下任何或所有操作:

  • Migrate the code with no changes.

    无需更改即可迁移代码。

  • Make changes to your code to handle 64-bit pointers correctly.

    更改您的代码以正确处理64位指针。

  • Work with other vendors, etc., to provide 64-bit versions of their products.

    与其他供应商等合作,提供其产品的64位版本。

  • Make changes to your logic to handle marshaling and/or serialization.

    更改逻辑以处理封送处理和/或序列化。

There may be cases where you make the decision either not to migrate the managed code to 64-bit, in which case you have the option to mark your assemblies so that the Windows loader can do the right thing at start up. Keep in mind that downstream dependencies have a direct impact on the overall application.

在某些情况下,您可能会决定不将托管代码迁移到64位,在这种情况下,您可以选择标记程序集,以便Windows加载程序在启动时可以做正确的事情。 请记住,下游依赖关系直接影响整个应用程序。

I have found that for smaller apps that don't need the benefits of x64 but need to call into some unmanaged COM components that marking the assemblies as x86 is a reasonable mitigation strategy.  The same rules apply if you're making a WPF app, a Console App or an ASP.NET app. Very likely you'll not have to deal with this stuff if you're staying managed, but I wanted to put it out there in case you bump into it.

我发现对于不需要x64优点但需要调用一些非托管COM组件的较小应用程序,这些组件将程序集标记为x86是一种合理的缓解策略。 如果要制作WPF应用程序,控制台应用程序或ASP.NET应用程序,则适用相同的规则。 如果您保持管理状态,很可能不需要处理这些东西,但是我想把它放在那里,以防您碰到它。

Regardless, always test on x86 and x64.

无论如何,请始终在x86和x64上进行测试。

Related Posts

相关文章

翻译自: https://www.hanselman.com/blog/back-to-basics-32bit-and-64bit-confusion-around-x86-and-x64-and-the-net-framework-and-clr

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值