1. 程式人生 > >呼叫工控機CAN卡時出現:對‘canInitializeLibrary’未定義的引用

呼叫工控機CAN卡時出現:對‘canInitializeLibrary’未定義的引用

1.問題背景:

我在linux中想要使用kavaser can卡將相關報文傳送出來,在官網下載驅動包linuxcan後,按照readme提示,成功安裝驅動,並通過載入虛擬通道,sudo modprobe virtualcan,在canlib\examples\listchannels中顯示增加的can通道,可參考can 卡配置部落格,測試can卡驅動成功安裝!

2.測試過程

當我想要呼叫can的基本測試程式時:

#include <canlib.h>
#include <stdio.h>
int main(void)
{
  canHandle hnd;
  canInitializeLibrary();
  hnd = canOpenChannel(0, canOPEN_EXCLUSIVE);
  if (hnd < 0) {
    char msg[64];
    canGetErrorText((canStatus)hnd, msg, sizeof(msg));
    fprintf(stderr, "canOpenChannel failed (%s)\n", msg);
    exit(1);
  }
  canSetBusParams(hnd, canBITRATE_500K, 0, 0, 0, 0, 0);
  canSetBusOutputControl(hnd, canDRIVER_NORMAL);
  canBusOn(hnd);
  char buffer[1];
  canWrite(hnd, 123, buffer, 6, 0);
  canWriteSync(hnd, 500);
  canBusOff(hnd);
  canClose(hnd);
  return 0;
}

3.問題報錯

/tmp/ccQnezCp.o:在函式‘main’中:
basictest.cpp:(.text+0x18):對‘canInitializeLibrary’未定義的引用
basictest.cpp:(.text+0x27):對‘canOpenChannel’未定義的引用
basictest.cpp:(.text+0x46):對‘canGetErrorText’未定義的引用
basictest.cpp:(.text+0x9a):對‘canSetBusParams’未定義的引用
basictest.cpp:(.text+0xad):對‘canSetBusOutputControl’未定義的引用
basictest.cpp:(.text+0xb7):對‘canBusOn’未定義的引用
basictest.cpp:(.text+0xd5):對‘canWrite’未定義的引用
basictest.cpp:(.text+0xe4):對‘canWriteSync’未定義的引用
basictest.cpp:(.text+0xee):對‘canBusOff’未定義的引用
basictest.cpp:(.text+0xf8):對‘canClose’未定義的引用
collect2: 錯誤:ld 返回 1

4.問題分析

主要是C/C++編譯為obj檔案的時候並不需要函式的具體實現,只要有函式的原型即可。但是在連結為可執行檔案的時候就必須要具體的實現了。如果錯誤是未宣告的引用,那就是找不到函式的原型。

5.解決方式

這幾個報錯的函式是通過載入動態連結庫canlib來實現的,所以需要在編譯的時候在最後新增-lcanlib,動態連線這個庫,如:

g++ basictest.cpp -o 2 -lcanlib

當然如果還是不行 那就需要專門指定動態連結庫的具體路徑,如:

g++ basictest.cpp -o 3 -L /home/hdh/can/linuxcan/canlib/ -lcanlib

6.深入探索問題

6.1 gcc和g++區別

先看結論:一般都用g++,他最牛逼!
兩者的主要區別:
(1)對於.c檔案gcc當做c語言處理,g++當做c++處理;
對於.cpp檔案gcc和g++均當做c++處理;
(2)g++編譯時實際上是呼叫gcc進行編譯;
(3)gcc不能自動連結庫檔案,一般用g++來連結庫檔案,非要用gcc的話,一般使用gcc -lstdc++命令;
(4)extern “c”對於gcc和g++沒有區別;
(5)實際使用時只需安裝gcc和g++中的一個就行了,如果使用gcc,編譯直接用gcc就行了,連結要加上-lstdc++引數;如果使用g++,編譯時實際還是呼叫gcc,連結直接使用g++即可;

6.2 gcc與g++具體的編譯流程

(1)預處理:編譯處理巨集定義等巨集命令(eg:#define)——生成字尾為“.i”的檔案   
(2)編譯:將預處理後的檔案轉換成組合語言——生成字尾為“.s”的檔案  
(3)彙編:由彙編生成的檔案翻譯為二進位制目標檔案——生成字尾為“.o”的檔案   
(4)連線:多個目標檔案(二進位制)結合庫函式等綜合成的能直接獨立執行的執行檔案——生成字尾為“.out”的檔案

6.3 實際測試

#include <stdio.h>

int main()
{
    int a =10;
    printf("g++ test ok!");
}

1.預處理:生成.i的預處理檔案,只啟用預處理,這個不生成檔案,需要把它重定向一個輸出檔案。

[email protected]:~/TCP/bianyitest$ ls
bianyi.cpp
[email protected]:~/TCP/bianyitest$ g++ -E bianyi.cpp -o bianyi.i
[email protected]:~/TCP/bianyitest$ ls
bianyi.cpp  bianyi.i

2.編譯:生成.s的編譯檔案,只啟用預處理和編譯,把檔案編譯成彙編程式碼。

[email protected]:~/TCP/bianyitest$ g++ -S bianyi.i -o bianyi.s
[email protected]:~/TCP/bianyitest$ ls
bianyi.cpp  bianyi.i  bianyi.s

3.彙編:生成.o的彙編檔案,只啟用預處理、編譯和彙編,把程式做成obj檔案。

[email protected]:~/TCP/bianyitest$ g++ -c bianyi.s -o bianyi.o
[email protected]:~/TCP/bianyitest$ ls
bianyi.cpp  bianyi.i  bianyi.o  bianyi.s

4.連結:生成連結檔案,啟用預處理、編譯、彙編和連結。

[email protected]:~/TCP/bianyitest$ g++ bianyi.o -o bianyi
[email protected]:~/TCP/bianyitest$ ls
bianyi  bianyi.cpp  bianyi.i  bianyi.o  bianyi.s

5.常用:

g++ -c bianyi.cpp  //生成.o檔案
g++ bianyi.cpp -o bianyi  //生成可執行檔案

6.4 庫檔案連線

開發軟體時,完全不使用第三方函式庫的情況是比較少見的,通常來講都需要藉助許多函式庫的支援才能夠完成相應的功能。從程式設計師的角度看,函式庫實際上就是一些標頭檔案(.h)和庫檔案(so、或lib、dll)的集合。。雖然Linux下的大多數函式都預設將標頭檔案放到/usr/include/目錄下,而庫檔案則放到/usr/lib/目錄下;Windows所使用的庫檔案主要放在Visual Stido的目錄下的include和lib,以及系統資料夾下。但也有的時候,我們要用的庫不再這些目錄下,所以GCC在編譯時必須用自己的辦法來查詢所需要的標頭檔案和庫檔案。

例如我們的程式test.c是在linux上使用c連線mysql,這個時候我們需要去mysql官網下載MySQL Connectors的C庫,下載下來解壓之後,有一個include資料夾,裡面包含mysql connectors的標頭檔案,還有一個lib資料夾,裡面包含二進位制so檔案libmysqlclient.so

其中inclulde資料夾的路徑是/usr/dev/mysql/include,lib資料夾是/usr/dev/mysql/lib

6.4.1 編譯成可執行檔案

首先我們要進行編譯test.c為目標檔案,這個時候需要執行

gcc –c –I /usr/dev/mysql/include test.c –o test.o

6.4.2 連結

最後我們把所有目標檔案連結成可執行檔案:

gcc –L /usr/dev/mysql/lib –lmysqlclient test.o –o test

Linux下的庫檔案分為兩大類分別是動態連結庫(通常以.so結尾)和靜態連結庫(通常以.a結尾),二者的區別僅在於程式執行時所需的程式碼是在執行時動態載入的,還是在編譯時靜態載入的。

6.4.3 強制連結時使用靜態連結庫

預設情況下, GCC在連結時優先使用動態連結庫,只有當動態連結庫不存在時才考慮使用靜態連結庫,如果需要的話可以在編譯時加上-static選項,強制使用靜態連結庫。

在/usr/dev/mysql/lib目錄下有連結時所需要的庫檔案libmysqlclient.so和libmysqlclient.a,為了讓GCC在連結時只用到靜態連結庫,可以使用下面的命令:

gcc –L /usr/dev/mysql/lib –static –lmysqlclient test.o –o test

靜態庫連結時搜尋路徑順序:

  1. ld會去找GCC命令中的引數-L
  2. 再找gcc的環境變數LIBRARY_PATH
  3. 再找內定目錄 /lib /usr/lib /usr/local/lib 這是當初compile gcc時寫在程式內的

動態連結時、執行時搜尋路徑順序:

  1. 編譯目的碼時指定的動態庫搜尋路徑
  2. 環境變數LD_LIBRARY_PATH指定的動態庫搜尋路徑
  3. 配置檔案/etc/ld.so.conf中指定的動態庫搜尋路徑
  4. 預設的動態庫搜尋路徑/lib
  5. 預設的動態庫搜尋路徑/usr/lib

有關環境變數:
LIBRARY_PATH環境變數:指定程式靜態連結庫檔案搜尋路徑
LD_LIBRARY_PATH環境變數:指定程式動態連結庫檔案搜尋路徑

7.第三方庫使用方法

7.1 最基礎

g++ -c -I /home/hdh/can/linuxcan/canlib bigdata.cpp -obigdata.o
g++ bigdata.o -o 1 -L /home/hdh/can/linuxcan/canlib/ -lcanlib

7.2 最簡單

g++ bigdata.cpp -o 1 -lcanlib

7.3 最根本

8.參考優秀部落格列表:

https://blog.csdn.net/na_beginning/article/details/53236968
https://blog.csdn.net/u013457167/article/details/80222557
https://blog.csdn.net/txl199106/article/details/39398439
https://blog.csdn.net/killwho/article/details/53785910
http://www.linuxdiyf.com/linux/16492.html