Как сказал кто-то другой, для двоичной совместимости вы, скорее всего, ограничитесь API C.
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 ++ вокруг этого C API. Что-то вроде:
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
будет старой версией, в которой отсутствует новый блестящий элемент данных из версии 2 API. Если теперь он вызывает функцию второго API, принимающего указатель на PluginInfo
, он передаст адрес старого PluginInfo
, одного короткого члена данных, в функцию нового API. Однако для функции API версии 2 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.