Передача данных между библиотеками dll

При разработке SkyXEngine, перед нами на всегда вставала важная проблема — как осуществить общение dll библиотек друг с другом? Как будет происходить передача данных между библиотеками dll? Очень часто мы обходились путем прямой посылки данных в библиотеку. Но когда кода стало слишком много, мы поняли что это неправильно.

Пересылка данных в виде функций:

void LibSetData(Data *data);

это не плохо, но все зависит от ситуации, и наша ситуация как раз говорила что это плохо))

Совсем недавно мы опробовали 2 метода общения библиотек, причем основаны они на одном принципе.

Примечание: абзац ниже это очень простое и приблизительное описание, которое может не совпадать с научной трактовкой, и данное описание приведено здесь лишь для отражения сути!

Для начала стоит понять что программа использующая dll, создает новый ее инстанс, в итоге данные в dll буду доступны этой программе. Если говорить масштабнее — все используемые в данный момент dll имеют свой индивидуальный инстанс принадлежащий программе, и этот инстанс единый, и не зависит от связей загрузки и использования другими dll в текущем контексте. То есть, есть программа, она использует несколько dll каждая из которых подключает другую dll. В итоге каждая dll будет иметь один инстанс на текущий контекст созданный программой. Иными словами, данные в dll при загрузке всеми библиотеками и exe в том числе, будут созданы единожды и доступны всем бибилиотекам и exe при наличии доступа.

Для меня это было не совсем понятно, хотя кому то может показаться вполне очевидным))

Псевдорегистры данных

Первый способ я подсмотрел в DirectX 9, а именно в регистрах матриц. Суть чрезвычайно проста. Ядро всей программы (у нас это dll) хранит в себе регистры (псевдорегистры), а точнее простые статические массивы вида:

bool g_aGRegistersBool[CORE_REGISTRY_SIZE];
int32_t g_aGRegistersInt[CORE_REGISTRY_SIZE];
float32_t g_aGRegistersFloat[CORE_REGISTRY_SIZE];
float4x4 g_aGRegistersMatrix[CORE_REGISTRY_SIZE];
float3 g_aGRegistersFloat3[CORE_REGISTRY_SIZE];
String g_aGRegistersString[CORE_REGISTRY_SIZE];

И для каждого типа регистра, имеется функции получения и установки значения, которая экспортируется из этой же dll. Все чрезвычайно просто.

При таком подходе каждая библиотека должна знать что хранится в определенном ключе массива/регистре. Мы сделали определенный файл с индексами регистров, который подключается к каждой библиотеки, чтобы та знала где и что хранится. Фрагмент из этого файла:

#define G_RI_FLOAT_OBSERVER_NEAR		0	/*!< ближняя плоскость отсечения наблюдателя */
#define G_RI_FLOAT_OBSERVER_FAR			1	/*!< дальняя плоскость отсечения наблюдателя или дальность видимости */
#define G_RI_FLOAT_OBSERVER_FOV			2	/*!< fov наблюдателя */
#define G_RI_FLOAT_WINSIZE_WIDTH		3	/*!< ширина окна рендера */
#define G_RI_FLOAT_WINSIZE_HEIGHT		4	/*!< высота окна рендера */

Мы в регистрах храним необходимые данные для рендера:

  • матрицы и их данные (к примеру near far)
  • позиции и направления камер
  • время
  • пути

Передача данных между библиотеками строго фиксирована набором регистров данных. Все индексы псевдорегистров заранее четко определены.

Лично мне такой подход очень нравится, так как все становится единообразным.

CVar — config variables

Второй метод  разработал Евгений. В общем он представляет собой некие именованные псевдорегистры — квары (CVar — config variables). Суть та же, только доступ по имени. Сюда же была прикручена загрузка из файлов настроек. Что очень удобно при конфигурировании.

Проблем со скоростью доступа нет, мы используем вот так:

static const int *winr_width = GET_PCVAR_INT("winr_width");

в итоге winr_width содержит всегда актуальные данные. Для изменения в участках кода не критичных по времени исполнения можно использовать функцию для установки значения, а там где время критично можно просто:

static int *winr_width = (int*)GET_PCVAR_INT("winr_width");

При двух вышеописанных методах организации, передача данных между библиотеками dll будет очень удобной. Но стоит различать между собой эти методы:

  • псевдорегистры больше подходят для run-time данных, то есть тех данных которые используются в момент исполнения кода и не могут задаваться заранее (или почти не могут), к примеру это матрицы мира, вида, проекции
  • квары больше похожи на настройки, которые могут изменяться во время исполнения.

После написания данной статьи, мы столкнулись с тем что сами неверно в плане семантики применяли данные методы, но исправились))

В итоге при применении вышеописанных методов:

  • избавились от лишних функций в dll, теперь не надо напрямую передавать данные,
  • избавились от кода хранения данных в каждой dll которая их использует, теперь только одна dll имеет эти данные, а остальные могут у нее их получить,
  • полностью стандартизировали интерфейс общение библиотек

 

Поделиться:

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

*

*

code