将 32 位托管代码迁移到 64 位

 

Microsoft Corporation

更新时间:2005 年 5 月

适用于:
   Microsoft .NET
   Microsoft .NET Framework 2.0

总结: 了解将 32 位托管应用程序迁移到 64 位所涉及的内容、可能影响迁移的问题以及可用于帮助你的工具。 (17 个打印页)

目录

简介
32 位环境中的托管代码
输入 64 位环境的 CLR
迁移和平台调用
迁移和 COM 互操作性
迁移和不安全代码
迁移和封送处理
迁移和序列化
总结

简介

本白皮书讨论:

  • 将托管应用程序从 32 位迁移到 64 位所涉及的内容
  • 可能影响迁移的问题
  • 哪些工具可帮助你

此信息不应是规范性的;相反,它旨在让你熟悉在迁移到 64 位的过程中容易出现问题的不同方面。 此时,没有具体的“指南”步骤可以遵循,并确保代码适用于 64 位。 本白皮书中包含的信息将让你熟悉不同的问题和应审查的内容。

正如你即将看到的,如果托管程序集不是 100% 类型安全代码,则需要查看应用程序及其依赖项,以确定迁移到 64 位时出现的问题。 你将在下一部分中阅读的许多项目都可以通过编程更改来解决。 在许多情况下,如果希望代码在 32 位和 64 位环境中都正常运行,还需要留出时间来更新代码。

Microsoft .NET 是一组用于连接信息、人员、系统和设备的软件技术。 自 2002 年发布 1.0 以来,组织已成功部署 。基于 NET 的解决方案,无论是由独立软件供应商内部构建的、由独立软件供应商 (ISV) ,还是某种组合。 有几种类型的 .NET 应用程序推送了 32 位环境的限制。 这些挑战包括但不限于对更真实的可寻址内存的需求,以及提高浮点性能的需求。 与在 x86 上相比,x64 和 Itanium 为浮点运算提供了更好的性能。 但是,在 x64 或 Itanium 上获得的结果也可能不同于在 x86 上获得的结果。 64 位平台旨在帮助解决这些问题。

随着 .NET Framework 版本 2.0 的发布,Microsoft 支持在 x64 和 Itanium 64 位平台上运行的托管代码。

托管代码只是“代码”,它提供足够的信息,允许 .NET 公共语言运行时 (CLR) 提供一组核心服务,包括:

  • 通过元数据对代码和数据进行自我描述
  • 堆栈审核
  • 安全性
  • 垃圾回收
  • 实时编译

除了托管代码外,在调查迁移问题时,还有其他几个定义需要了解。

托管数据 - 在托管堆上分配并通过垃圾回收进行收集的数据。

程序集 - 部署单元,使 CLR 能够完全理解应用程序的内容并强制实施应用程序定义的版本控制与依赖项规则。

类型安全代码 - 仅使用托管数据的代码,没有不可验证的数据类型或不支持的数据类型转换/强制操作 (即非可区分联合或结构/接口指针) 。 使用 /clr:safe 编译的 C#、Visual Basic .NET 和 Visual C++ 代码生成类型安全代码。

不安全代码 - 允许执行较低级别的操作的代码,例如对指针进行声明和操作、在指针和整型类型之间执行转换以及获取变量的地址。 此类操作允许与基础操作系统交互、访问内存映射设备或实现时间关键型算法。 本机代码不安全。

32 位环境中的托管代码

为了了解将托管代码迁移到 64 位环境所涉及的复杂性,让我们回顾一下如何在 32 位环境中执行托管代码。

选择要执行托管或非托管的应用程序时,将调用 Windows 加载程序,并负责决定如何加载并执行应用程序。 此过程的一部分涉及查看可执行文件的可移植执行 (PE) 标头,以确定是否需要 CLR。 你可能已经猜到,PE 中有指示托管代码的标志。 在这种情况下,Windows 加载程序启动 CLR,然后负责加载和执行托管应用程序。 (这是对该过程的简化说明,因为涉及许多步骤,包括确定要执行的 CLR 版本、设置 AppDomain“沙盒”等)

互操作性

当托管应用程序运行时,它可以 (假定适当的安全权限) 通过 CLR 互操作性与本机 API 交互 (包括 Win32 API) 和 COM 对象。 无论是调用本机平台 API、发出 COM 请求还是封送结构,在 32 位环境中完全运行时,开发人员无需考虑数据类型大小和数据对齐。

考虑迁移到 64 位时,研究应用程序具有哪些依赖项至关重要。

输入 64 位环境的 CLR

为了使托管代码在与 32 位环境一致的 64 位环境中执行,.NET 团队为 Itanium 和 x64 64 位系统开发了公共语言运行时 (CLR) 。 CLR 必须严格遵守公共语言基础结构 (CLI) 和公共语言类型系统的规则,以确保用任何 .NET 语言编写的代码能够像在 32 位环境中那样进行互操作。 此外,下面列出了还必须为 64 位环境移植和/或开发的一些其他部分:

  • 基类库 (System.*)
  • 实时编译器
  • 调试支持
  • .NET Framework SDK

64 位托管代码支持

.NET Framework版本 2.0 支持运行 Itanium 和 x64 64 位处理器:

  • Windows Server 2003 SP1
  • 将来的 Windows 64 位客户端版本

(无法在 Windows 2000 上安装 .NET Framework 版本 2.0。使用 .NET Framework 版本 1.0 和 1.1 生成的输出文件将在 64 位操作系统的 WOW64 下运行。)

在 64 位平台上安装 .NET Framework 2.0 版时,不仅要安装所有必要的基础结构以在 64 位模式下执行托管代码,而且还要安装托管代码在 Windows 子系统中运行的必要基础结构,或 WoW64 (32 位模式) 。

简单的 64 位迁移

请考虑使用 100% 类型安全代码的 .NET 应用程序。 在这种情况下,可以获取在 32 位计算机上运行的 .NET 可执行文件,并将其移动到 64 位系统并使其成功运行。 为什么这样做? 由于程序集是 100% 类型安全的,我们知道本机代码或 COM 对象没有依赖项,并且没有“不安全”代码,这意味着应用程序完全在 CLR 的控制下运行。 CLR 保证,虽然由于实时 (JIT) 编译而生成的二进制代码在 32 位和 64 位之间是不同的,但执行的代码在语义上是相同的。 (无法在 Windows 2000 上安装 .NET Framework 版本 2.0。使用 .NET Framework 版本 1.0 和 1.1 生成的输出文件将在 64 位操作系统的 WOW64 下运行。)

实际上,从加载托管应用程序的角度来看,前面的方案稍微复杂一些。 如上一部分所述,Windows 加载程序负责决定如何加载和执行应用程序。 但是,与 32 位环境不同,在 64 位 Windows 平台上运行意味着有两个 (2) 环境,可以在本机 64 位模式或 WoW64 中执行应用程序。

Windows 加载程序现在必须基于它在 PE 标头中发现的内容做出决策。 正如你可能猜到的那样,托管代码中有可设置的标志可帮助完成此过程。 (请参阅corflags.exe在 PE 中显示设置。) 以下列表表示在 PE 中找到有助于决策过程的信息。

  • 64 位 - 表示开发人员已生成专门针对 64 位进程的程序集。
  • 32 位 - 表示开发人员已生成专门针对 32 位进程的程序集。 在此实例中,程序集将在 WoW64 中运行。
  • 不可知 — 表示开发人员使用代码为“Whidbey”的 Visual Studio 2005 生成程序集。 或更高版本的工具,并且程序集可以在 64 位或 32 位模式下运行。 在这种情况下,64 位 Windows 加载程序将在 64 位中运行程序集。
  • 旧版 - 表示生成程序集的工具是“pre-Whidbey”。 在这种情况下,程序集将在 WoW64 中运行。

注意 PE 中还有一些信息,告知 Windows 加载程序程序集是否针对特定体系结构。 此附加信息可确保针对特定体系结构的程序集不会加载到其他体系结构中。

C#、Visual Basic .NET 和 C++ Whidbey 编译器允许在 PE 标头中设置适当的标志。 例如,C# 和 THIRD 具有 /platform:{anycpu, x86, Itanium, x64} 编译器选项。

注意 虽然从技术上讲,可以在程序集的 PE 标头中对其进行编译后修改标志,但 Microsoft 不建议这样做。

如果想知道如何在托管程序集上设置这些标志,可以运行 .NET Framework SDK 中提供的 ILDASM 实用工具。 下图显示了一个“旧版”应用程序。

请记住,将程序集标记为 Win64 的开发人员确定应用程序的所有依赖项将在 64 位模式下执行。 64 位进程无法在进程 (中使用 32 位组件,32 位进程无法在进程) 加载 64 位组件。 请记住,系统能够将程序集加载到 64 位进程中并不自动意味着它将正确执行。

因此,我们现在知道,由 100% 类型安全的托管代码组成的应用程序可以复制 (或 xcopy 将其) 部署到 64 位平台,并使其 JIT 并在 64 位模式下通过 .NET 成功运行。

但是,我们经常看到不理想的情况,这让我们来到了本文main重点,即提高人们对迁移相关问题的认识。

可以有一个不是 100% 类型安全的应用程序,并且该应用程序仍能够在 .NET 下的 64 位中成功运行。 请务必仔细查看应用程序,记住以下部分中讨论的潜在问题,并确定是否可以在 64 位中成功运行。

迁移和平台调用

使用平台调用 (或 p/invoke) .NET 的功能是指调用非托管代码或本机代码的托管代码。 在典型方案中,此本机代码是动态链接库, (DLL) ,它是系统 (Windows API 等) 的一部分、应用程序的一部分或第三方库。

使用非托管代码并不意味着迁移到 64 位将出现问题;相反,它应该被视为需要进一步调查的指标。

Windows 中的数据类型

每个应用程序和每个操作系统都有一个抽象数据模型。 许多应用程序不会显式公开此数据模型,但模型会指导应用程序代码的编写方式。 在 32 位编程模型中 (称为 ILP32 模型) ,整数、长和指针数据类型的长度为 32 位。 大多数开发人员在未意识到的情况下使用了此模型。

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

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

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

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

SizeOf IntPtr is: 4

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

SizeOf IntPtr is: 8

注意如果要在运行时检查,无论是否在 64 位环境中运行,都可以使用 IntPtr.Size 作为一种方法来做出此决定。

迁移注意事项

迁移使用 p/invoke 的托管应用程序时,请考虑以下事项:

  • 64 位版本的 DLL 的可用性
  • 数据类型的使用

可用性

需要确定的首要事项之一是应用程序所依赖的非托管代码是否可用于 64 位。

如果此代码是内部开发的,则你的成功能力将提高。 当然,你仍然需要分配资源来将非托管代码移植到 64 位,以及用于测试、质量保证等的适当资源。 (本白皮书未提供有关开发过程的建议;相反,它试图指出,可能需要将资源分配给任务以移植 code.)

如果此代码来自第三方,则需要调查此第三方是否已拥有可用于 64 位的代码,以及第三方是否愿意使其可用。

如果第三方不再为此代码提供支持,或者第三方不愿意执行该工作,则会出现更高的风险问题。 这些情况需要进一步研究执行类似功能的可用库,第三方是否会让客户自己执行端口,等等。

请务必记住,64 位版本的依赖代码可能已更改接口签名,这可能意味着额外的开发工作,并解决应用程序 32 位和 64 位版本之间的差异。

数据类型

使用 p/invoke 要求在 .NET 中开发的代码声明托管代码所面向的方法的原型。 给定以下 C 声明:

[C++]
typedef void * HANDLE
HANDLE GetData();

原型方法的示例如下所示:

[C#]

[DllImport( "sampleDLL", CallingConvention=CallingConvention.Cdecl )]
      public static extern int DoWork( int x, int y );

[DllImport( "sampleDLL", CallingConvention=CallingConvention.Cdecl )]
      public unsafe static extern int GetData();

让我们查看这些示例,了解 64 位迁移问题:

第一个示例调用方法 DoWork ,该方法传入两个 (2) 32 位整数,我们预计返回一个 32 位整数。 即使我们在 64 位平台上运行,整数仍为 32 位。 在此特定示例中,没有任何内容会阻碍我们的迁移工作。

第二个示例要求对代码进行一些更改才能在 64 位中成功运行。 我们在这里执行的操作是调用 GetData 方法,并声明我们期望返回一个整数,但函数实际上会返回 int 指针。 这就是我们的问题所在:请记住,整数是 32 位,但在 64 位指针中是 8 个字节。 事实证明,在 32 位世界中编写了相当多的代码,假定指针和整数的长度相同,即 4 个字节。 在 64 位环境中,这不再是事实。

在最后一种情况下,可以通过更改方法声明以使用 IntPtr 代替 int 来解决该问题。

public unsafe static extern IntPtr GetData();

进行此更改将在 32 位和 64 位环境中都起作用。 请记住,IntPtr 特定于平台。

在托管应用程序中使用 p/invoke 并不意味着无法迁移到 64 位平台。 也不意味着会有问题。 这意味着,必须查看托管应用程序对非托管代码的依赖关系,并确定是否存在任何问题。

迁移和 COM 互操作性

COM 互操作性是 .NET 平台的假设功能。 与前面关于平台调用的讨论一样,使用 COM 互操作性意味着托管代码正在调用非托管代码。 但是,与平台调用不同,COM 互操作性还意味着非托管代码能够像调用 COM 组件一样调用托管代码。

同样,使用非托管 COM 代码并不意味着迁移到 64 位将出现问题;相反,它应该被视为需要进一步调查的指标。

迁移注意事项

请务必了解,随着 .NET Framework 版本 2.0 的发布,不支持体系结构间互操作性。 为了更简洁,不能在同一进程中使用 32 位和 64 位之间的 COM 互操作性。 但是,如果有进程外 COM 服务器,则可以使用 32 位和 64 位之间的 COM 互操作性。 如果无法使用进程外 COM 服务器,则需要将托管程序集标记为 Win32 而不是 Win64 或不可知,以便使程序在 WoW64 中运行,以便它可以与 32 位 COM 对象互操作。

下面讨论了在托管代码在 64 位环境中进行 COM 调用时使用 COM 互操作性时必须给予的不同注意事项。 具体而言:

  • 64 位版本的 DLL 的可用性
  • 数据类型的使用
  • 类型库

可用性

p/invoke 部分中关于依赖代码的 64 位版本可用性的讨论也与本节相关。

数据类型

p/invoke 部分中关于 64 位依赖代码的数据类型的讨论也与本节相关。

类型库

与程序集不同,类型库不能标记为“中性”;它们必须标记为 Win32 或 Win64。 此外,必须为运行 COM 的每个环境注册类型库。 使用 tlbimp.exe 从类型库生成 32 位或 64 位程序集。

在托管应用程序中使用 COM 互操作性并不意味着无法迁移到 64 位平台。 也不意味着会有问题。 这意味着,必须查看托管应用程序具有的依赖项,并确定是否存在任何问题。

迁移和不安全代码

核心 C# 语言在省略作为数据类型的指针方面与 C 和 C++ 明显不同。 相反,C# 提供引用和创建由垃圾回收器管理的对象的功能。 在核心 C# 语言中,根本不可能具有未初始化的变量、“悬空”指针或索引超出其边界的数组的表达式。 因此,消除了经常困扰 C 和 C++ 程序的整个类别的 bug。

虽然 C 或 C++ 中的每个指针类型构造在 C# 中都有对应的引用类型,但在某些情况下,必须访问指针类型。 例如,如果不访问指针,可能无法实现与基础操作系统交互、访问内存映射设备或实现时间关键型算法。 为了满足此需求,C# 提供了编写不安全代码的功能。

在不安全的代码中,可以声明和操作指针,在指针和整型类型之间执行转换,获取变量的地址,等等。 从某种意义上说,编写不安全的代码与在 C# 程序中编写 C 代码非常类似。

从开发人员和用户的角度来看,不安全代码实际上是一项“安全”功能。 不安全的代码必须清楚地标记为修饰符 不安全,因此开发人员不可能意外使用不安全的功能。

迁移注意事项

为了讨论不安全代码的潜在问题,我们来探索以下示例。 托管代码调用非托管 DLL。 具体而言,有一个名为 GetDataBuffer 的方法,该方法返回 100 个项 (在本示例中,我们将返回固定数量的项) 。 其中每个项都包含一个整数和一个指针。 下面的示例代码是托管代码的摘录,其中显示了负责处理此返回数据的不安全函数。

[C#]

public unsafe int UnsafeFn() {
   IntPtr * inputBuffer = sampleDLL.GetDataBuffer();
   IntPtr * ptr = inputBuffer;
   int   result = 0;

   for ( int idx = 0; idx < 100; idx ++ ) {
      // Add 'int' from DLL to our result
      result = result + ((int) *ptr);

// Increment pointer over int (
      ptr = (IntPtr*)( ( (byte *) ptr ) + sizeof( int ) );

      // Increment pointer over pointer (
      ptr = (IntPtr*)( ( (byte *) ptr ) + sizeof( int ) );
   }
   return result;
}

注意 本可以在不使用不安全代码的情况下完成此特定示例。 更具体地说,可以使用其他技术,例如封送处理。 但为此,我们使用不安全的代码。

UnsafeFn 循环访问 100 个项,并将整数数据求和。 在浏览数据缓冲区时,代码需要单步执行整数和指针。 在 32 位环境中,此代码正常工作。 但是,正如我们之前所讨论的,指针在 64 位环境中是 8 个字节,因此下面所示的代码段 () 将无法正常工作,因为它正在使用一种常见的编程技术,例如,将指针视为等效于整数。

// Increment pointer over pointer (
ptr = (IntPtr*)( ( (byte *) ptr ) + sizeof( int ) );

为了使此代码同时在 32 位和 64 位环境中运行,有必要将代码更改为以下内容。

// Increment pointer over pointer (
ptr = (IntPtr*)( ( (byte *) ptr ) + sizeof( IntPtr ) );

如前所述,在某些情况下,需要使用不安全代码。 在大多数情况下,由于托管代码依赖于其他接口,因此需要它。 无论存在不安全代码的原因如何,都必须在迁移过程中对其进行评审。

我们上面使用的示例相对简单,使程序在 64 位中正常工作的修补程序非常简单。 显然,有许多不安全代码的示例更为复杂。 有些需要深入审查,也许退后一步,重新思考托管代码使用的方法。

若要重复已阅读的内容,请在托管应用程序中使用不安全的代码并不意味着无法迁移到 64 位平台。 也不意味着会有问题。 这意味着,必须查看托管应用程序拥有的所有不安全代码,并确定是否存在任何问题。

迁移和封送处理

封送处理提供了一系列方法,用于分配非托管内存、复制非托管内存块、将托管类型转换为非托管类型,以及在与非托管代码交互时使用的其他其他方法。

封送处理通过 .NET 封送 类进行显示。 在 Marshal 类上定义的 静态共享 方法对于处理非托管数据至关重要。 构建需要在托管和非托管编程模型之间架起桥梁的自定义封送处理器的高级开发人员通常使用定义的大多数方法。

迁移注意事项

封送处理带来了与将应用程序迁移到 64 位相关的一些更复杂的挑战。 鉴于开发人员尝试通过封送处理实现的功能的性质,即向托管和非托管代码传输结构化信息,或者从托管和非托管代码传输结构化信息,我们将看到我们提供的信息(有时是低级别)来帮助系统。

在布局方面,开发人员可以进行两个特定的声明:这些声明通常是通过使用编码属性进行的。

LayoutKind.Sequential

让我们查看.NET Framework SDK 帮助中提供的定义:

“对象的成员按导出到非托管内存时出现的顺序按顺序排列。 成员根据 StructLayoutAttribute.Pack 中指定的包装进行布局,并且可能不连续。”

我们被告知,布局特定于其定义顺序。 然后,我们需要做的就是确保托管和非托管声明相似。 但是,我们也被告知,包装也是一个关键成分。 此时,你不会惊讶地发现,如果没有开发人员的显式干预,就会有一个默认的包值。 你可能已经猜到,32 位和 64 位系统之间的默认包值不相同。

定义中有关不连续成员的语句指的是这样一个事实:由于存在默认的包大小,内存中布局的数据可能不是字节 0、字节 1、字节 2 等。相反,第一个 成员的字节为 0,但第二个成员可能位于字节 4。 系统执行此默认打包以允许计算机访问成员,而无需处理未对齐问题。

下面是一个需要密切关注打包的领域,同时,尝试让系统在其首选模式下运行。

下面是托管代码中定义的结构的示例,以及在非托管代码中定义的相应结构。 应仔细注意此示例如何演示在两个环境中设置包值。

[C#]
[StructLayout(LayoutKind.Sequential, Pack=1)]
public class XYZ {
      public byte arraysize = unchecked((byte)-1);
      [MarshalAs(UnmanagedType.ByValArray, SizeConst=52)]
      public int[] padding = new int[13];
};
[unmanaged c++]
#pragma pack(1)
typedef struct{
      BYTE arraysize;      // = (byte)-1;
      int      padding[13];
} XYZ;

LayoutKind.Explicit

让我们查看 .NET FrameworkSDK 帮助中提供的定义:

“显式控制非托管内存中对象每个成员的精确位置。 每个成员必须使用 FieldOffsetAttribute 来指示该字段在类型中的位置。”

我们在此被告知,开发人员将提供确切的偏移量,以帮助封送信息。 因此,开发人员必须正确指定 FieldOffset 属性中的信息。

那么,潜在的问题在哪里呢? 请记住,字段偏移量是在知道数据成员大小的情况下定义的,请务必记住,并非所有数据类型大小都在 32 位和 64 位之间。 具体而言,指针的长度为 4 或 8 个字节。

我们现在有一种情况,我们可能需要更新托管源代码以针对特定环境。 以下示例显示了一个包含指针的结构。 尽管我们已将指针设置为 IntPtr,但在移动到 64 位时仍存在差异。

[C#]
[StructLayout(LayoutKind.Explicit)]
    internal struct FooValue {
        [FieldOffset(0)] public int dwType;
        [FieldOffset(4)] public IntPtr pType;
        [FieldOffset(8)] public int typeValue;
    }

对于 64 位,我们必须调整结构中最后一个数据成员的字段偏移量,因为它实际上从偏移量 12 而不是 8 开始。

[C#]
[StructLayout(LayoutKind.Explicit)]
    internal struct FooValue {
        [FieldOffset(0)] public int dwType;
        [FieldOffset(4)] public IntPtr pType;
        [FieldOffset(12)] public int typeValue;
    }

当托管代码和非托管代码之间需要复杂的互操作性时,使用封送处理是现实。 利用这一强大的功能并不表示可以将 32 位应用程序迁移到 64 位环境。 但是,由于与使用封送处理相关的复杂性,这是一个需要仔细注意细节的领域。

代码分析将指示每个平台是否需要单独的二进制文件,以及是否还必须修改非托管代码来解决打包等问题。

迁移和序列化

序列化是将对象状态转换为可保持或传输的形式的过程。 序列化的补集是反序列化,后者将流转换为对象。 这两个过程一起保证数据易于存储和传输。

.NET Framework 提供了两个序列化技术:

  • 二进制序列化保持类型保真,这对于多次调用应用程序时保持对象状态非常有用。 例如,通过将对象序列化到剪贴板,可在不同的应用程序之间共享对象。 您可以将对象序列化到流、磁盘、内存和网络等。 .NET 远程处理使用序列化将对象“按值”从一个计算机或应用程序域传递到另一个计算机或应用程序域。
  • XML 序列化只序列化公共属性和字段,并且不保持类型保真。 当您希望提供或使用数据而不限制使用该数据的应用程序时,这一点非常有用。 由于 XML 是开放式的标准,因此它对于通过 Web 共享数据来说是一个理想选择。 SOAP 同样是开放式的标准,这使它也成为一个理想选择。

迁移注意事项

当我们考虑序列化时,我们需要记住我们试图实现的目标。 迁移到 64 位时要记住的一个问题是,是否打算在不同的平台之间共享序列化的信息。 换句话说,64 位托管应用程序读取 (还是反序列化) 32 位托管应用程序存储的信息。

你的答案将有助于提高解决方案的复杂性。

  • 你可能想要编写自己的序列化例程来考虑平台。
  • 你可能希望限制信息的共享,同时仍允许每个平台读取和写入自己的数据。
  • 你可能想要重新访问正在序列化的内容并进行更改,以帮助避免某些问题。

那么,在序列化方面有哪些注意事项?

  • IntPtr 的长度为 4 或 8 个字节,具体取决于平台。 如果序列化信息,则会将特定于平台的数据写入输出。 这意味着,如果尝试共享此信息,可能会而且将遇到问题。

如果你考虑我们在上一部分中关于封送和偏移的讨论,你可能会想出一两个关于序列化如何处理打包信息的问题。 对于二进制序列化,.NET 使用基于字节的读取并正确处理数据,在内部使用对序列化流的正确未对齐访问。

如前所述,使用序列化不会阻止迁移到 64 位。 如果使用 XML 序列化,则必须在序列化过程中从 和 本机托管类型转换,从而将你与平台之间的差异隔离开来。 使用二进制序列化可提供更丰富的解决方案,但会创建需要就不同平台如何共享序列化信息做出决策的情况。

总结

迁移到 64 位即将到来,Microsoft 一直在努力使从 32 位托管应用程序到 64 位的转换尽可能简单。

但是,假设可以在 64 位环境中运行 32 位代码,而不查看要迁移的内容即可运行,这是不切实际的。

如前所述,如果你有 100% 类型安全的托管代码,则实际上只需将其复制到 64 位平台,并在 64 位 CLR 下成功运行它。

但托管应用程序很可能涉及以下任何或全部内容:

  • 通过 p/invoke 调用平台 API
  • 调用 COM 对象
  • 使用不安全的代码
  • 使用封送处理作为共享信息的机制
  • 使用序列化作为持久化状态的一种方式

无论应用程序正在执行哪种操作,做家庭作业并调查代码执行的操作以及你拥有的依赖项都很重要。 完成此家庭作业后,必须查看以下任一或全部操作的选择:

  • 迁移代码,无需更改。
  • 更改代码以正确处理 64 位指针。
  • 与其他供应商等合作,提供其产品的 64 位版本。
  • 对逻辑进行更改以处理封送处理和/或序列化。

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

FxCop

还应了解可用于帮助你迁移的工具。

如今,Microsoft 有一个名为 FxCop 的工具,该工具是一种代码分析工具,用于检查 .NET 托管代码程序集是否符合 Microsoft .NET Framework设计指南。 它使用反射、MSIL 分析和调用图分析来检查程序集中以下方面的 200 多个缺陷:命名约定、库设计、本地化、安全性和性能。 FxCop 包括该工具的 GUI 和命令行版本,以及用于创建自己的规则的 SDK。 有关详细信息,请参阅 FxCop 网站。 Microsoft 正在开发其他 FxCop 规则,这些规则将为你提供信息来帮助你完成迁移工作。

还有一个区域托管库函数,可帮助你在运行时确定正在运行的环境。

  • System.IntPtr.Size - 用于确定是在 32 位还是 64 位模式下运行
  • System.Reflection.Module.GetPEKind - 以编程方式查询.exe或.dll,以查看它是否只打算在特定平台上或在 WOW64 下运行

没有一组特定的过程来解决可能遇到的所有挑战。 本白皮书旨在提高你对这些挑战的认识,并介绍可能的替代方案。