ROS CAN匯流排裝置接入(一)Linux動態庫的顯式呼叫
阿新 • • 發佈:2019-02-04
前提:
(1),如果在libpcan安裝正常的話,那麼可以用以下命令查詢到libpcan.so
ls /usr/lib/libpcan*
查詢到方可進行api載入。
(2),確保CMakeList.txt 當前目標檔案已加入add_executable...;target_link_libraries...;
此外加入 target_link_libraries(原始碼名 ${CMAKE_DL_LIBS})
【正文】
1,新增載入需要的標頭檔案及檔案中自己使用的標頭檔案。載入相關的標頭檔案為 dlfcn.h, fcntl.h,由於目標使用libpcan函式,所以libpcan.h也要加入。
dlfcn.h : Linux動態庫的顯式呼叫庫,包括dlopen(),dlclose()等。
fcntl.h :fcntl.h定義了很多巨集和open,fcntl函式原型,包括close open等關閉檔案的系列操作。本文中用到了其開啟檔案的巨集定義,即開啟檔案的控制模式。
int open(const char *pathname, int oflag, ... /* mode_t mode */);返回值:成功則返回檔案描述符,否則返回 -1
對於 open 函式來說,第三個引數(...)僅當建立新檔案時(即使用了O_CREAT 時)才使用,用於指定檔案的訪問許可權位(access permission bits)。pathname 是待開啟/建立檔案的路徑名(如 C:/cpp/a.cpp);oflag 用於指定檔案的開啟/建立模式,這個引數可由以下常量(定義於 fcntl.h)通過邏輯或構成。
O_RDONLY 只讀模式
O_WRONLY 只寫模式
O_RDWR 讀寫模式
開啟/建立檔案時,至少得使用上述三個常量中的一個。以下常量是選用的:
O_APPEND 每次寫操作都寫入檔案的末尾
O_CREAT 如果指定檔案不存在,則建立這個檔案
O_EXCL 如果要建立的檔案已存在,則返回 -1,並且修改 errno 的值
O_TRUNC 如果檔案存在,並且以只寫/讀寫方式開啟,則清空檔案全部內容(即將其長度截短為0)
O_NOCTTY 如果路徑名指向終端裝置,不要把這個裝置用作控制終端。
O_NONBLOCK 如果路徑名指向 FIFO/塊檔案/字元檔案,則把檔案的開啟和後繼 I/O設定為非阻塞模式(nonblocking mode)
以下三個常量同樣是選用的,它們用於同步輸入輸出
O_DSYNC 等待物理 I/O 結束後再 write。在不影響讀取新寫入的資料的前提下,不等待檔案屬性更新。
O_RSYNC read 等待所有寫入同一區域的寫操作完成後再進行
O_SYNC 等待物理 I/O 結束後再 write,包括更新檔案屬性的 I/O open 返回的檔案描述符一定是最小的未被使用的描述符。 2,根據目標使用的函式及其引數形式定義目標指標。 此處暫時使用兩個函式,一個CAN_Init,一個LINUX_CAN_Open函式。 下面為libpcan.h標頭檔案中的定義。
根據上面定義我們宣告兩個函式指標的型別,方便後文使用DWORD CAN_Init(HANDLE hHandle, WORD wBTR0BTR1, int nCANMsgType); HANDLE LINUX_CAN_Open(const char *szDeviceName, int nFlag);
//define mapping function according to target function in libpcan.h
typedef DWORD (*funCAN_Init_TYPE)(HANDLE hHandle, WORD wBTR0BTR1, int nCANMsgType);
typedef HANDLE (*funLINUX_CAN_Open_TYPE)(const char *szDeviceName, int nFlag);
然後用宣告的型別定義對映函式名
//define function pointer,there is a one-to-one mapping between target function and your defined function
funCAN_Init_TYPE fun_CAN_Init;
funLINUX_CAN_Open_TYPE funLINUX_CAN_Open;
3,定義檔案訪問訪問的指標,實現檔案載入。 void *libm_handle = NULL;//define pointer used for file acess of libpcan.so
// dlopen 函式還會自動解析共享庫中的依賴項。這樣,如果您打開了一個依賴於其他共享庫的物件,它就會自動載入它們。
// 函式返回一個控制代碼,該控制代碼用於後續的 API 呼叫
libm_handle = dlopen("libpcan.so", RTLD_LAZY );
// 如果返回 NULL 控制代碼,表示無法找到物件檔案,過程結束。否則的話,將會得到物件的一個控制代碼,可以進一步詢問物件
if (!libm_handle){
// 如果返回 NULL 控制代碼,通過dlerror方法可以取得無法訪問物件的原因
printf("Open Error:%s.\n",dlerror());
return 0;
}
其中用到的dlopen的引數解釋如下:
void *dlopen(const char *filename, int flag);其中flag有:RTLD_LAZY RTLD_NOW RTLD_GLOBAL,其含義分別為:
RTLD_LAZY:在dlopen返回前,對於動態庫中存在的未定義的變數(如外部變數extern,也可以是函式)不執行解析,就是不解析這個變數的地址。
RTLD_NOW:與上面不同,他需要在dlopen返回前,解析出每個未定義變數的地址,如果解析不出來,在dlopen會返回NULL,錯誤為: undefined symbol: xxxx.......
RTLD_GLOBAL:它的含義是它的含義是使得庫中的解析的定義變數在隨後的隨後其它的連結庫中變得可以使用。
4,實現動態庫函式到目標函式的對映。
// 使用 dlsym 函式,嘗試解析新開啟的物件檔案中的符號。您將會得到一個有效的指向該符號的指標,或者是得到一個 NULL 並返回一個錯誤
//one-to-one mapping
char *errorInfo;//error information pointer
fun_CAN_Init =(funCAN_Init_TYPE)dlsym(libm_handle,"CAN_Init");
funLINUX_CAN_Open = (funLINUX_CAN_Open_TYPE)dlsym(libm_handle,"LINUX_CAN_Open");
errorInfo = dlerror();// 呼叫dlerror方法,返回錯誤資訊的同時,記憶體中的錯誤資訊被清空
if (errorInfo != NULL){
printf("Dlsym Error:%s.\n",errorInfo);
return 0;
}
5,定義訪問硬體的HANDLE(指標),實現自定義函式對於can匯流排的訪問,訪問完成要關閉物件。
此前文中已有定義。#define DEFAULT_NODE "/dev/pcan0"
此處實現如下:
HANDLE pcan_handle =NULL;//void *pcan_handle
const char *szDevNode = DEFAULT_NODE;//define const pointer point to device name
pcan_handle = funLINUX_CAN_Open(szDevNode, O_RDWR | O_NONBLOCK);//use mapping function
//judge whether the call is success.if pcan_handle=null,the call would be failed
if(pcan_handle){
can_set_init();
printf("receivetest: %s have been opened\n", szDevNode);
}
else
printf("receivetest: can't open %s\n", szDevNode);
// 呼叫 ELF 物件中的目標函式後,通過呼叫 dlclose 來關閉對它的訪問
dlclose(libm_handle);
這樣便實現了對於libpcan中函式的匯入及呼叫。但尚未深入研究,後期會進行進一步的更新。
該部分完整示例如下:
#include <ros/ros.h>
#include <stdio.h>
#include <dlfcn.h>
#include <libpcan.h>
#include <fcntl.h>
#include <unistd.h>
//define mapping function according to target function in libpcan.h
typedef DWORD (*funCAN_Init_TYPE)(HANDLE hHandle, WORD wBTR0BTR1, int nCANMsgType);
typedef HANDLE (*funLINUX_CAN_Open_TYPE)(const char *szDeviceName, int nFlag);
//the target device name
#define DEFAULT_NODE "/dev/pcan0"
//can init function
bool can_set_init()
{
return true;
}
int main(int argc, char *argv[])
{
void *libm_handle = NULL;//define pointer used for file acess of libpcan.so
//define function pointer,there is a one-to-one mapping between target function and your defined function
funCAN_Init_TYPE fun_CAN_Init;
funLINUX_CAN_Open_TYPE funLINUX_CAN_Open;
// dlopen 函式還會自動解析共享庫中的依賴項。這樣,如果您打開了一個依賴於其他共享庫的物件,它就會自動載入它們。
// 函式返回一個控制代碼,該控制代碼用於後續的 API 呼叫
libm_handle = dlopen("libpcan.so", RTLD_LAZY );
// 如果返回 NULL 控制代碼,表示無法找到物件檔案,過程結束。否則的話,將會得到物件的一個控制代碼,可以進一步詢問物件
if (!libm_handle){
// 如果返回 NULL 控制代碼,通過dlerror方法可以取得無法訪問物件的原因
printf("Open Error:%s.\n",dlerror());
return 0;
}
// 使用 dlsym 函式,嘗試解析新開啟的物件檔案中的符號。您將會得到一個有效的指向該符號的指標,或者是得到一個 NULL 並返回一個錯誤
//one-to-one mapping
char *errorInfo;//error information pointer
fun_CAN_Init =(funCAN_Init_TYPE)dlsym(libm_handle,"CAN_Init");
funLINUX_CAN_Open = (funLINUX_CAN_Open_TYPE)dlsym(libm_handle,"LINUX_CAN_Open");
errorInfo = dlerror();// 呼叫dlerror方法,返回錯誤資訊的同時,記憶體中的錯誤資訊被清空
if (errorInfo != NULL){
printf("Dlsym Error:%s.\n",errorInfo);
return 0;
}
HANDLE pcan_handle =NULL;//void *pcan_handle
const char *szDevNode = DEFAULT_NODE;//define const pointer point to device name
pcan_handle = funLINUX_CAN_Open(szDevNode, O_RDWR | O_NONBLOCK);//use mapping function
//judge whether the call is success.if pcan_handle=null,the call would be failed
if(pcan_handle){
can_set_init();
printf("receivetest: %s have been opened\n", szDevNode);
}
else
printf("receivetest: can't open %s\n", szDevNode);
// 呼叫 ELF 物件中的目標函式後,通過呼叫 dlclose 來關閉對它的訪問
dlclose(libm_handle);
return 0;
}
由於寶寶的qt不能輸入中文,中英混合看著難受,於是乎在附加一個全英文註釋版。
#include <ros/ros.h>
#include <stdio.h>
#include <dlfcn.h>
#include <libpcan.h>
#include <fcntl.h>
//define mapping function according to target function in libpcan.h
typedef DWORD (*funCAN_Init_TYPE)(HANDLE hHandle, WORD wBTR0BTR1, int nCANMsgType);
typedef HANDLE (*funLINUX_CAN_Open_TYPE)(const char *szDeviceName, int nFlag);
//the target device name
#define DEFAULT_NODE "/dev/pcan0"
//can init function
bool can_set_init()
{
return true;
}
int main(int argc, char *argv[])
{
//define function pointer,there is a one-to-one mapping between target function and your defined function
funCAN_Init_TYPE fun_CAN_Init;
funLINUX_CAN_Open_TYPE funLINUX_CAN_Open;
void *libm_handle = NULL;//define pointer used for file acess of libpcan.so
//load libpcan.so using dlopen function,return handle for further use
libm_handle = dlopen("libpcan.so", RTLD_LAZY );
if (!libm_handle){
printf("Open Error:%s.\n",dlerror());//if file can't be loaded,return null,get reason using dlerror function
return 0;
}
char *errorInfo;//error information pointer
//one-to-one mapping using dlsym function,if return null,mapping would be failed
fun_CAN_Init =(funCAN_Init_TYPE)dlsym(libm_handle,"CAN_Init");
funLINUX_CAN_Open = (funLINUX_CAN_Open_TYPE)dlsym(libm_handle,"LINUX_CAN_Open");
errorInfo = dlerror();//get error using dlerror function,and clear the error list in memory
if (errorInfo != NULL){
printf("Dlsym Error:%s.\n",errorInfo);
return 0;
}
HANDLE pcan_handle =NULL;//void *pcan_handle
const char *szDevNode = DEFAULT_NODE;//define const pointer point to device name
pcan_handle = funLINUX_CAN_Open(szDevNode, O_RDWR | O_NONBLOCK);//use mapping function
//judge whether the call is success.if pcan_handle=null,the call would be failed
if(pcan_handle){
can_set_init();
printf("receivetest: %s have been opened\n", szDevNode);
}
else
printf("receivetest: can't open %s\n", szDevNode);
//after call the target function in ELF object,close it using dlclose
dlclose(libm_handle);
return 0;
}