CArray MFC序列化多平台,16位、32位和64位。

5

我正在处理非常古老的遗留代码,并将其从32位移植到64位。

我遇到问题的其中一个方面是MFC序列化。 32位和64位之间的差异之一是指针数据的大小。 这意味着,例如,如果出于某种原因我已经对CArray的大小进行了序列化,则需要考虑指针大小的变化。

ar << m_array.GetSize();

32位和64位平台之间的数据不同,因为GetSize返回一个INT_PTR。为了获得与在32位和64位编译的同一应用程序完全兼容的序列化数据,在存储阶段强制数据类型,并在读取时进行相同操作。(非常确定32位对于这个数据足够了)

存储

ar << (int)m_array.GetSize();

读取

int iNumSize = 0;
ar >> iNumSize ;

换句话说,无论是编译成32位还是64位的应用程序,都会将这些数据序列化为int。现在我对CArray类型的序列化有一个疑问;为了序列化一个CArray,代码使用了内置的CArchive序列化。
//defined as CArray m_arrayVertex; on .h
m_arrayVertex.Serialize(ar);

而这个Serialize是在 MFC 文件 afxtemp.h 中使用此模板定义的

template<class TYPE, class ARG_TYPE>
void CArray<TYPE, ARG_TYPE>::Serialize(CArchive& ar)
{
    ASSERT_VALID(this);

    CObject::Serialize(ar);
    if (ar.IsStoring())
    {
        ar.WriteCount(m_nSize);
    }
    else
    {
        DWORD_PTR nOldSize = ar.ReadCount();
        SetSize(nOldSize, -1);
    }
    SerializeElements<TYPE>(ar, m_pData, m_nSize);
}

位于 (afx.h) 文件中

// special functions for reading and writing (16-bit compatible) counts
DWORD_PTR ReadCount();
void WriteCount(DWORD_PTR dwCount);

这是我的问题:`ReadCount` 和 `WriteCount` 使用 `DWORD_PTR`,它们在不同平台上的大小不同……这种序列化能够兼容32位/64位吗?或者由于大小的变化,序列化的数据只能在各自的平台上工作?
我的意思是,这些数据可以被32位和64位应用程序都读取而没有错误吗?评论中说它也适用于“16位”,但我没有找到有关此序列化的详细信息。
如果这样做行不通,是否有一种解决方法可以序列化 `CArray`,以使数据与32位和64位应用程序完全兼容?
编辑:两个答案都很好。我只是接受第一个给出的答案作为解决方案。非常感谢两位,希望能帮助其他人!

2
编译和兼容性取决于平台,但如果您愿意的话,实现并不是“通用”的。MFC的源代码是开放的(在最新的Visual Studio构建中位于C:\ Program Files \ Microsoft Visual Studio \ 2022 \ Professional \ VC \ Tools \ MSVC \ 14.34.31933 \ atlmfc \ src \ mfc中),因此您可以自行检查所需的所有内容。这是在线的https://github.com/pixelspark/corespark/blob/master/Libraries/atlmfc/src/mfc/arccore.cpp#L632 - Simon Mourier
我总是选择在位版本之间不会改变的变量类型。 - Andrew Truckle
@Andrew,你的意思是什么?如果我创建了一个CArray<userStruct_Int>,并且userStruct_Int是一个只包含int值的结构体,那么它应该可以被两个平台都读取吗? - GiordiX
如果您打算支持32位/64位应用程序来读取数据,则序列化int类型总是一个坏主意,因为int类型的大小不同。这就是为什么在提供的答案中都使用像我已经说明的变量。因此,如果您要进行强制转换,请将其转换为在不同位版本之间保持恒定的变量。 - Andrew Truckle
1
@AndrewTruckle 在 MSVC 中,intlong 无论目标架构如何都是32位。运行代码的操作系统没有影响,编译器已经做出了决定。更多信息请参见这里 - IInspectable
显示剩余8条评论
2个回答

4

根据您所写的,ReadCount返回一个DWORD_PTR,它的宽度为32位或64位,具体取决于代码是作为32位还是64位代码编译的。

现在只要实际对象计数适合32位,那么在由32位或64位程序编写的文件之间进行互操作就没有问题。

另一方面,如果您的64位代码序列化了一个具有超过4294967295个元素的CArray(这很不可能发生),那么如果您想从32位程序中读取反序列化此文件,则会遇到麻烦。但在32位程序上,CArray无论如何都不能存储超过4294967295个元素。

长话短说,您不需要做任何特殊处理,只需对数据进行序列化/反序列化即可。


我不确定我是否理解正确。升级不是问题,但如果我们在x64编译中序列化(写入)数据,然后在x32编译中反序列化(读取)回来,这会有什么问题吗? - Tom Tom
@TomTom,不,这一点都不是问题。还要阅读IInspectable的答案,那个更加详细。 - Jabberwocky

3

CArray实例的项目计数的存储和检索分别在CArchive::WriteCountCArchive::ReadCount中实现。

它们将16位(WORD)、32位(DWORD)或64位(在64位平台上,DWORD_PTR)值写入或从流中读取。写入使用以下算法:

  • 如果项目计数小于0xFFFF,则将项目计数作为16位WORD值写入
  • 否则,在流中转储一个“无效值”标记((WORD)0xFFFF),然后跟随以下内容:
    • 32位:项目计数作为32位值(DWORD
    • 64位:如果项目计数小于0xFFFF'FFFF,则将项目计数作为32位DWORD值写入
      • 否则,在流中转储一个“无效值”标记((DWORD)0xFFFFFFFF),然后跟随项目计数作为64位值(DWORD_PTR

根据CArray中的项目计数,流布局如下表所示(其中❌表示流中不存在的值):

项数 n WORD DWORD DWORD_PTR
n < 0xFFFF n
0xFFFF <= n < 0xFFFF'FFFF 0xFFFF n
n == 0xFFFF'FFFF (仅限32位) 0xFFFF 0xFFFF'FFFF
0xFFFF'FFFF <= n (仅限64位) 0xFFFF 0xFFFF'FFFF n

当反序列化流时,代码会读取项目计数值,检查它是否与“无效值”标记匹配,并在找到标记后继续使用更大的值。

只要 CArray 不保存超过 0xFFFF'FFFE 个值,这就可以跨比特率工作。对于32位平台,这始终为真;您不能有一个使用整个地址空间的 CArray

当从64位进程进行序列化时,您只需要确保数组中不超过 0xFFFF'FFFE 项。


摘要:

对于小于0xFFFF'FFFF(4294967295)个项目的CArray,无论是在32位平台还是64位平台上创建的,序列化流都是逐字节相同的。

有一个奇怪的角落情况,即在32位平台上恰好有0xFFFF'FFFF个项目的CArray1。如果将其流出并在64位平台上读回,则流中的大小字段将被误认为是“无效值”标记,造成灾难性后果。幸运的是,我们不需要担心这种情况。32位进程无法分配与可用地址空间大小成倍数的容器。

这涵盖了在32位平台上序列化的流在64位平台上被消耗的情况。实际上,一切都按设计工作。

那么转向另一个方向:在64位平台上创建的流将在32位平台上进行反序列化。这里唯一相关的分歧是容器大于32位程序可以表示的大小。64位序列化器将放置一个“无效值”标记(DWORD),后跟实际项目计数(DWORD_PTR2。32位反序列化器将假定标记(0xFFFF'FFFF)是真实的项目计数,并在未查看实际项目计数的情况下失败后续内存分配。在发生任何数据损坏之前,使用任何异常处理拆除事物3
虽然如此,这并不是新颖的错误模式,也不是跨位互操作性所特有的。在32位平台上序列化的CArray同样可能无法在32位平台上反序列化,如果进程耗尽资源,则可能比耗尽内存早得多,因为CArray需要连续内存。

1 上表第3行。
2 上表第4行。
3 假设调用栈中没有忽略异常的catch(...)


1
基本上和我的答案一样,只不过更好。 - Jabberwocky
2
@Jabberwocky 它在客观上更长。我不确定它是否更好。有些人总是匆忙,而“短”可能更符合他们的注意力范围。 - IInspectable

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