SICK Ranger3源码分析——GenTL API封装
前言
发现SICK代码中有一个有意思的实现,主要用于GenTL API的封装。通过学习,俺也算是窥见到了一点宏的魅力。
TLOpen
本文中将以GenTL API中的TLOpen()
作为主要示例,我们先看一下相关的定义:
1 2 3 4 5 6 7 8 9
|
#define GC_API GC_IMPORT_EXPORT GC_ERROR GC_CALLTYPE GC_API TLOpen ( TL_HANDLE *phTL );
#define GC_API_P(function) typedef GC_ERROR( GC_CALLTYPE *function ) GC_API_P(PTLOpen )( TL_HANDLE *phTL );
|
其中:
TLOpen
是一个GenTL Producer对外提供的函数。如果在编译时可以解析到这个符号(编译时链接了.a或.so库文件),那么使用者直接通过函数名调用即可。
PTLOpen
是一个函数指针类型,在运行时动态加载库(如通过dlopen
)后,获取并存储函数地址(如通过dlsym
)。使用者需要通过这个函数指针来调用相应函数。
GenTL Consumer
作为工业相机的使用者,我们是以GenTL Consumer的身份去调用GenTL Producer提供的API。
在SICK SDK中,实例化GenTL Consumer对象之后,紧接着会调用其open()
函数,目的是①加载生产者库并初始化②打开GenTL系统模块
例如:
1 2 3
| m_pconsumer = std::make_unique<SiConsumer>(ctiFile); m_tlHandle = m_pconsumer->open();
|
1 2 3 4 5 6 7 8 9
| GenTL::TL_HANDLE Consumer::open() { mTl = loadProducer(mCtiPath); CR(mTl->GCInitLib()); CR(mTl->TLOpen(&mTlHandle)); mOpened = true; return mTlHandle; }
|
上述的loadProducer()
和CR(XXX)
都定义在GenTApi类中,这个类是SICK SDK中实现的对GenTL API的封装。
在本文中,我们不关注,如GCInitLib
,TLOpen
,这些函数的功能。我们关注SICK SDK是如何调用它们的。
GenTLApi类
先总体看一下GenTApi类的声明和定义,省略了一些代码方便展示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| #include "TLI/GenTL.h" ... #elif defined(__GNUC__) #define LOAD_LIBRARY(path) dlopen(path, RTLD_LAZY) #define FREE_LIBRARY(path) (dlclose(path) == 0) #endif
#define API_LIST(code)\ code(GCGetInfo)\ code(GCGetLastError)\ code(GCInitLib)\ ... code(TLOpen)\ ...
class GenTLApi { public: GenTLApi(HMODULE module); ~GenTLApi();
#define FUNC_PTR(func) GenTL::P##func func; API_LIST(FUNC_PTR) #undef FUNC_PTR
HMODULE mModule; };
...
std::unique_ptr<GenTLApi> loadProducer(std::string ctiFile);
#define CC(tl, CALL) \ if (GenTL::GC_ERR_SUCCESS != CALL) \ { \ char message[1024]; \ memset(message, 0, sizeof(message)); \ size_t size = sizeof(message); \ GenTL::GC_ERROR errorCode; \ tl->GCGetLastError(&errorCode, message, &size); \ std::stringstream ss; \ ss << "GenTL call failed: " << errorCode << ", Message: " << message; \ throw std::runtime_error(ss.str().c_str()); \ }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| #include "GenTLApi.h" ...
GenTLApi::GenTLApi(HMODULE module) : mModule(module) { }
GenTLApi::~GenTLApi() { if (mModule == nullptr) { std::cout << "try to free GenTLApi\n"; FREE_LIBRARY(mModule); mModule = nullptr; } }
std::unique_ptr<GenTLApi> loadProducer(std::string ctiFile) { HMODULE module = LOAD_LIBRARY(ctiFile.c_str()); if (module == nullptr) { std::stringstream sstr; sstr << "Could not load: " << ctiFile; std::string errorMessage(sstr.str()); std::cerr << errorMessage << std::endl; throw std::runtime_error(errorMessage.c_str()); }
std::unique_ptr<GenTLApi> tl(new GenTLApi(module));
#define LOAD_PROC_ADDRESS(func) \ tl->func = (GenTL::P##func)dlsym(module, #func); \ assert(tl->func);
API_LIST(LOAD_PROC_ADDRESS) #undef LOAD_PROC_ADDRESS return tl; }
|
API_LIST
1 2 3 4 5
| #define API_LIST(code)\ code(GCGetInfo)\ code(GCGetLastError)\ code(GCInitLib)\ ...
|
API_LIST(code)
是一个多行宏定义。它将所需的GenTL函数名称以code(XXX)的形式列出来,方便在不同情况下用不同的方式一次性地对这些函数或标识符进行批量操作。
函数指针声明
现在关注GenTLApi.h
中的:
1 2 3
| #define FUNC_PTR(func) GenTL::P##func func; API_LIST(FUNC_PTR) #undef FUNC_PTR
|
这是一个批量的声明操作,声明了GenTL中那些API对应的函数指针(因为GenTL Producer库是运行时动态加载的,详见)
例如,对于GenTL API中的TLOpen()
,将在此被展开为GenTL::PTLOpen TLOpen
loadProducer()
SICK SDK中采用的策略是运行时动态加载Producer库,因此需要手动加载:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| std::unique_ptr<GenTLApi> loadProducer(std::string ctiFile) { HMODULE module = LOAD_LIBRARY(ctiFile.c_str()); if (module == nullptr) { std::stringstream sstr; sstr << "Could not load: " << ctiFile; std::string errorMessage(sstr.str()); std::cerr << errorMessage << std::endl; throw std::runtime_error(errorMessage.c_str()); }
std::unique_ptr<GenTLApi> tl(new GenTLApi(module));
#define LOAD_PROC_ADDRESS(func) \ tl->func = (GenTL::P##func)dlsym(module, #func); \ assert(tl->func);
API_LIST(LOAD_PROC_ADDRESS) #undef LOAD_PROC_ADDRESS return tl; }
|
有意思的是这一段:
1 2 3 4 5 6
| #define LOAD_PROC_ADDRESS(func) \ tl->func = (GenTL::P##func)dlsym(module, #func); \ assert(tl->func);
API_LIST(LOAD_PROC_ADDRESS) #undef LOAD_PROC_ADDRESS
|
这是一个批量获取函数地址,并将地址赋值给对应指针的操作。
例如,对于GenTL API中的TLOpen()
,将在此被展开为tl->TLOpen = (GenTL::PTLOpen)dlsym(module, "TLOpen");
然后assert(tl->TLOpen);
会确保成功找到符号,如果没有找到会断言失败。
CC
1 2 3 4 5 6 7 8 9 10 11 12 13
| #define CC(tl, CALL) \ if (GenTL::GC_ERR_SUCCESS != CALL) \ { \ char message[1024]; \ memset(message, 0, sizeof(message)); \ size_t size = sizeof(message); \ GenTL::GC_ERROR errorCode; \ tl->GCGetLastError(&errorCode, message, &size); \ std::stringstream ss; \ ss << "GenTL call failed: " << errorCode << ", Message: " << message; \ throw std::runtime_error(ss.str().c_str()); \ }
|
这个宏封装了错误检查的功能,在调用GenTL库的函数后,如果返回值不为GenTL::GC_ERR_SUCCESS
,则通过调用GCGetLastError
获取错误信息并打印。
使用者在调用GenTL API时可以直接使用此宏进行调用:
1
| CC(mTl, mTl->TLOpen(&mTlHandle));
|
CR
前面还提到了CR这个宏:
1
| #define CR(CALL) CC(tl(), CALL);
|
基本上就是给CC(tl, CALL)
又包了一层简化宏,用来省略在调用时显式传入tl
这个参数
可以这么使用:
1
| CR(mTl->TLOpen(&mTlHandle));
|
没了