История изменений
Исправление nikitos, (текущая версия) :
Попробую объяснить на примере с кодом, как есть, и чего хотелось бы достичь.
Работа с OpenCL организована таким образом: в ocl_defs.h находятся описания структур и битовых полей из OpenCL, также есть ocl_api.h следующего содержания:
#include "ocl_defs.h"
typedef CL_API_ENTRY cl_int CL_API_CALL tclGetPlatformIDs(cl_uint, cl_platform_id*, cl_uint*);
typedef CL_API_ENTRY cl_int CL_API_CALL tclGetPlatformInfo(cl_platform_id, cl_platform_info, size_t, void*, size_t*);
typedef CL_API_ENTRY cl_int CL_API_CALL tclGetDeviceIDs(cl_platform_id, cl_device_type, cl_uint, cl_device_id*, cl_uint*);
typedef CL_API_ENTRY cl_int CL_API_CALL tclGetDeviceInfo(cl_device_id, cl_device_info, size_t, void *, size_t *);
/* и так далее описаны типы функций из OpenCL 1.2 */
Пользовательские подсистемы, которые хотят использовать OpenCL для своих вычислений, подключают файл ocl_trampoline.h:
#include "ocl_api.h"
extern tclGetPlatformIDs *clGetPlatformIDs;
extern tclGetPlatformInfo *clGetPlatformInfo;
extern tclGetDeviceIDs *clGetDeviceIDs;
extern tclGetDeviceInfo *clGetDeviceInfo;
/* далее описаны указатели на остальные функции из OpenCL 1.2 */
extern int CL_API_CALL OpenCLRuntimeDynload();
Таким образом, подключив файл ocl_trampoline.h, пользовательский код использует точно такие же имена функций, как и в стандарте OpenCL без необходимости связываться (линковаться) с OpenCL ICD, так как работает с указателями-трамплинами имеющими такие же имена.
Перед всякой работой связанной с реальными вызовами функций из OpenCL, пользовательский код обязан вызвать функцию OpenCLRuntimeDynload(), чтоб быть уверенным в том, что указатели 'имитирующие' функции, были инициализированы правильными адресами.
OpenCLRuntimeDynload() как раз и содержит в себе пары dlopen/dlsym:
tclGetPlatformIDs *clGetPlatformIDs = nullptr;
tclGetPlatformInfo *clGetPlatformInfo = nullptr;
tclGetDeviceIDs *clGetDeviceIDs = nullptr;
tclGetDeviceInfo *clGetDeviceInfo = nullptr;
/* далее обнуляются и остальные указатели,
есть также опциональная функция, которая их все обнуляет.*/
int CL_API_CALL OpenCLRuntimeDynload()
{
OCLDynloadError dl_err = le_OK;
#define QUOTE(x) #x
#if (OCL_TRAMPOLINE_OS == OCL_TRAMPOLINE_WIN)
#define SET_PROC_ADDR(name) name = reinterpret_cast<t##name *>(GetProcAddress(reinterpret_cast<HMODULE>(ocl_lib_handle_instance().get()), QUOTE(name))); if (!name) { dl_err = le_##name; break; }
#elif (OCL_TRAMPOLINE_OS == OCL_TRAMPOLINE_UNIX)
#define SET_PROC_ADDR(name) name = reinterpret_cast<t##name *>(dlsym(ocl_lib_handle_instance().get(), QUOTE(name))); if (!name) { dl_err = le_##name; break; }
#endif
for(;;) {
if (!ocl_lib_handle_instance()) {
std::unique_lock<std::mutex> lock(ocl_lib_handle_mutex_instance());
#if(OCL_TRAMPOLINE_OS == OCL_TRAMPOLINE_WIN)
ocl_lib_handle_instance().reset(reinterpret_cast<void*>(LoadLibraryW(L"OpenCL.dll")));
#elif (OCL_TRAMPOLINE_OS == OCL_TRAMPOLINE_UNIX)
ocl_lib_handle_instance().reset(dlopen("libOpenCL.so", RTLD_LAZY));
#endif
}
if (!ocl_lib_handle_instance()) {
dl_err = le_ICD_LIB_NOT_FOUND;
break;
}
SET_PROC_ADDR(clGetPlatformIDs);
SET_PROC_ADDR(clGetPlatformInfo);
SET_PROC_ADDR(clGetDeviceIDs);
SET_PROC_ADDR(clGetDeviceInfo);
/* далее устанавливаем адреса всех оставшихся OpenCL 1.2 функций */
break;
}
int32_t res = PXP_OK; /* унифицированный код ошибки */
/* если во время dlsym что-то пошло не так - формируем
унифицированный код ошибки, содержащий коды
подсистемы (facility) и собственно ошибки (error code), в виде
severity | facility | error_code, в данном случае
Error | OpenCLDynloadSubsystem | SomeFnNotFound
*/
if (dl_err != le_OK)
res = DOCLDL_MAKE_ERROR(dl_err);
return res;
#undef QUOTE
#undef SET_PROC_ADDR
}
Хотелось бы соорудить некую сущность - «контекст инициализации», передав ее параметром в функцию OpenCLRuntimeDynload() быть уверенным в том, что инициализированы только те функции, которые в данной подсистеме будут использованы а не вообще весь набор.
Для чего это нужно: например есть библиотека, которая использует OpenCL только для того, чтоб перечислить OpenCL устройства в системе, для ее работы достаточно лишь нескольких функций, а не всего набора.
Другие подсистемы используют чуть больше возможностей чем просто перечисление устройств, а уже создают контексты, компилирую кернелы итд, им соответственно контекст инициализации другой.
Как достичь цели с помощью «структуры с полями» я пока не совсем понимаю, так как структура будет каждый раз иметь разный тип, если ее использовать в качестве параметра для OpenCLRuntimeDynload().
Возможно нагородил тут все слишком сложно и неправильно, то интересно как сделать проще и правильнее по-другому. Вдохновлялся примерами из CUDA для их работы с их Driver API, когда сочинил такое.
Возможно Vulkan со своими validation layers имеет нужную мне архитектуру и решил бы мои хотелки, но пока с ним слабо знаком.
Понимаю, что изобретаю велосипед, который раз и навсегда решит проблему dll-hell но то такое, может кто изобрел получше :)
Исходная версия nikitos, :
Попробую объяснить на примере с кодом, как есть, и чего хотелось бы достичь.
Работа с OpenCL организована таким образом: в ocl_defs.h находятся описания структур и битовых полей из OpenCL, также есть ocl_api.h следующего содержания:
#include "ocl_defs.h"
typedef CL_API_ENTRY cl_int CL_API_CALL tclGetPlatformIDs(cl_uint, cl_platform_id*, cl_uint*);
typedef CL_API_ENTRY cl_int CL_API_CALL tclGetPlatformInfo(cl_platform_id, cl_platform_info, size_t, void*, size_t*);
typedef CL_API_ENTRY cl_int CL_API_CALL tclGetDeviceIDs(cl_platform_id, cl_device_type, cl_uint, cl_device_id*, cl_uint*);
typedef CL_API_ENTRY cl_int CL_API_CALL tclGetDeviceInfo(cl_device_id, cl_device_info, size_t, void *, size_t *);
/* и так далее описаны типы функций из OpenCL 1.2 */
Пользовательские подсистемы, которые хотят использовать OpenCL для своих вычислений, подключают файл ocl_trampoline.h:
#include "ocl_api.h"
extern tclGetPlatformIDs *clGetPlatformIDs;
extern tclGetPlatformInfo *clGetPlatformInfo;
extern tclGetDeviceIDs *clGetDeviceIDs;
extern tclGetDeviceInfo *clGetDeviceInfo;
/* далее описаны указатели на остальные функции из OpenCL 1.2 */
extern int CL_API_CALL OpenCLRuntimeDynload();
Таким образом, подключив файл ocl_trampoline.h, пользовательский код использует точно такие же имена функций, как и в стандарте OpenCL без необходимости связываться (линковаться) с OpenCL ICD, так как работает с указателями-трамплинами имеющими такие же имена.
Перед всякой работой связанной с реальными вызовами функций из OpenCL, пользовательский код обязан вызвать функцию OpenCLRuntimeDynload(), чтоб быть уверенным в том, что указатели 'имитирующие' функции, были инициализированны правильными адресами.
OpenCLRuntimeDynload() как раз и содержит в себе пары dlopen/dlsym:
tclGetPlatformIDs *clGetPlatformIDs = nullptr;
tclGetPlatformInfo *clGetPlatformInfo = nullptr;
tclGetDeviceIDs *clGetDeviceIDs = nullptr;
tclGetDeviceInfo *clGetDeviceInfo = nullptr;
/* далее обнуляются и остальные указатели,
есть также опциональная функция, которая их все обнуляет.*/
int CL_API_CALL OpenCLRuntimeDynload()
{
OCLDynloadError dl_err = le_OK;
#define QUOTE(x) #x
#if (OCL_TRAMPOLINE_OS == OCL_TRAMPOLINE_WIN)
#define SET_PROC_ADDR(name) name = reinterpret_cast<t##name *>(GetProcAddress(reinterpret_cast<HMODULE>(ocl_lib_handle_instance().get()), QUOTE(name))); if (!name) { dl_err = le_##name; break; }
#elif (OCL_TRAMPOLINE_OS == OCL_TRAMPOLINE_UNIX)
#define SET_PROC_ADDR(name) name = reinterpret_cast<t##name *>(dlsym(ocl_lib_handle_instance().get(), QUOTE(name))); if (!name) { dl_err = le_##name; break; }
#endif
for(;;) {
if (!ocl_lib_handle_instance()) {
std::unique_lock<std::mutex> lock(ocl_lib_handle_mutex_instance());
#if(OCL_TRAMPOLINE_OS == OCL_TRAMPOLINE_WIN)
ocl_lib_handle_instance().reset(reinterpret_cast<void*>(LoadLibraryW(L"OpenCL.dll")));
#elif (OCL_TRAMPOLINE_OS == OCL_TRAMPOLINE_UNIX)
ocl_lib_handle_instance().reset(dlopen("libOpenCL.so", RTLD_LAZY));
#endif
}
if (!ocl_lib_handle_instance()) {
dl_err = le_ICD_LIB_NOT_FOUND;
break;
}
SET_PROC_ADDR(clGetPlatformIDs);
SET_PROC_ADDR(clGetPlatformInfo);
SET_PROC_ADDR(clGetDeviceIDs);
SET_PROC_ADDR(clGetDeviceInfo);
/* далее устанавливаем адреса всех оставшихся OpenCL 1.2 функций */
break;
}
int32_t res = PXP_OK; /* унифицированный код ошибки */
/* если во время dlsym что-то пошло не так - формируем
унифицированный код ошибки, содержащий коды
подсистемы (facility) и собственно ошибки (error code), в виде
severity | facility | error_code, в данном случае
Error | OpenCLDynloadSubsystem | SomeFnNotFound
*/
if (dl_err != le_OK)
res = DOCLDL_MAKE_ERROR(dl_err);
return res;
#undef QUOTE
#undef SET_PROC_ADDR
}
Хотелось бы соорудить некую сущность - «контекст инициализации», передав ее параметром в функцию OpenCLRuntimeDynload() быть уверенным в том, что инициализированы только те функции, которые в данной подсистеме будут использованы а не вообще весь набор.
Для чего это нужно: например есть библиотека, которая использует OpenCL только для того, чтоб перечислить OpenCL устройства в системе, для ее работы достаточно лишь нескольких функций, а не всего набора.
Другие подсистемы используют чуть больше возможностей чем просто перечисление устройств, а уже создают контексты, компилирую кернелы итд, им соответсвенно контекст инициализации другой.
Как достичь цели с помощью «структуры с полями» я пока не совсем понимаю, так как структура будет каждый раз иметь разный тип, если ее использовать в качестве параметра для OpenCLRuntimeDynload().
Возможно нагородил тут все слишком сложно и неправильно, то интересно как сделать проще и правильнее по-другому. Вдохновлялся примерами из CUDA для их работы с их Driver API, когда сочинил такое.
Возможно Vulkan со своими validation layers имеет нужную мне архитектуру и решил бы мои хотелки, но пока с ним слабо знаком.
Понимаю, что изобретаю велосипед, который раз и навсегда решит проблему dll-hell но то такое, может кто изобрел получше :)