正如其他人所说,为了二进制兼容性,您很可能会将自己限制为 C API。
许多地方的 Windows API 通过将size
成员放入结构中来保持二进制兼容性:
struct PluginInfo
{
std::size_t size; // should be sizeof(PluginInfo)
const char* s_Author;
const char* s_Process;
const char* s_ReleaseDate;
//And so on...
struct PluginVersion
{
const char* s_MajorVersion;
const char* s_MinorVersion;
//And so on...
};
PluginVersion o_Version;
};
当您创建这样的野兽时,您需要size
相应地设置成员:
PluginInfo pluginInfo;
pluginInfo.size = sizeof(pluginInfo);
// set other members
当您针对 API 的较新版本编译代码时,其中struct
有其他成员,其大小会发生变化,并在其size
成员中注明。API 函数在被传递时,struct
大概会首先读取它的成员,然后根据它的大小size
分支到不同的方式来处理.struct
当然,这假设进化是线性的,并且新数据总是只在struct
. 也就是说,您永远不会拥有具有相同大小的此类类型的不同版本。
然而,使用这样的野兽是确保用户在他们的代码中引入错误的好方法。当他们针对新的 API 重新编译代码时,sizeof(pluginInfo)
会自动适应,但不会自动设置其他成员。struct
通过“初始化” C方式可以获得合理的安全性:
PluginInfo pluginInfo;
std::memset( &pluginInfo, 0, sizeof(pluginInfo) );
pluginInfo.size = sizeof(pluginInfo);
但是,即使从技术上讲,归零内存可能不会为每个成员提供合理的值(例如,可能存在所有位设置为零的体系结构不是浮点类型的有效值)这一事实,这很烦人并且容易出错,因为它需要三步构建。
一种出路是围绕该 C API 设计一个小的内联 C++ 包装器。就像是:
class CPPPluginInfo : PluginInfo {
public:
CPPPluginInfo()
: PluginInfo() // initializes all values to 0
{
size = sizeof(PluginInfo);
}
CPPPluginInfo(const char* author /* other data */)
: PluginInfo() // initializes all values to 0
{
size = sizeof(PluginInfo);
s_Author = author;
// set other data
}
};
该类甚至可以负责将 C 的成员指向的字符串存储struct
在缓冲区中,这样该类的用户甚至不必担心这一点。
编辑:因为这似乎不像我想象的那么明确,这里有一个例子。
假设struct
在更高版本的 API 中同样会获得一些额外的成员:
struct PluginInfo
{
std::size_t size; // should be sizeof(PluginInfo)
const char* s_Author;
const char* s_Process;
const char* s_ReleaseDate;
//And so on...
struct PluginVersion
{
const char* s_MajorVersion;
const char* s_MinorVersion;
//And so on...
};
PluginVersion o_Version;
int fancy_API_version2_member;
};
当链接到旧版本 API 的插件现在struct
像这样初始化时
PluginInfo pluginInfo;
pluginInfo.size = sizeof(pluginInfo);
// set other members
它将struct
是旧版本,缺少 API 版本 2 中新的闪亮数据成员。如果它现在调用第二个接受指向 的 API 的函数PluginInfo
,它将把旧的PluginInfo
、短的一个数据成员的地址传递给新 API 的函数。但是,对于第 2 版 API 函数,pluginInfo->size
将小于sizeof(PluginInfo)
,因此它将能够捕获它,并将指针视为指向不具有fancy_API_version2_member
. (推测,宿主应用程序 API 的内部,PluginInfo
是带有 , 的新的闪亮的fancy_API_version2_member
,并且PluginInfoVersion1
是旧类型的新名称。所以所有新的 API 需要做的就是将PluginInfo*
它交给插件转换成一个PluginInfoVersion1*
和分支到可以处理那个尘土飞扬的旧东西的代码。)
另一种方法是针对新版本的 API 编译的插件,其中PluginInfo
包含fancy_API_version2_member
,插入到对它一无所知的旧版本的主机应用程序中。同样,宿主应用程序的 API 函数可以通过检查是否pluginInfo->size
大于它们sizeof
自己的PluginInfo
. 如果是这样,则该插件可能是针对比主机应用程序所知的更新版本的 API 编译的。(或者插件写入未能正确初始化size
成员。请参阅下文了解如何简化处理这个有点脆弱
的方案。)有两种处理方法: 最简单的是拒绝加载插件。或者,如果可能的话,主机应用程序无论如何都可以使用它,只需忽略末尾的二进制内容PluginInfo
它通过了它不知道如何解释的对象。但是,后者很棘手,因为您需要在实现旧 API 时
决定这一点,而无需确切知道新 API 的外观。