• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            tqsheng

            go.....
            隨筆 - 366, 文章 - 18, 評論 - 101, 引用 - 0
            數據加載中……

            QT精彩實例分析

            qt的畫圖機制為顯示器和打印提供了統一的api接口,主要有3個大類QPainter(提供畫圖操作的基本接口和操作) QPaintDevice(提供畫圖的設備也就是你的圖畫在那個地方)和QPaintEngine(為QPainter和QPaintDevice提供內部使用的抽象接口定義,一般不會用到的).

            QPainter-->QPaintEngine---->QPaintDevice

            其中QPaintDevice有QWidget 、QImage、QPixmap、QPicture、QGLWidget、QPrinter、QGLPixleBuffer

            http://qt-apps.org/

            http://www.qtcentre.org/content/


            今天寫個簡單的時鐘的程序,秒鐘、分鐘,時鐘能夠自動的正確的顯示現在的時間,但是不能夠修改。這個程序非常簡單需要的基本知識是:

            1.Qpainter類

            2.QTimer

            步驟:

            1.先新建一個widget

            2.重新定義void paintEvent(QPainter *painter)

            3.定義一個畫圖的函數 void draw(QPainter *painter)

            main.cpp

            #include <QtGui/QApplication>

            #include "timer.h"
            #include<QTextCodec>
            int main(int argc, char *argv[])
            {
                QApplication a(argc, argv);
                QTextCodec::setCodecForTr(QTextCodec::codecForLocale());
                Timer w;
                w.show();
                return a.exec();
            }

            timer.h

            #ifndef TIMER_H

            #define TIMER_H
            #include <QWidget>
            namespace Ui {
                class Timer;
            }
            class Timer : public QWidget
            {
                Q_OBJECT
            public:
                explicit Timer(QWidget *parent = 0);
                ~Timer();
                void paintEvent(QPaintEvent *);
                void draw(QPainter *painter);
            private:
                Ui::Timer *ui;
            };
            #endif // TIMER_H

            timer.cpp

            #include "timer.h"

            #include "ui_timer.h"
            #include<QtGui>
            Timer::Timer(QWidget *parent) :
                QWidget(parent),
                ui(new Ui::Timer)
            {
                ui->setupUi(this);
                QTimer *timer = new QTimer;//新建一個定時器來定時刷新圖
                connect(timer,SIGNAL(timeout()),this,SLOT(update()));
                timer->start(1000);//每隔1秒鐘重新繪制一下
                setWindowTitle(tr("深海的小魚兒"));//定義widget的標題
                resize(200,200);//初始固定大小為200x200
            }
            Timer::~Timer()
            {
                delete ui;
            }
            void Timer::paintEvent(QPaintEvent *)
            {
                QPainter painter(this); //定義一個畫筆
                painter.setRenderHint(QPainter::Antialiasing);//開啟反轉使你畫的圖比較光滑
                int side = qMin(width(), height());//取長寬的最小值來作為你放大和縮小的標準值(也就是說你的
                painter.translate(width() / 2, height() / 2);//長寬是一樣的,這樣縮放時不會變形。移到中心點,這時width/height=200
                painter.scale(side / 200.0, side / 200.0);
                draw(&painter);//開始畫圖
            }
            void Timer::draw(QPainter *painter)
            {
                static const QPoint hourHand[3] = {
                    QPoint(7, 8),
                    QPoint(-7, 8),
                    QPoint(0, -40)
                };//定義時針的圖像
                static const QPoint minuteHand[3] = {
                    QPoint(7, 8),
                    QPoint(-7, 8),
                    QPoint(0, -65)
                };//定義分針的圖像
                static const QPoint secondHand[3] = {
                    QPoint(7, 8),
                    QPoint(-7, 8),
                    QPoint(0, -85)
                };//定義秒針的圖像
                QColor hourColor(127, 0, 127);//定義時針的顏色
                QColor minuteColor(0, 127, 127, 191);//定義分針的顏色
                QColor secondColor(0, 0,255);//定義秒針的顏色
                QTime time = QTime::currentTime();//取系統的時間
                painter->setPen(Qt::NoPen);//開始畫秒針的在系統時間的位置
                painter->setBrush(secondColor);
                painter->save();
                painter->rotate(time.second()*6);//360/60=6
                painter->drawConvexPolygon(secondHand, 3);
                painter->restore();
                painter->setPen(Qt::NoPen);//開始畫分針的在系統時間的位置
                painter->setBrush(hourColor);
                painter->save();
                painter->rotate(30.0 * ((time.hour() + time.minute() / 60.0)));
                painter->drawConvexPolygon(hourHand, 3);
                painter->restore();
                painter->setPen(hourColor);
                for (int i = 0; i < 12; ++i)
                {
                    painter->drawLine(88, 0, 96, 0);
                    painter->rotate(30.0);
                }
                painter->setPen(Qt::NoPen);//開始畫時針的在系統時間的位置
                painter->setBrush(minuteColor);
                painter->save();
                painter->rotate(6.0 * (time.minute() + time.second() / 60.0));
                painter->drawConvexPolygon(minuteHand, 3);
                painter->restore();
                painter->setPen(minuteColor);
                for (int j = 0; j < 60; ++j)
                {
                    if ((j % 5) != 0)
                        painter->drawLine(92, 0, 96, 0);
                    painter->rotate(6.0);
                }

            posted @ 2012-12-07 22:58 tqsheng 閱讀(810) | 評論 (0)編輯 收藏

            elf文件格式學習總結

                編譯器將一個源文件生成目標文件時,會在目標文件中生成符號表和重定位表。

                

                符號表包含在文件中定義的全局符號以及在文件中引用的外部符號(外部函數或變量)。

                重定位表告訴鏈接器在哪些位置要進行重定位操作。

                

                編譯器生成的目標文件在文件的開始處會有一個elf頭,描繪了整個文件的組織結構。它還包括很多節(section)。這些節有的是系統定義好的,有些是用戶在文件在通過.section命令自定義的,鏈接器會將多個輸入目標文件中的相同的節合并。

                

                鏈接器對編譯生成的目標文件進行鏈接時,A. 首先進行符號解析,找出外部符號在哪定義。如果外部符號在一個靜態庫中定義,則直接將對應的定義代碼復制到最終生成的目標文件中。B. 接著鏈接器進行符號重定位。編譯器在生成目標文件時,通常使用從零開始的相對地址,而在鏈接過程中,鏈接器從一個指定的地址開始,根據輸入目標文件的順序,以段(segment)為單位將它們拼裝起來。其中每個段可以包括很多個節(section)。除了目標文件的拼裝,重定位過程中還完成了下面兩個任務:一是生成最終的符號表,二是對代碼段(.text)中的某些位置進行修改,要修改的位置由編譯器生成的重定位表指出。

                

                鏈接過程中還會生成兩個表:got表和plt表。

                

                got表中每一項都是本運行模塊要引用的全局變量或函數的地址,可以用got表來間接引用全局變量。函數也可以把got表的首地址作為一個基準,用相對該基準的偏移量來(直接)引用靜態函數。由于動態鏈接器(ld-linux.so)不會把運行模塊(*.so)加載到固定地址,在不同進程的地址空間中各運行模塊的絕對地址、相對地址都不同。這種不同反映到got表上,就是每個進程的每個運行模塊都有獨立的got表,所以進程間不能共享got表(內容)。

                

                plt表中每一項都是一小段匯編代碼,對應于本運行模塊要引用的每一個全局函數。當鏈接器發現某個符號引用是位于其它共享目標文件(動態連接庫*.so)中的一個全局函數時,就在文件的plt表里創建一個項目,以便將來重定位。

                

                鏈接生成的目標文件在文件開頭也有一個elf頭號,描繪了整個文件的組織結構,這個文件中會有多個段(segment),每個段都由相應的節(section)拼裝而成。

                對由鏈接器鏈接生成的可執行目標文件進行加載運行時,內核首先讀取elf頭。根據頭部數據指示分別讀入各種數據結構,找出可加載的段并調用mmap()函數將其加載到內存。內核找到標記為PT_INTERP的段,這個段對應著動態鏈接器的名稱,然后加載動態鏈接器(linux中通常是/lib/ld-linux.so.2)。接著內核將控制權交給動態鏈接器。動態鏈接器檢查程序對外部文件(共享庫)的依賴性,并在需要時對其進行加載。之后動態鏈接器開始對程序中的外部引用(即全局變量/函數)進行重定位,即定位出程序其引用的外部變量/函數的真實內存地址。R_386_GLOB_DAT類型的重定位項目涉及到got表。R_386_JMP_SLOT類型的重定位項目涉及到plt表。動態鏈接還有一個延遲(lazy)特性,即真正引用時才進行重定位(環境變量LD_BIND_NOW為空值NULL時)。接下來動態鏈接器執行elf文件中標記為.init節的代碼,進行程序運行的初始化。最后動態鏈接器把控制權交給程序,從elf頭中定義的入口處開始執行程序。

            posted @ 2012-12-07 22:43 tqsheng 閱讀(316) | 評論 (0)編輯 收藏

            Qt學習筆記

            老規矩,先下載一個qt-x11-opensource版本,configure,make,make install一切都照說明來。
            默認安裝目錄:/usr/local/Trolltech/Qt-4.5.0/

            編寫環境變量設置腳本,以免鏈到系統的舊Qt庫。
            qt_pc.env 腳本內容如下:

            export QMAKESPEC=linux-g++
            export QTDIR=/usr/local/Trolltech/Qt-4.5.0
            export PKG_CONFIG_PATH=/usr/local/lib:${QTDIR}/lib/pkgconfig
            export LD_LIBRARY_PATH=/usr/local/lib:${QTDIR}/lib
            export PATH=${QTDIR}/bin:$PATH

            第一行指定qmake使用的spec,意思是使用/usr/local/Trolltech/Qt-4.5.0/mkspecs/linux-g++這個目錄中的文件指定一些變量。如果是嵌入式平臺可以看到里面連用什么編譯器都指定了,比如/usr/local/Trolltech/Qt-4.5.0/mkspecs/qws/linux-arm-g++目錄。
            第二行聲明的QT的安裝目錄。
            第三行是為了pkg-config能夠找到qt庫
            第四行是為了運行時能找到qt庫
            第五行保證運行qmake,rcc等工具時是運行的最新的,而不是系統上的舊版本。

            1.建個目錄存放工程:
                  mkdir qt_study
                  cd qt_study
                  mkdir src
            2.創建工程pro文件:
                  vi qt_study.pro
                  文件內容如下:
                  #模板app,生成獨立運行的程序
                  TEMPLATE = app
                  #生成的可執行文件存放目錄
                  DESTDIR = bin
                  #生成的可執行文件名稱
                  TARGET = qt_study
                  #源文件、臨時文件存放目錄
                  DEPENDPATH += src
                  INCLUDEPATH +=
                  MOC_DIR = .tmp/moc
                  RCC_DIR = .tmp/rcc
                  UI_DIR = .tmp/ui
                  OBJECTS_DIR = .tmp/obj
                  #打開警告、支持大文件
                  CONFIG += qt warn_on largefile
                  #指定代碼中國際化需要翻譯的字符串存儲文件。可指定多個
                  TRANSLATIONS = qt_study_zh_CN.ts
                  #資源文件,一些圖片,翻譯文本等。編譯后會與可執行文件集成在一個文件中
                  RESOURCES += qt_study.qrc
            # 源代碼
            HEADERS += src/mainwindow.h

            SOURCES += src/main.cpp \
                       src/mainwindow.cpp

            3.創建資源文件
            vi qt_study.qrc
            文件內容如下:
            <!DOCTYPE RCC><RCC version="1.0">
            <qresource>
            <file>./qt_study_zh_CN.qm</file>
            </qresource>
            </RCC>
            這兒只包含了一個翻譯文本文件,qm文件是由ts文件生成的,后面會有說明。
            4.編寫代碼:
            mkdir src
            cd src
            編寫main.cpp,mainwindow.cpp,mainwindow.h。
            main.cpp:
            #include "mainwindow.h"

            void installTranslator()
            {
                    //獲取系統的local name。
                    //在我的系統上執行locale,得到的是"zh_CN.UTF-8"。這兒name()會得到zh_CN
                    QString locale = QLocale::system().name();

                    //新創建一個QTranslator對象,不能是棧對象。
                    QTranslator* translator = new QTranslator;
                    //從內置資源中load語言包。:/表示是內置資源;qt_study_zh_CN.qm會被load
                    translator->load( QString(":/qt_study_" + locale) );
                    //安裝語言包。程序啟動后的界面就是zh_CN的了。
                    qApp->installTranslator(translator);
            }

            int main( int argc, char** argv )
            {
                    //初始化資源。就是qt_study.qrc文件去掉擴展名
                    Q_INIT_RESOURCE( qt_study );

                    QApplication app( argc, argv );
                    //安裝translator
                    installTranslator();
                    //創建主界面
                    qtStudyMainWindow w;
                    w.show();

                    return app.exec();
            }
            mainwindow.h 及 mainwindow.cpp見附件
            5.翻譯文本:
            生成ts文件:
            lupdate -pro qt_study.pro
            翻譯文本
            linguist qt_study_zh_CN.ts
            生成qm文件
            lrelease qt_study_zh_CN.ts
            6.編譯:
            qmake
            make
            7.執行:
            如果系統內沒有中文字體,需要把ttf字體復制到/usr/local/Trolltech/Qt-4.5.0/lib/fonts/
            ./bin/qt_study
            可以看到正常翻譯結果出現。


            posted @ 2012-12-07 22:41 tqsheng 閱讀(318) | 評論 (0)編輯 收藏

            Windows下靜態編譯QT程序

            Windows下編寫的QT程序拿到別的機器上運行時總是要打包DLL,編譯成靜態程序更方便一些。 

            首先要編譯一個靜態的Qt庫,因為ms的連接器需要.lib庫,而qt自帶的是.a庫,所以要下載代碼自己編譯了。 
            編譯前先修改一下spec:(下面是對于vs2008,如果是2005,則去找win32-msvc2005) 
            mkspecs ----> win32-msvc2008 ----> qmake.conf 
            修改QMAKE_CFLAGS_RELEASE = -O2 -MD 為QMAKE_CFLAGS_RELEASE = -O2 -MT 
            修改QMAKE_CFLAGS_DEBUG 為  = -Zi -MTd 
            修改這兒是因為Qt configure時的static選項對核心庫無效,核心庫仍然是動態鏈接的。另外-Zi也可以去掉,不生成pdb文件,如果不調試Qt內部就沒必要要。 
            執行: 
            configure -release -static -fast -qt-sql-odbc -qt-sql-sqlite -no-webkit 
            nmake 
            等待個1,2小時的編譯,生成一堆的.lib文件。 
            如果需要webkit就不要加-no-webkit參數 

            在vs中修改工程的屬性: 
            配置屬性 ----> C/C++ ----> 代碼生成 ---->運行時庫 ----> /MT or /MTd 

            這樣最終生成的程序就是靜態程序了,拿到沒有QT庫的機器上也可以運行了。

            posted @ 2012-12-07 22:37 tqsheng 閱讀(496) | 評論 (0)編輯 收藏

            dll和so文件區別與構成

            動態鏈接,在可執行文件裝載時或運行時,由操作系統的裝載程序加載庫。大多數操作系統將解析外部引用(比如庫)作為加載過程的一部分。在這些系統上,可執行文件包含一個叫做import   directory的表,該表的每一項包含一個庫的名字。根據表中記錄的名字,裝載程序在硬盤上搜索需要的庫,然后將其加載到內存中預先不確定的位置,之后根據加載庫后確定的庫的地址更新可執行程序。可執行程序根據更新后的庫信息調用庫中的函數或引用庫中的數據。這種類型的動態加載成為裝載時加載   ,被包括Windows和Linux的大多數系統采用。裝載程序在加載應用軟件時要完成的最復雜的工作之一就是加載時鏈接。  
               
              其他操作系統可能在運行時解析引用。在這些系統上,可執行程序調用操作系統API,將庫的名字,函數在庫中的編號和函數參數一同傳遞。操作系統負責立即解析然后代表應用調用合適的函數。這種動態鏈接叫做運行時鏈接   。因為每個調用都會有系統開銷,運行時鏈接要慢得多,對應用的性能有負面影響。現代操作系統已經很少使用運行時鏈接。  
               
              可以動態鏈接的庫,在Windows上是dynamic   link   library   (DLL),在UNIX或Linux上是Shared   Library。庫文件是預先編譯鏈接好的可執行文件,存儲在計算機的硬盤上。大多數情況下,同一時間多個應用可以使用一個庫的同一份拷貝,操作系統不需要加載這個庫的多個實例。  
               
              Windows   和   Linux   的加載時鏈接是由操作系統來完成的,格式在不同的系統下有不同的區別,但是原理還是一樣的。
            linux下文件的類型是不依賴于其后綴名的,但一般來講:
            .o,是目標文件,相當于windows中的.obj文件
            .so 為共享庫,是shared object,用于動態連接的,和dll差不多
            .a為靜態庫,是好多個.o合在一起,用于靜態連接
            .la為libtool自動生成的一些共享庫,vi編輯查看,主要記錄了一些配置信息。可以用如下命令查看*.la文件的格式   $file *.la
                  *.la: ASCII English text
            所以可以用vi來查看其內容。
            @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
            創建.a庫文件和.o庫文件:
            [yufei@localhost perl_c2]$ pwd
            /home/yufei/perl_c2
            [yufei@localhost perl_c2]$ cat mylib.c
            #include <stdio.h>
            #include <string.h>
            void hello(){
                    printf("success call from perl to c library\n");
            }
            [yufei@localhost perl_c2]$ cat mylib.h
            extern void hello();
            [yufei@localhost perl_c2]$ gcc -c mylib.c
            [yufei@localhost perl_c2]$ dir
            mylib.c  mylib.h  mylib.o
            [yufei@localhost perl_c2]$ ar -r mylib.a mylib.o
            ar: 正在創建 mylib.a
            [yufei@localhost perl_c2]$ dir
            mylib.a  mylib.c  mylib.h  mylib.o
            @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
            111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
            動態鏈接庫*.so的編譯與使用- -
                                                  
            動態庫*.so在linux下用c和c++編程時經常會碰到,最近在網站找了幾篇文章介紹動態庫的編譯和鏈接,總算搞懂了這個之前一直不太了解得東東,這里做個筆記,也為其它正為動態庫鏈接庫而苦惱的兄弟們提供一點幫助。
            1、動態庫的編譯
            下面通過一個例子來介紹如何生成一個動態庫。這里有一個頭文件:so_test.h,三個.c文件:test_a.c、test_b.c、test_c.c,我們將這幾個文件編譯成一個動態庫:libtest.so。
            so_test.h:
            #include <stdio.h>
            #include <stdlib.h>
            void test_a();
            void test_b();
            void test_c();
            test_a.c:
            #include "so_test.h"
            void test_a()
            {
                printf("this is in test_a...\n");
            }
            test_b.c:
            #include "so_test.h"
            void test_b()
            {
                printf("this is in test_b...\n");
            }
            test_c.c:
            #include "so_test.h"
            void test_c()
            {
                printf("this is in test_c...\n");
            }
            將這幾個文件編譯成一個動態庫:libtest.so
            $ gcc test_a.c test_b.c test_c.c -fPIC -shared -o libtest.so
            2、動態庫的鏈接
            在1、中,我們已經成功生成了一個自己的動態鏈接庫libtest.so,下面我們通過一個程序來調用這個庫里的函數。程序的源文件為:test.c。
            test.c:
            #include "so_test.h"
            int main()
            {
                test_a();
                test_b();
                test_c();
                return 0;
            }
            l         將test.c與動態庫libtest.so鏈接生成執行文件test:
            $ gcc test.c -L. -ltest -o test
            l         測試是否動態連接,如果列出libtest.so,那么應該是連接正常了
            $ ldd test
            l         執行test,可以看到它是如何調用動態庫中的函數的。
            3、編譯參數解析
            最主要的是GCC命令行的一個選項:
                      -shared 該選項指定生成動態連接庫(讓連接器生成T類型的導出符號表,有時候也生成弱連接W類型的導出符號),不用該標志外部程序無法連接。相當于一個可執行文件
            l         -fPIC:表示編譯為位置獨立的代碼,不用此選項的話編譯后的代碼是位置相關的所以動態載入時是通過代碼拷貝的方式來滿足不同進程的需要,而不能達到真正代碼段共享的目的。
            l         -L.:表示要連接的庫在當前目錄中
            l         -ltest:編譯器查找動態連接庫時有隱含的命名規則,即在給出的名字前面加上lib,后面加上.so來確定庫的名稱
            l         LD_LIBRARY_PATH:這個環境變量指示動態連接器可以裝載動態庫的路徑。
            l         當然如果有root權限的話,可以修改/etc/ld.so.conf文件,然后調用 /sbin/ldconfig來達到同樣的目的,不過如果沒有root權限,那么只能采用輸出LD_LIBRARY_PATH的方法了。
            4、注意
                   調用動態庫的時候有幾個問題會經常碰到,有時,明明已經將庫的頭文件所在目錄 通過 “-I” include進來了,庫所在文件通過 “-L”參數引導,并指定了“-l”的庫名,但通過ldd命令察看時,就是死活找不到你指定鏈接的so文件,這時你要作的就是通過修改 LD_LIBRARY_PATH或者/etc/ld.so.conf文件來指定動態庫的目錄。通常這樣做就可以解決庫無法鏈接的問題了。
            makefile里面怎么正確的編譯和連接生成.so庫文件,然后又是在其他程序的makefile里面如何編譯和連接才能調用這個庫文件的函數????
            答:
                   你需要告訴動態鏈接器、加載器ld.so在哪里才能找到這個共享庫,可以設置環境變量把庫的路徑添加到庫目錄/lib和/usr/lib,LD_LIBRARY_PATH=$(pwd),這種方法采用命令行方法不太方便,一種替代方法
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^注釋^^^^^^^^^^^^^^^^^^^^^^^^^^^^
            LD_LIBRARY_PATH可以在/etc/profile還是 ~/.profile還是 ./bash_profile里設置,或者.bashrc里,
            改完后運行source /etc/profile或 . /etc/profile
            更好的辦法是添入/etc/ld.so.conf, 然后執行 /sbin/ldconfig
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^注釋^^^^^^^^^^^^^^^^^^^^^^^^^^^^
            是把庫路徑添加到/etc/ld.so.conf,然后以root身份運行ldconfig
                  也可以在連接的時候指定文件路徑和名稱 -I  -L.
                  GCC=gcc
            CFLAGS=-Wall -ggdb -fPIC
            #CFLAGS=
            all: libfunc test
            libfunc:func.o func1.o
                    $(GCC) -shared -Wl,-soname,libfunc.so.1 -o libfunc.so.1.1 $<
                    ln -sf libfunc.so.1.1 libfunc.so.1
                    ln -sf libfunc.so.1 libfunc.so
            ***********************************************注釋************************************************
            ln -s是用來創建軟鏈接,也就相當于windows中的快捷方式,在當前目錄中創建上一級目錄中的文件ttt的命名為ttt2軟鏈接的命令是ln -s ../ttt ttt2,如果原文件也就是ttt文件刪除的話,ttt2也變成了空文件。
            ln -d是用來創建硬鏈接,也就相當于windows中文件的副本,當原文件刪除的時候,并不影響“副本”的內容。
            編譯目標文件時使用gcc的-fPIC選項,產生與位置無關的代碼并能被加載到任何地址:
            gcc –fPIC –g –c liberr.c –o liberr.o
            使用gcc的-shared和-soname選項;
            使用gcc的-Wl選項把參數傳遞給連接器ld;
            使用gcc的-l選項顯示的連接C庫,以保證可以得到所需的啟動(startup)代碼,從而避免程序在使用不同的,可能不兼容版本的C庫的系統上不能啟動執行。
            gcc –g –shared –Wl,-soname,liberr.so –o liberr.so.1.0.0 liberr.o –lc
            建立相應的符號連接:
            ln –s liberr.so.1.0.0 liberr.so.1;
            ln –s liberr.so.1.0.0 liberr.so;
            在MAKEFILE中:
            $@
                表示規則中的目標文件集。在模式規則中,如果有多個目標,那么,"$@"就是匹配于目標中模式定義的集合。
            $%
                僅當目標是函數庫文件中,表示規則中的目標成員名。例如,如果一個目標是"foo.a(bar.o)",那么,"$%"就是"bar.o","$@"就是 "foo.a"。如果目標不是函數庫文件(Unix下是[.a],Windows下是[.lib]),那么,其值為空。
            $<
                依賴目標中的第一個目標名字。如果依賴目標是以模式(即"%")定義的,那么"$<"將是符合模式的一系列的文件集。注意,其是一個一個取出來的。
            $?
                所有比目標新的依賴目標的集合。以空格分隔。
            $^
                所有的依賴目標的集合。以空格分隔。如果在依賴目標中有多個重復的,那個這個變量會去除重復的依賴目標,只保留一份。
            *********************************************注釋***********************************************************************
            test: test.o libfunc
                    $(GCC) -o test test.o -L. -lfunc
            %.o:%.c
                    $(GCC) -c $(CFLAGS) -o $@ $<
            clean:
                    rm -fr *.o
                    rm -fr *.so*
                    rm -fr test
            要生成.so文件,cc要帶-shared 參數;要調用.so的文件,比如libfunc.so,可以在cc命令最后加上-lfunc,還要視情況加上 -L/usr/xxx 指出libfunc.so的路徑;這樣,在你要編譯的源文件中就可以調用libfunc.so這個庫文件的函數.
                   前面的都說的差不多了,最后提醒一下最好提供一個接口頭文件
                   動態加載,用dlopen,dlclose,dlsym
             
            ref:http://niefei.blog.ccidnet.com/blog/ccid/do_showone/tid_42855.html
            1. 介紹
              使用GNU的工具我們如何在Linux下創建自己的程序函數庫?一個“程序 函數庫”簡單的說就是一個文件包含了一些編譯好的代碼和數據,這些編 譯好的代碼和數據可以在事后供其他的程序使用。程序函數庫可以使整個程序更加模塊化,更容易重新編譯,而且更方便升級。程序函數庫可分為3種類型:靜態函 數庫(static libraries)、共享函數庫(shared libraries)和動態加載函數庫(dynamically loaded libraries)。
              靜態函數庫是在程序執行前就加入到目標程序中去了;而共享函數庫則是在程序啟動的時候加載到程序中,它可以被 不同的程序共享;動態加載函數庫則可以在程序運行的任何時候動態的加載。實際上,動態函數庫并非另外一種庫函數格式,區別是動態加載函數庫是如何被程序員 使用的。后面我們將舉例說明。
              本文檔主要參考Program Library HOWTO,作者是luster(hwang@ustc.edu),任何非商業目的的再次發行本文檔都是允許的,但是請保留作者信息和本版權聲明。本文檔首先在www.linuxaid.com.cn發布。
              2. 靜態函數庫
               靜態函數庫實際上就是簡單的一個普通的目標文件的集合,一般來說習慣用“.a”作為文件的后綴。可以用ar這個程序來產生靜態函數庫文件。Ar 是archiver的縮寫。靜態函數庫現在已經不在像以前用得那么多了,主要是共享函數庫與之相比較有很多的優勢的原因。慢慢地,大家都喜歡使用共享函數 庫了。不過,在一些場所靜態函數庫仍然在使用,一來是保持一些與以前某些程序的兼容,二來它描述起來也比較簡單。
              靜態庫函數允許程序 員把程序link起來而不用重新編譯代碼,節省了重新編譯代碼的時間。不過,在今天這么快速的計算機面前,一般的程序的重新編譯也花費不了多少時間,所以 這個優勢已經不是像它以前那么明顯了。靜態函數庫對開發者來說還是很有用的,例如你想把自己提供的函數給別人使用,但是又想對函數的源代碼進行保密,你就 可以給別人提供一個靜態函數庫文件。理論上說,使用ELF格式的靜態庫函數生成的代碼可以比使用共享函數庫(或者動態函數 庫)的程序運行速度上快一些,大概1-5%。
              創建一個靜態函數庫文件,或者往一個已經存在地靜態函數庫文件添加新的目標代碼,可以用下面的命令:
            ar rcs my_library.a file1.o file2.o
               這個例子中是把目標代碼file1.o和file2.o加入到my_library.a這個函數庫文件中,如果my_library.a不存在 則創建一個新的文件。在用ar命令創建靜態庫函數的時候,還有其他一些可以選擇的參數,可以參加ar的使用幫助。這里不再贅述。
              一旦 你創建了一個靜態函數庫,你可以使用它了。你可以把它作為你編譯和連接過程中的一部分用來生成你的可執行代碼。如果你用gcc來編譯產生可 執行代碼的話,你可以用“-l”參數來指定這個庫函數。你也可以用ld來做,使用它的“-l”和“-L”參數選項。具體用法,可以參考info:gcc。
             3. 共享函數庫
              共享函數庫中的函數是在當一個可執行程序在啟動的時候被加載。如果一個共享函數庫正常安裝,所有的程序在重新運行的時候都可以自動加載最新的函數庫中的函數。對于Linux系統還有更多的可以實現的功能:
            o 升級了函數庫但是仍然允許程序使用老版本的函數庫。 o 當執行某個特定程序的時候可以覆蓋某個特定的庫或者庫中指定的函數。 o 可以在庫函數被使用的過程中修改這些函數庫。
              3.1. 一些約定
               如果你要編寫的共享函數庫支持所有有用的特性,你在編寫的過程中必須遵循一系列約定。你必須理解庫的不同的名字間的區別,例如它的 “soname”和“real name”之間的區別和它們是如何相互作用的。你同樣還要知道你應該把這些庫函數放在你文件系統的什么位置等等。下面我們具體看看這些問題。
              3.1.1. 共享庫的命名
              每個共享函數庫都有個特殊的名字,稱作“soname”。Soname名字命名必須以“lib”作為前綴,然后是函數庫的名字,然后是“.so”,最后是版本號信息。不過有個特例,就是非常底層的C庫函數都不是以lib開頭這樣命名的。
              每個共享函數庫都有一個真正的名字(“real name”),它是包含真正庫函數代碼的文件。真名有一個主版本號,和一個發行版本號。最后一個發行版本號是可選的,可以沒有。主版本號和發行版本號使你可以知道你到底是安裝了什么版本的庫函數。
            另外,還有一個名字是編譯器編譯的時候需要的函數庫的名字,這個名字就是簡單的soname名字,而不包含任何版本號信息。
               管理共享函數庫的關鍵是區分好這些名字。當可執行程序需要在自己的程序中列出這些他們需要的共享庫函數的時候,它只要用soname就可以了; 反過來,當你要創建一個新的共享函數庫的時候,你要指定一個特定的文件名,其中包含很細節的版本信息。當你安裝一個新版本的函數庫的時候,你只要先將這些 函數庫文件拷貝到一些特定的目錄中,運行ldconfig這個實用就可以。Ldconfig檢查已經存在的庫文件,然后創建soname的符號鏈接到真正 的函數庫,同時設置/etc/ld.so.cache這個緩沖文件。這個我們稍后再討論。
              Ldconfig并不設置鏈接的名字,通常 的做法是在安裝過程中完成這個鏈接名字的建立,一般來說這個符號鏈接就簡單的指向最新的soname 或者最新版本的函數庫文件。最好把這個符號鏈接指向soname,因為通常當你升級你的庫函數的后,你就可以自動使用新版本的函數庫勒。
              我們來舉例看看:
               /usr/lib/libreadline.so.3 是一個完全的完整的soname,ldconfig可以設置一個符號鏈接到其他某個真正的函數庫文件,例如是 /usr/lib/libreadline.so.3.0。同時還必須有一個鏈接名字,例如/usr/lib/libreadline.so 就是一個符號鏈接指向/usr/lib/libreadline.so.3。
            3.1.2. 文件系統中函數庫文件的位置
               共享函數庫文件必須放在一些特定的目錄里,這樣通過系統的環境變量設置,應用程序才能正確的使用這些函數庫。大部分的源碼開發的程序都遵循 GNU的一些標準,我們可以看info幫助文件獲得相信的說明,info信息的位置是:info: standards#Directory_Variables。GNU標準建議所有的函數庫文件都放在/usr/local/lib目錄下,而且建議命令 可執行程序都放在/usr/local/bin目錄下。這都是一些習慣問題,可以改變的。
              文件系統層次化標準FHS(Filesystem Hierarchy Standard)(http://www.pathname.com/fhs)規定了在一個發行包中大部分的函數庫文件應該安裝到/usr/lib目錄 下,但是如果某些庫是在系統啟動的時候要加載的,則放到/lib目錄下,而那些不是系統本身一部分的庫則放到/usr/local/lib下面。
              上面兩個路徑的不同并沒有本質的沖突。GNU提出的標準主要對于開發者開發源碼的,而FHS的建議則是針對發行版本的路徑的。具體的位置信息可以看/etc/ld.so.conf里面的配置信息。
              3.2. 這些函數庫如何使用
               在基于GNU glibc的系統里,包括所有的linux系統,啟動一個ELF格式的二進制可執行文件會自動啟動和運行一個program loader。對于Linux系統,這個loader的名字是/lib/ld-linux.so.X(X是版本號)。這個loader啟動后,反過來就會 load所有的其他本程序要使用的共享函數庫。
              到底在哪些目錄里查找共享函數庫呢?這些定義缺省的是放在 /etc/ld.so.conf文件里面,我們可以修改這個文件,加入我們自己的一些 特殊的路徑要求。大多數RedHat系列的發行包的/etc/ld.so.conf文件里面不包括/usr/local/lib這個目錄,如果沒有這個目 錄的話,我們可以修改/etc/ld.so.conf,自己手動加上這個條目。
              如果你想覆蓋某個庫中的一些函數,用自己的函數替換它們,同時保留該庫中其他的函數的話,你可以在/etc/ld.so.preload中加入你想要替換的庫(.o結尾的文件),這些preloading的庫函數將有優先加載的權利。
               當程序啟動的時候搜索所有的目錄顯然會效率很低,于是Linux系統實際上用的是一個高速緩沖的做法。Ldconfig缺省情況下讀出 /etc/ld.so.conf相關信息,然后設置適當地符號鏈接,然后寫一個cache到/etc/ld.so.cache這個文件中,而這個 /etc/ld.so.cache則可以被其他程序有效的使用了。這樣的做法可以大大提高訪問函數庫的速度。這就要求每次新增加一個動態加載的函數庫的時 候,就要運行ldconfig來更新這個cache,如果要刪除某個函數庫,或者某個函數庫的路徑修改了,都要重新運行ldconfig來更新這個 cache。通常的一些包管理器在安裝一個新的函數庫的時候就要運行ldconfig。
              另外,FreeBSD使用cache的文件不一樣。FreeBSD的ELF cache是/var/run/ld-elf.so.hints,而a.out的cache責是/var/run/ld.so.hints。它們同樣是通過ldconfig來更新。
              3.3. 環境變量
               各種各樣的環境變量控制著一些關鍵的過程。例如你可以臨時為你特定的程序的一次執行指定一個不同的函數庫。Linux系統中,通常變量 LD_LIBRARY_PATH就是可以用來指定函數庫查找路徑的,而且這個路徑通常是在查找標準的路徑之前查找。這個是很有用的,特別是在調試一個新的 函數庫的時候,或者在特殊的場合使用一個肥標準的函數庫的時候。環境變量LD_PRELOAD列出了所有共享函數庫中需要優先加載的庫文件,功能和 /etc/ld.so.preload類似。這些都是有/lib/ld-linux.so這個loader來實現的。值得一提的是, LD_LIBRARY_PATH可以在大部分的UNIX-linke系統下正常起作用,但是并非所有的系統下都可以使用,例如HP-UX系統下,就是用 SHLIB_PATH這個變量,而在AIX下則使用LIBPATH這個變量。
              LD_LIBRARY_PATH在開發和調試過程中經常大量使用,但是不應該被一個普通用戶在安裝過程中被安裝程序修改,大家可以去參考 http://www.visi.com/~barr/ldpath.html,這里有一個文檔專門介紹為什么不使用LD_LIBRARY_PATH這個 變量。
              事實上還有更多的環境變量影響著程序的調入過程,它們的名字通常就是以LD_或者RTLD_打頭。大部分這些環境變量的使用的文檔都是不全,通常搞得人頭昏眼花的,如果要真正弄清楚它們的用法,最好去讀loader的源碼(也就是gcc的一部分)。
               允許用戶控制動態鏈接函數庫將涉及到setuid/setgid這個函數如果特殊的功能需要的話。因此,GNU loader通常限制或者忽略用戶對這些變量使用setuid和setgid。如果loader通過判斷程序的相關環境變量判斷程序的是否使用了 setuid或者setgid,如果uid和euid不同,或者gid和egid部一樣,那么loader就假定程序已經使用了setuid或者 setgid,然后就大大的限制器控制這個老鏈接的權限。如果閱讀GNU glibc的庫函數源碼,就可以清楚地看到這一點,特別的我們可以看elf/rtld.c和sysdeps/generic/dl-sysdep.c這兩 個文件。這就意味著如果你使得uid和gid與euid和egid分別相等,然后調用一個程序,那么這些變量就可以完全起效。
            3.4. 創建一個共享函數庫
               現在我們開始學習如何創建一個共享函數庫。其實創建一個共享函數庫非常容易。首先創建object文件,這個文件將加入通過gcc –fPIC 參數命令加入到共享函數庫里面。PIC的意思是“位置無關代碼”(Position Independent Code)。下面是一個標準的格式:
            gcc -shared -Wl,-soname,your_soname -o library_name file_list library_list
              下面再給一個例子,它創建兩個object文件(a.o和b.o),然后創建一個包含a.o和b.o的共享函數庫。例子中”-g”和“-Wall”參數不是必須的。
            gcc -fPIC -g -c -Wall a.cgcc -fPIC -g -c -Wall b.cgcc -shared -Wl,
            -soname,liblusterstuff.so.1 -o liblusterstuff.so.1.0.1 a.o b.o -lc
              下面是一些需要注意的地方:
            · 不用使用-fomit-frame-pointer這個編譯參數除非你不得不這樣。雖然使用了這個參數獲得的函數庫仍然可以使用,但是這使得調試程序幾乎 沒有用,無法跟蹤調試。 · 使用-fPIC來產生代碼,而不是-fpic。 · 某些情況下,使用gcc 來生成object文件,需要使用“-Wl,-export-dynamic”這個選項參數。通常,動態函數庫的符號表里面包含了這些動態的對象的符號。 這個選項在創建ELF格式的文件時候,會將所有的符號加入到動態符號表中。可以參考ld的幫助獲得更詳細的說明。
              3.5. 安裝和使用共享函數庫
              一旦你了一個共享函數庫,你還需要安裝它。其實簡單的方法就是拷貝你的庫文件到指定的標準的目錄(例如/usr/lib),然后運行ldconfig。
               如果你沒有權限去做這件事情,例如你不能修改/usr/lib目錄,那么你就只好通過修改你的環境變量來實現這些函數庫的使用了。首先,你需要 創建這些共享函數庫;然后,設置一些必須得符號鏈接,特別是從soname到真正的函數庫文件的符號鏈接,簡單的方法就是運行ldconfig:
            ldconfig -n directory_with_shared_libraries
              然后你就可以設置你的LD_LIBRARY_PATH這個環境變量,它是一個以逗號分隔的路徑的集合,這個可以用來指明共享函數庫的搜索路徑。例如,使用bash,就可以這樣來啟動一個程序my_program:
            LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH my_program
               如果你需要的是重載部分函數,則你就需要創建一個包含需要重載的函數的object文件,然后設置LD_PRELOAD環境變量。通常你可以很 方便的升級你的函數庫,如果某個API改變了,創建庫的程序會改變soname。然而,如果一個函數升級了某個函數庫而保持了原來的soname,你可以 強行將老版本的函數庫拷貝到某個位置,然后重新命名這個文件(例如使用原來的名字,然后后面加.orig后綴),然后創建一個小的“wrapper”腳本 來設置這個庫函數和相關的東西。例如下面的例子:
            #!/bin/sh export LD_LIBRARY_PATH=/usr/local/my_lib:$LD_LIBRARY_PATH exec
            /usr/bin/my_program.orig $*
              我們可以通過運行ldd來看某個程序使用的共享函數庫。例如你可以看ls這個實用工具使用的函數庫:
            ldd /bin/ls
                libtermcap.so.2 => /lib/libtermcap.so.2 (0x4001c000)
                libc.so.6 => /lib/libc.so.6 (0x40020000)
                /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)
              通常我么可以看到一個soname的列表,包括路徑。在所有的情況下,你都至少可以看到兩個庫:
            · /lib/ld-linux.so.N(N是1或者更大,一般至少2)。
            這是這個用力加載其他所有的共享庫的庫。
            · libc.so.N(N應該大于或者等于6)。這是C語言函數庫。
               值得一提的是,不要在對你不信任的程序運行ldd命令。在ldd的manual里面寫得很清楚,ldd是通過設置某些特殊的環境變量(例如,對 于ELF對象,設置LD_TRACE_LOADED_OBJECTS),然后運行這個程序。這樣就有可能使得某地程序可能使得ldd來執行某些意想不到的 代碼,而產生不安全的隱患。
            3.6. 不兼容的函數庫
              如果一個新版的函數庫要和老版本的二進制的庫不兼容,則soname需要改變。對于C語言,一共有4個基本的理由使得它們在二進制代碼上很難兼容:
              o. 一個函數的行文改變了,這樣它就可能與最開始的定義不相符合。
              o. 輸出的數據項改變了。
              o. 某些輸出的函數刪除了。
              o. 某些輸出函數的接口改變了。
              如果你能避免這些地方,你就可以保持你的函數庫在二進制代碼上的兼容,或者說,你可以使得你的程序的應用二進制接口(ABI:Application Binary Interface)上兼容。
              4. 動態加載的函數庫Dynamically Loaded (DL) Libraries
               動態加載的函數庫Dynamically loaded (DL) libraries是一類函數庫,它可以在程序運行過程中的任何時間加載。它們特別適合在函數中加載一些模塊和plugin擴展模塊的場合,因為它可以在 當程序需要某個plugin模塊時才動態的加載。例如,Pluggable Authentication Modules(PAM)系統就是用動態加載函數庫來使得管理員可以配置和重新配置身份驗證信息。
              Linux系統下,DL函數庫與其 他函數庫在格式上沒有特殊的區別,我們前面提到過,它們創建的時候是標準的object格式。主要的區別就是 這些函數庫不是在程序鏈接的時候或者啟動的時候加載,而是通過一個API來打開一個函數庫,尋找符號表,處理錯誤和關閉函數庫。通常C語言環境下,需要包 含這個頭文件。
              Linux中使用的函數和Solaris中一樣,都是dlpoen() API。當時不是所有的平臺都使用同樣的接口,例如HP-UX使用shl_load()機制,而Windows平臺用另外的其他的調用接口。如果你的目的 是使得你的代碼有很強的移植性,你應該使用一些wrapping函數庫,這樣的wrapping函數庫隱藏不同的平臺的接口區別。一種方法是使用 glibc函數庫中的對動態加載模塊的支持,它使用一些潛在的動態加載函數庫界面使得它們可以夸平臺使用。具體可以參考http: //developer.gnome.org/doc/API/glib/glib-dynamic-loading-of-modules.html. 另外一個方法是使用libltdl,是GNU libtool的一部分,可以進一步參考CORBA相關資料。
              4.1. dlopen()
              dlopen函數打開一個函數庫然后為后面的使用做準備。C語言原形是:
            void * dlopen(const char *filename, int flag);
              如果文件名filename是以“/”開頭,也就是使用絕對路徑,那么dlopne就直接使用它,而不去查找某些環境變量或者系統設置的函數庫所在的目錄了。否則dlopen()
              就會按照下面的次序查找函數庫文件:
            1. 環境變量LD_LIBRARY指明的路徑。 2. /etc/ld.so.cache中的函數庫列表。 3. /lib目錄,然后/usr/lib。不過一些很老的a.out的loader則是采用相反的次序,也就是先查/usr/lib,然后是/lib。
               Dlopen()函數中,參數flag的值必須是RTLD_LAZY或者RTLD_NOW,RTLD_LAZY的意思是resolve undefined symbols as code from the dynamic library is executed,而RTLD_NOW的含義是resolve all undefined symbols before dlopen() returns and fail if this cannot be done'。
              如果有好幾個函數庫,它們之間有一些依賴關系的話,例如X依賴Y,那么你就要先加載那些被依賴的函數。例如先加載Y,然后加載X。
              dlopen()函數的返回值是一個句柄,然后后面的函數就通過使用這個句柄來做進一步的操作。如果打開失敗dlopen()就返回一個NULL。如果一個函數庫被多次打開,它會返回同樣的句柄。
              如果一個函數庫里面有一個輸出的函數名字為_init,那么_init就會在dlopen()這個函數返回前被執行。我們可以利用這個函數在我的函數庫里面做一些初始化的工作。我們后面會繼續討論這個問題的。
              4.2. dlerror()
              通過調用dlerror()函數,我們可以獲得最后一次調用dlopen(),dlsym(),或者dlclose()的錯誤信息。
            4.3. dlsym()
              如果你加載了一個DL函數庫而不去使用當然是不可能的了,使用一個DL函數庫的最主要的一個函數就是dlsym(),這個函數在一個已經打開的函數庫里面查找給定的符號。這個函數如下定義:
            void * dlsym(void *handle, char *symbol);
              函數中的參數handle就是由dlopen打開后返回的句柄,symbol是一個以NIL結尾的字符串。
               如果dlsym()函數沒有找到需要查找的symbol,則返回NULL。如果你知道某個symbol的值不可能是NULL或者0,那么就很 好,你就可以根據這個返回結果判斷查找的symbol是否存在了;不過,如果某個symbol的值就是NULL,那么這個判斷就有問題了。標準的判斷方法 是先調用dlerror(),清除以前可能存在的錯誤,然后調用dlsym()來訪問一個symbol,然后再調用dlerror()來判斷是否出現了錯 誤。一個典型的過程如下:
            dlerror();
            s = (actual_type) dlsym(handle, symbol_being_searched_for);
            if ((err = dlerror()) != NULL)
            {
            }
            else
            {
            }
              4.4. dlclose()
               dlopen()函數的反過程就是dlclose()函數,dlclose()函數用力關閉一個DL函數庫。Dl函數庫維持一個資源利用的計數 器,當調用dlclose的時候,就把這個計數器的計數減一,如果計數器為0,則真正的釋放掉。真正釋放的時候,如果函數庫里面有_fini()這個函 數,則自動調用_fini()這個函數,做一些必要的處理。Dlclose()返回0表示成功,其他非0值表示錯誤。
              4.5. DL Library Example
              下面是一個例子。例子中調入math函數庫,然后打印2.0的余弦函數值。例子中每次都檢查是否出錯。應該是個不錯的范例:
              #include
              #include
              #include
              int main(int argc, char **argv)
              {
                void *handle;
                double (*cosine)(double);
                char *error;
                handle = dlopen ("/lib/libm.so.6", RTLD_LAZY);
                if (!handle) {
                    fputs (dlerror(), stderr);
                    exit(1);
                }
                cosine = dlsym(handle, "cos");
                if ((error = dlerror()) != NULL)
              {
                    fputs(error, stderr);
                    exit(1);
                }
                printf ("%f ", (*cosine)(2.0));
                dlclose(handle);
            }
              如果這個程序名字叫foo.c,那么用下面的命令來編譯:
              gcc -o foo foo.c -ldl
            5. 其他
              5.1. nm命令
               nm命令可以列出一個函數庫文件中的符號表。它對于靜態的函數庫和共享的函數庫都起作用。對于一個給定的函數庫,nm命令可以列出函數庫中定義 的所有符號,包括每個符號的值和類型。還可以給出在原程序中這個函數(符號)是在多少行定義的,不過這必須要求編譯該函數庫的時候加“-l”選項。
               關于符號的類型,這里我們再多討論一下。符號的類型是以一個字母的形式顯示的,小寫字母表示這個符號是本地(local)的,而大寫字母則表示 這個符號是全局的(global,externel)。一般來說,類型有一下幾種:T、D、B、U、W。各自的含義如下:T表示在代碼段中定義的一般變量 符號;D表示時初始化過的數據段;B表示初始化的數據段;U表示沒有定義的,在這個庫里面使用了,但是在其他庫中定義的符號;W,weak的縮寫,表示如 果其他函數庫中也有對這個符號的定義,則其他符號的定義可以覆蓋這個定義。
              如果你知道一個函數的名字,但是你不知道這個函數在什么庫中定義的,那么可以用mn的“-o”選項和grep命令來查找庫的名字。-o選項使得顯示的每一行都有這個函數庫文件名。例如,你要查找“cos”這個是在什么地方定義的,大致可以用下面的命令:
            nm -o /lib* /usr/local/libGROUP ( /lib/libc.so.6
            /usr/lib/libc_nonshared.a )
              更多的信息可以參考texinfo文檔中關于ld鏈接的腳本部分。一般的信息還可以參考: info:ld#Options 和info:ld#Commands,也可以參考info:ld#Option Commands。
              5.4. GNU libtool
               如果你正在編譯的系統相很方便的移植到其他操作系統下,你可以使用GNU libtool來創建和安裝這個函數庫。GNU libtool是一個函數庫支持的典型的腳本。Libtool隱藏了使用一個可移植的函數庫的負責性。Libtool提供了一個可以移植的界面來創建 object文件,鏈接函數庫(靜態或者共享的),并且安裝這些庫。它還包含了libltdl,一個可移植的動態函數庫調入程序的wrapper。更多的 詳細討論,可以在http://www.gnu.org/software/libtool/manual.html看到。
              5.5. 刪除一些符號
              在一個生產的文件中很多符號都是為了debug而包含的,占用了不少空間。如果空間不夠,而且這些符號也許不再需要,就可以將其中一些刪除。
               最好的方法就是先正常的生成你需要的object文件,然后debug和測試你需要的一些東西。一旦你完全測試完畢了,就可以用strip去刪 除一些不需要的符號了。Strip命令可以使你很方便的控制刪除什么符號,而保留什么符號。Strip的具體用法可以參考其幫助文件。
              另外的方法就是使用GNU ld的選項“-S”和“-s”;“-S”會刪除一些debugger的符號,而“-s”則是將所有的符號信息都刪除。通常我們可以在gcc中加這樣的參數“-Wl,-S”和“-Wl,-s”來達到這個目的。
            摘要
            下 面是一些例子,例子中我們會使用三種函數庫(靜態的、共享的和動態加載的函數庫)。文件libhello.c是一個函數庫,libhello.h 是它的頭文件;demo_use.c則是一個使用了libhello函數庫的。Script_static和script_dynamic分別演示如何以 靜態和共享方式使用函數庫,而后面的demo_dynamic.c和script_dynamic則表示演示如何以動態加載函數庫的方式來使用它。
            (2002-08-25 17:38:37)
            By Wing
              6. 更多的例子
               下面是一些例子,例子中我們會使用三種函數庫(靜態的、共享的和動態加載的函數庫)。文件libhello.c是一個函數庫, libhello.h是它的頭文件;demo_use.c則是一個使用了libhello函數庫的。Script_static和 script_dynamic分別演示如何以靜態和共享方式使用函數庫,而后面的demo_dynamic.c和script_dynamic則表示演示 如何以動態加載函數庫的方式來使用它。
              6.1. File libhello.c
            #include
            void hello(void)
            {
            printf("Hello, library world.
            ");
            }
              6.2. File libhello.h
            void hello(void);
              6.3. File demo_use.c
            #include "libhello.h"
            int main(void)
            {
            hello();
            return 0;
            }
              6.4. File script_static
            #!/bin/sh
            # Static library demo
            # Create static library's object file, libhello-static.o.
            # I'm using the name libhello-static to clearly
            # differentiate the static library from the
            # dynamic library examples, but you don't need to use
            # "-static" in the names of your
            # object files or static libraries.gcc -Wall -g -c -o libhello-static.o
            libhello.c
            # Create static library.ar rcs libhello-static.a libhello-static.o
            # At this point we could just copy libhello-static.a
            # somewhere else to use it.
            # For demo purposes, we'll just keep the library
            # in the current directory.
            # Compile demo_use program file.gcc -Wall -g -c demo_use.c -o demo_use.o
            # Create demo_use program; -L. causes "." to be searched during
            # creation of the program. Note that this command causes
            # the relevant object file in libhello-static.a to be
            # incorporated into file demo_use_static.gcc -g -o demo_use_static
            demo_use.o -L. -lhello-static
            # Execute the program../demo_use_static
              6.5. File script_shared
            #!/bin/sh
            # Shared library demo
            # Create shared library's object file, libhello.o.gcc -fPIC -Wall
            -g -c libhello.c
            # Create shared library.
            # Use -lc to link it against C library, since libhello
            # depends on the C library.gcc -g -shared -Wl,-soname,libhello.so.0 -o
            libhello.so.0.0 libhello.o -lc# At this point we could just copy
            libhello.so.0.0 into
            # some directory, say /usr/local/lib.
            # Now we need to call ldconfig to fix up the symbolic links.
            # Set up the soname. We could just execute:
            # ln -sf libhello.so.0.0 libhello.so.0
            # but let's let ldconfig figure it out./sbin/ldconfig -n .
            # Set up the linker name.
            # In a more sophisticated setting, we'd need to make
            # sure that if there was an existing linker name,
            # and if so, check if it should stay or not.ln -sf libhello.so.0
            libhello.so
            # Compile demo_use program file.gcc -Wall -g -c demo_use.c -o
            demo_use.o
            # Create program demo_use.
            # The -L. causes "." to be searched during creation
            # of the program; note that this does NOT mean that "."
            # will be searched when the program is executed.gcc -g -o demo_use
            demo_use.o -L. -lhello
            # Execute the program. Note that we need to tell the program
            # where the shared library is,
            using LD_LIBRARY_PATH.LD_LIBRARY_PATH="." ./demo_use
              6.6. File demo_dynamic.c
            #include
            #include
            #include
            typedef void (*simple_demo_function)(void);
            int main(void)
            {
            const char *error;
            void *module;
            simple_demo_function demo_function;
            module = dlopen("libhello.so", RTLD_LAZY);
            if (!module)
            {
              fprintf(stderr, "Couldn't open libhello.so: %s
            ",dlerror());
              exit(1);
            }
            dlerror();
            demo_function = dlsym(module, "hello");
            if ((error = dlerror()))
            {
              fprintf(stderr, "Couldn't find hello: %s
            ", error);
              exit(1);
            }
            (*demo_function)();
            dlclose(module);
            return 0;
            }
              6.7. File script_dynamic
            #!/bin/sh
            # Dynamically loaded library demo
            # Presume that libhello.so and friends have
            # been created (see dynamic example).
            # Compile demo_dynamic program file into an object file.gcc
            -Wall -g -c demo_dynamic.c
            # Create program demo_use.
            # Note that we don't have to tell it where to search
            for DL libraries,
            # since the only special library this program uses won't be
            # loaded until after the program starts up.
            # However, we DO need the option -ldl to include the library
            # that loads the DL libraries.gcc -g -o demo_dynamic
            demo_dynamic.o -ldl
            # Execute the program. Note that we need to tell the
            # program where get the dynamically loaded library,
            # using LD_LIBRARY_PATH.LD_LIBRARY_PATH="." ./demo_dynamic

            posted @ 2012-12-07 22:34 tqsheng 閱讀(243) | 評論 (0)編輯 收藏

            linux下動態庫so文件的一些認識

            個人創作,歡迎指錯。 
            牽扯到ELF格式,gcc編譯選項待補,簡單實用的說明一下,對Linux下的so文件有個實際性的認識。 
            1.so文件是什么? 
            2.怎么生成以及使用一個so動態庫文件? 
            3.地址空間,以及線程安全. 
            4.庫的初始化,解析: 
            5.使用我們自己庫里的函數替換系統函數: 
            //------------------------------------------------------------------------------- 
            1.so文件是什么? 
            也是ELF格式文件,共享庫(動態庫),類似于DLL。節約資源,加快速度,代碼升級簡化。 
            知道這么多就夠了,實用主義。等有了印象再研究原理。 
            2.怎么生成以及使用一個so動態庫文件? 
            先寫一個C文件:s.c 
            C代碼  
            #include <stdio.h>  
            int count;  
            void out_msg(const char *m)  
            {//2秒鐘輸出1次信息,并計數  
             for(;;) {printf("%s %d\n", m, ++count); sleep(2);}  
            }  
            編譯:得到輸出文件libs.o 
            gcc -fPIC -g -c s.c -o libs.o 
              
            鏈接:得到輸出文件libs.so 
            gcc -g -shared -Wl,-soname,libs.so -o libs.so libs.o -lc 
            一個頭文件:s.h 
            C代碼  
            #ifndef _MY_SO_HEADER_  
            #define _MY_SO_HEADER_  
            void out_msg(const char *m);  
            #endif  
            再來一個C文件來引用這個庫中的函數:ts.c 
            C代碼  
            #include <stdio.h>  
             #include "s.h"  
             int main(int argc, char** argv)  
             {  
              printf("TS Main\n");  
              out_msg("TS ");  
              sleep(5);  //這句話可以注釋掉,在第4節的時候打開就可以。  
              printf("TS Quit\n");  
             }  
            編譯鏈接這個文件:得到輸出文件ts 
            gcc -g ts.c -o ts -L. -ls 
            執行./ts,嗯:成功了。。。還差點 
            得到了ts:error while loading shared libraries: libs.so: cannot open shared object file: No such file or directory 
            系統不能找到我們自己定義的libs.so,那么告訴他,修改變量LD_LIBRARY_PATH,為了方便,寫個腳本:e(文件名就叫e,懶得弄長了) 
            #!/bin/sh 
            export LD_LIBRARY_PATH=${pwd}:${LD_LIBRARY_PATH} 
            ./ts 
            執行:./e & 
            屏幕上就開始不停有信息輸出了,當然TS Quit你是看不到的,前面是個死循環,后面會用到這句 
            3.地址空間,以及線程安全: 
            如果這樣: 
            ./e &開始執行后,稍微等待一下然后再 ./e&, 
            這個時候屏幕信息會怎么樣呢?全局變量count會怎么變化? 
            會是兩個進程交叉輸出信息,并且各自的count互不干擾,雖然他們引用了同一個so文件。 
            也就是說只有代碼是否線程安全一說,沒有代碼是否是進程安全這一說法。 
            4.庫的初始化,解析: 
            windows下的動態庫加載,卸載都會有初始化函數以及卸載函數來完成庫的初始化以及資源回收,linux當然也可以實現。 
            ELF文件本身執行時就會執行一個_init()函數以及_fini()函數來完成這個,我們只要把自己的函數能讓系統在這個時候執行 
            就可以了。 
            修改我們前面的s.c文件: 
            C代碼  
            #include <stdio.h>  
             void my_init(void) __attribute__((constructor)); //告訴gcc把這個函數扔到init section  
             void my_fini(void) __attribute__((destructor));  //告訴gcc把這個函數扔到fini section  
             void out_msg(const char *m)  
             {  
              printf(" Ok!\n");   
             }  
             int i; //仍然是個計數器  
             void my_init(void)  
             {  
              printf("Init ... ... %d\n", ++i);  
             }  
             void my_fini(void)  
             {  
              printf("Fini ... ... %d\n", ++i);  
             }  
            重新制作 libs.so,ts本是不用重新編譯了,代碼維護升級方便很多。 
            然后執行: ./e & 
            可以看到屏幕輸出:(不完整信息,只是順序一樣) 
            Init 
            Main 
            OK 
            Quit 
            Fini 
            可以看到我們自己定義的初始化函數以及解析函數都被執行了,而且是在最前面以及最后面。 
            如果s.c中的sleep(5)沒有注釋掉,那么有機會: 
            ./e& 
            ./e&連續執行兩次,那么初始化函數和解析函數也會執行兩次,雖然系統只加載了一次libs.so。 
            如果sleep時候kill 掉后臺進程,那么解析函數不會被執行。 
            5.使用我們自己庫里的函數替換系統函數: 
            創建一個新的文件b.c:我們要替換系統函數malloc以及free(可以自己寫個內存泄露檢測工具了) 
            C代碼  
            #include <stdio.h>  
             void* malloc(int size)  
             {  
              printf("My malloc\n");  
              return NULL;  
             }  
             void free(void* ad)  
             {  
              printf("My free\n");  
             }  
            老規矩,編譯鏈接成一個so文件:得到libb.so 
            gcc -fPIC -g -c b.c -o libb.o 
            gcc -g -shared -Wl,-soname,libb.so -o libb.so -lc 
            修改s.c:重新生成libs.so 
            C代碼  
            void out_msg()  
             {  
              int *p;  
              p = (int*)malloc(100);  
              free(p);  
              printf("Stop Ok!\n");  
             }  
            修改腳本文件e: 
            #!/bin/sh 
            export LD_PRELOAD=${pwd}libb.so:${LD_PRELOAD} 
            export LD_LIBRARY_PATH=${pwd}:${LD_LIBRARY_PATH} 
            ./ts 
            關鍵就在LD_PRELOAD上了,這個路徑指定的so將在所有的so之前加載,并且符號會覆蓋后面加載的so文件中的符號。如果可執行文件的權限不合適(SID),這個變量會被忽略。 
            執行:./e & 
            嗯,可以看到我們的malloc,free工作了。 
            暫時就想到這么多了。

            posted @ 2012-12-07 22:33 tqsheng 閱讀(722) | 評論 (0)編輯 收藏

            Linux下動態庫只導出部分函數

            編譯一個1.c文件:
            #include "stdio.h"
            #if defined(__GNUC__) && \
                    ((__GNUC__ >= 4) || (__GNUC__ == 3 && __GNUC_MINOR__ >= 3))
            #define NP_VISIBILITY_DEFAULT __attribute__((visibility("default")))
            #else
            #define NP_VISIBILITY_DEFAULT
            #endif
            #define NP_EXPORT(__type) NP_VISIBILITY_DEFAULT __type
            NP_EXPORT(int) a()
            {
            printf("1.c i am a\0");
            return 0;
            }
            int b()
            {
            return 0;
            }
            我的目的是默認沒有使用NP_EXPORT宏的函數都隱藏起來,即把b函數給隱藏起來,而a函數則導出去。
            我的編譯步驟和指令如下:
            gcc -c 1.c
            gcc –fPIC -shared -fvisibility=hidden  -o 1.so 1.o
            編譯后生成了1.so文件。
            我使用nm工具查看,這個時候就可以達到隱藏不必要的函數,而只是導出定義過的函數。

            posted @ 2012-12-07 22:28 tqsheng 閱讀(1306) | 評論 (0)編輯 收藏

            Linux中.a,.la,.o,.so文件的意義和編程實現_動態鏈接

            Linux下文件的類型是不依賴于其后綴名的,但一般來講:
              .o,是目標文件,相當于windows中的.obj文件
              .so 為共享庫,是shared object,用于動態連接的,和dll差不多
              .a為靜態庫,是好多個.o合在一起,用于靜態連接
              .la為libtool自動生成的一些共享庫,vi編輯查看,主要記錄了一些配置信息。可以用如下命令查看.la文件的格式 $file .la
              .la: ASCII English text
              所以可以用vi來查看其內容。
              創建.a庫文件和.o庫文件:
              [yufei@localhost perl_c2]$ pwd
              /home/yufei/perl_c2
              [yufei@localhost perl_c2]$ cat mylib.c
              #include 
              #include 
              void hello(){
              printf("success call from perl to c library\n");
              }
              [yufei@localhost perl_c2]$ cat mylib.h
              extern void hello();
              [yufei@localhost perl_c2]$ gcc -c mylib.c
              [yufei@localhost perl_c2]$ dir
              mylib.c mylib.h mylib.o
              [yufei@localhost perl_c2]$ ar -r mylib.a mylib.o
              ar: 正在創建 mylib.a
              [yufei@localhost perl_c2]$ dir
              mylib.a mylib.c mylib.h mylib.o
              動態鏈接庫.so的編譯與使用- -
              動態庫.so在Linux下用c和c++編程時經常會碰到,最近在網站找了幾篇文章介紹動態庫的編譯和鏈接,總算搞懂了這個之前一直不太了解得東東,這里做個筆記,也為其它正為動態庫鏈接庫而苦惱的兄弟們提供一點幫助。
              1、動態庫的編譯
              下面通過一個例子來介紹如何生成一個動態庫。這里有一個頭文件:so_test.h,三個.c文件:test_a.c、test_b.c、test_c.c,我們將這幾個文件編譯成一個動態庫:libtest.so。
              so_test.h:
              #include 
              #include 
              void test_a();
              void test_b();
              void test_c();
              test_a.c:
              #include "so_test.h"
              void test_a()
              {
              printf("this is in test_a...\n");
              }
              test_b.c:
              #include "so_test.h"
              void test_b()
              {
              printf("this is in test_b...\n");
              }
              test_c.c:
              #include "so_test.h"
              void test_c()
              {
              printf("this is in test_c...\n");
              }
              將這幾個文件編譯成一個動態庫:libtest.so
              $ gcc test_a.c test_b.c test_c.c -fPIC -shared -o libtest.so 來
            2、動態庫的鏈接
              在1、中,我們已經成功生成了一個自己的動態鏈接庫libtest.so,下面我們通過一個程序來調用這個庫里的函數。程序的源文件為:test.c。
              test.c:
              #include "so_test.h"
              int main()
              {
              test_a();
              test_b();
              test_c();
              return 0;
              }
              l 將test.c與動態庫libtest.so鏈接生成執行文件test:
              $ gcc test.c -L. -ltest -o test
              l 測試是否動態連接,如果列出libtest.so,那么應該是連接正常了
              $ ldd test
              l 執行test,可以看到它是如何調用動態庫中的函數的。
              3、編譯參數解析
              最主要的是GCC命令行的一個選項:
              -shared 該選項指定生成動態連接庫(讓連接器生成T類型的導出符號表,有時候也生成弱連接W類型的導出符號),不用該標志外部程序無法連接。相當于一個可執行文件
              -fPIC:表示編譯為位置獨立的代碼,不用此選項的話編譯后的代碼是位置相關的所以動態載入時是通過代碼拷貝的方式來滿足不同進程的需要,而不能達到真正代碼段共享的目的。
              -L.:表示要連接的庫在當前目錄中
              -ltest:編譯器查找動態連接庫時有隱含的命名規則,即在給出的名字前面加上lib,后面加上.so來確定庫的名稱
              LD_LIBRARY_PATH:這個環境變量指示動態連接器可以裝載動態庫的路徑。
              當然如果有root權限的話,可以修改/etc/ld.so.conf文件,然后調用 /sbin/ldconfig來達到同樣的目的,不過如果沒有root權限,那么只能采用輸出LD_LIBRARY_PATH的方法了。
              4、注意
              調用動態庫的時候有幾個問題會經常碰到,有時,明明已經將庫的頭文件所在目錄 通過 “-I” include進來了,庫所在文件通過“-L”參數引導,并指定了“-l”的庫名,但通過ldd命令察看時,就是死活找不到你指定鏈接的so文件,這時你要作的就是通過修改LD_LIBRARY_PATH或者/etc/ld.so.conf文件來指定動態庫的目錄。通常這樣做就可以解決庫無法鏈接的問題了。
              makefile里面怎么正確的編譯和連接生成.so庫文件,然后又是在其他程序的makefile里面如何編譯和連接才能調用這個庫文件的函數????
              答:
              你需要告訴動態鏈接器、加載器ld.so在哪里才能找到這個共享庫,可以設置環境變量把庫的路徑添加到庫目錄/lib和/usr/lib,LD_LIBRARY_PATH=$(pwd),這種方法采用命令行方法不太方便,一種替代方法
              注釋
              LD_LIBRARY_PATH可以在/etc/profile還是 ~/.profile還是 ./bash_profile里設置,或者.bashrc里,
              改完后運行source /etc/profile或 . /etc/profile
              更好的辦法是添入/etc/ld.so.conf, 然后執行 /sbin/ldconfig
              注釋
              是把庫路徑添加到/etc/ld.so.conf,然后以root身份運行ldconfig
              也可以在連接的時候指定文件路徑和名稱 -I -L.
              GCC=gcc
              CFLAGS=-Wall -ggdb -fPIC
              #CFLAGS=
              all: libfunc test
              libfunc:func.o func1.o
              $(GCC) -shared -Wl,-soname,libfunc.so.1 -o libfunc.so.1.1 $<
              ln -sf libfunc.so.1.1 libfunc.so.1
              ln -sf libfunc.so.1 libfunc.so
              注釋
              ln -s是用來創建軟鏈接,也就相當于windows中的快捷方式,在當前目錄中創建上一級目錄中的文件ttt的命名為ttt2軟鏈接的命令是ln -s ../ttt ttt2,如果原文件也就是ttt文件刪除的話,ttt2也變成了空文件。
              ln -d是用來創建硬鏈接,也就相當于windows中文件的副本,當原文件刪除的時候,并不影響“副本”的內容。
              編譯目標文件時使用gcc的-fPIC選項,產生與位置無關的代碼并能被加載到任何地址:
              gcc –fPIC –g –c liberr.c –o liberr.o
              使用gcc的-shared和-soname選項;
              使用gcc的-Wl選項把參數傳遞給連接器ld;
              使用gcc的-l選項顯示的連接C庫,以保證可以得到所需的啟動(startup)代碼,從而避免程序在使用不同的,可能不兼容版本的C庫的系統上不能啟動執行。
              gcc –g –shared –Wl,-soname,liberr.so –o liberr.so.1.0.0 liberr.o –lc
              建立相應的符號連接:
              ln –s liberr.so.1.0.0 liberr.so.1;
              ln –s liberr.so.1.0.0 liberr.so;
              在MAKEFILE中:
              $@
              表示規則中的目標文件集。在模式規則中,如果有多個目標,那么,"$@"就是匹配于目標中模式定義的集合。
              $%
              僅當目標是函數庫文件中,表示規則中的目標成員名。例如,如果一個目標是"foo.a(bar.o)",那么,"$%"就是"bar.o","$@"就是"foo.a"。如果目標不是函數庫文件(Unix下是[.a],Windows下是[.lib]),那么,其值為空。
              $<
              依賴目標中的第一個目標名字。如果依賴目標是以模式(即"%")定義的,那么"$<"將是符合模式的一系列的文件集。注意,其是一個一個取出來的。
              $?
              所有比目標新的依賴目標的集合。以空格分隔。
              $
              所有的依賴目標的集合。以空格分隔。如果在依賴目標中有多個重復的,那個這個變量會去除重復的依賴目標,只保留一份。
              注釋
              test: test.o libfunc
              $(GCC) -o test test.o -L. -lfunc
              %.o:%.c
              $(GCC) -c $(CFLAGS) -o $@ $<
              clean:
              rm -fr .o
              rm -fr .so
              rm -fr test
              要生成.so文件,cc要帶-shared 參數;要調用.so的文件,比如libfunc.so,可以在cc命令最后加上-lfunc,還要視情況加上-L/usr/xxx 指出libfunc.so的路徑;這樣,在你要編譯的源文件中就可以調用libfunc.so這個庫文件的函數.
              前面的都說的差不多了,最后提醒一下最好提供一個接口頭文件
              動態加載,用dlopen,dlclose,dlsym 

            posted @ 2012-12-07 22:21 tqsheng 閱讀(159) | 評論 (0)編輯 收藏

            rpm find

            http://rpm.pbone.net/

            posted @ 2012-12-06 20:22 tqsheng 閱讀(242) | 評論 (0)編輯 收藏

            Visual Studio Team System 2008

            不然你試試 Visual Studio Team System 2008 Team Suite 英文版,它是VS的最高級版本。

            下載地址: http://119.147.41.16/down?cid=D4D18C5498974FF51DE3C26AFBBB4DB08120F633&t=2&fmt=&usrinput=visual%20studio%202008&dt=2004000&ps=0_0&rt=0kbs&plt=0

            破解方法:

            一、先安裝試用版,然后在“添加或刪除程序”里找到VS2008,點“更改/刪除”就會看到一個輸入序列號的地方,把序列號輸進去,點“升級”按鈕即可,Team Suite和Professional通用。

            二、把安裝盤下Setupsetup.sdb文件中的[Product Key]項中對應的序列號更改為正式版的序列號后再安裝即可。此方法需要重新打包。 因為九十天試用版本已經是rtm版本。所以改變序列號以后的升級或者安裝,就會變成正式版,不再有使用期限。 以下是收集的序列號:

            1.Visual Studio 2008 Professional Edition: XMQ2Y-4T3V6-XJ48Y-D3K2V-6C4WT (本人親自測試,確實可用)

            2.Visual Studio 2008 Team Test Load Agent: WPX3J-BXC3W-BPYWP-PJ8CM-F7M8T

            3.Visual Studio 2008 Team System: PYHYP-WXB3B-B2CCM-V9DX9-VDY8T

            posted @ 2012-12-05 22:07 tqsheng 閱讀(207) | 評論 (1)編輯 收藏

            qt 不錯的blog

            http://www.cnblogs.com/rockhawk/archive/2010/12/15/1906956.html
            http://blog.csdn.net/vbskj/article/details/7792163

            posted @ 2012-12-05 16:59 tqsheng 閱讀(168) | 評論 (1)編輯 收藏

            1 個不錯的howto 網站

            www.ehow.com

            posted @ 2012-12-05 11:20 tqsheng 閱讀(118) | 評論 (0)編輯 收藏

            http://www.xitongzhijia.net/xp/201207/13158.html

            http://www.xitongzhijia.net/xp/201207/13158.html

            posted @ 2012-12-04 18:52 tqsheng 閱讀(485) | 評論 (2)編輯 收藏

            褲子尺碼對照表

            posted @ 2012-11-28 21:30 tqsheng 閱讀(166) | 評論 (0)編輯 收藏

            中科創想視頻會議

            http://www.zkspcn.cn/productlist/4_1.html

             

            聯系我們

            • 公司總機:+86-010-67832066
            • 全國免費咨詢電話:4008-981-678
            • E-mail:service@zkspcn.cn
            • 地址:北京經濟技術開發區同濟中路7號興盛國際9層

            posted @ 2012-11-28 11:03 tqsheng 閱讀(135) | 評論 (0)編輯 收藏

            僅列出標題
            共25頁: First 2 3 4 5 6 7 8 9 10 Last 
            久久久黄片| 久久香蕉国产线看观看99| 久久综合综合久久97色| 人妻少妇久久中文字幕一区二区| 久久99精品久久久久久水蜜桃| 亚洲午夜久久久精品影院| 国产精久久一区二区三区| 国产福利电影一区二区三区久久老子无码午夜伦不 | 国内精品久久久久久久涩爱 | 久久精品国产只有精品66| 青青草原1769久久免费播放| 久久本道伊人久久| 日本精品久久久久中文字幕8| 亚洲国产精品久久久久久| 久久se精品一区二区影院 | 久久婷婷久久一区二区三区| 一本久久久久久久| 久久久久久亚洲精品无码| 久久亚洲天堂| 亚洲av成人无码久久精品| 久久精品免费观看| 香蕉99久久国产综合精品宅男自 | 国内精品伊人久久久久网站| 亚洲日本va午夜中文字幕久久| 2021最新久久久视精品爱| 久久久久无码精品国产| 91麻精品国产91久久久久| 亚洲AⅤ优女AV综合久久久| 亚洲中文字幕无码久久2017 | 7777精品伊人久久久大香线蕉| 精品永久久福利一区二区| 国产精品美女久久久久AV福利| 日韩人妻无码一区二区三区久久99| 狠狠精品久久久无码中文字幕 | 亚洲日本va午夜中文字幕久久| 久久99国产综合精品女同| 久久久青草青青国产亚洲免观| 亚洲国产欧洲综合997久久| 国产成人久久777777| 久久精品无码专区免费东京热| 久久精品成人免费观看97|