C/C++ 跨平台交叉編譯、靜態庫/動態庫編譯、MinGW、Cygwin、CodeBlocks使用原理及鏈接參數選項


目錄

0. 引言
1. 交叉編譯
2. Cygwin簡介
3. 靜態庫編譯及使用
4. 動態庫編譯及使用
5. MinGW簡介
6. CodeBlocks簡介

 

0. 引言

UNIX是一個注冊商標,是要滿足一大堆條件並且支付可觀費用才能夠被授權使用的一個操作系統。linux是unix的克隆版本,是由其創始人Linus和諸多世界知名的黑客手工打造的一個操作系統。為什么linux和unix之間有很多軟件可以很輕松的移植?因為linux也滿足POSIX規范,所以在運行機制上跟unix相近。同時,POSIX標准也是Linux、windows下能夠進行交叉編譯的基礎

0x1: POSIX(Portable Operating System Interface)

可移植操作系統接口(Portable Operating System Interface POSIX),是IEEE為要在各種UNIX操作系統上運行的軟件,而定義API的一系列互相關聯的標准的總稱,其正式稱呼為IEEE 1003,而國際標准名稱為ISO/IEC 9945。此標准源於一個大約開始於1985年的項目。POSIX這個名稱是由理查德·斯托曼應IEEE的要求而提議的一個易於記憶的名稱。它基本上是(Portable Operating System Interface可移植操作系統接口)的縮寫,而X則表明其對Unix API的傳承

Linux基本上逐步實現了POSIX兼容,但並沒有參加正式的POSIX認證。微軟的Windows NT聲稱部分實現了POSIX標准,因為有POSIX標准的存在,我們在unix、linux、windows上進行編程的時候,會發現有很多API都是通用的,雖然大多數情況下進行跨系統兼容編程是很困難的

當前的POSIX主要分為四個部分

1. Base Definitions
2. System Interfaces
3. Shell and Utilities
4. Rationale

0x2: POSIX 1.1標准

POSIX(Portable Operating System Interface for Computing Systems)是由IEEE 和ISO/IEC 開發的一簇標准。該標准是基於現有的UNIX實踐和經驗,描述了操作系統的調用服務接口,用於保證編制的應用程序可以在源代碼一級上在多種操作系統上移植運行。

1. 1003.0
管理POSIX開放式系統環境(OSE)。IEEE在1995年通過了這項標准。ISO的版本是ISO/IEC 14252:1996

2. 1003.1
被廣泛接受、用於源代碼級別的可移植性標准。1003.1提供一個操作系統的C語言應用編程接口(API)。IEEE和ISO已經在1990年通過了這個標准,IEEE在1995年重新修訂了該標准。

3. 1003.1b
一個用於實時編程的標准(以前的P1003.4或POSIX.4)。這個標准在1993年被IEEE通過,被合並進ISO/IEC 9945-1

4. 1003.1c
一個用於線程(在一個程序中當前被執行的代碼段)的標准。以前是P1993.4或POSIX.4的一部分,這個標准已經在1995年被IEEE通過,歸入ISO/IEC 9945-1:1996

5. 1003.1g
一個關於協議獨立接口的標准,該接口可以使一個應用程序通過網絡與另一個應用程序通訊。1996年,IEEE通過了這個標准

6. 1003.2
一個應用於shell和工具軟件的標准,它們分別是操作系統所必須提供的命令處理器和工具程序。1992年IEEE通過了這個標准。ISO也已經通過了這個標准(ISO/IEC 9945-2:1993)

7. 1003.2d
改進的1003.2標准

8. 1003.5
一個相當於1003.1的Ada語言的API。在1992年,IEEE通過了這個標准。並在1997年對其進行了修訂。ISO也通過了該標准

9. 1003.5b
一個相當於1003.1b(實時擴展)的Ada語言的API。IEEE和ISO都已經通過了這個標准。ISO的標准是ISO/IEC 14519:1999

10. 1003.5c
一個相當於1003.1q(協議獨立接口)的Ada語言的API。在1998年,IEEE通過了這個標准。ISO也通過了這個標准。

11. 1003.9
一個相當於1003.1的FORTRAN語言的API。在1992年,IEEE通過了這個標准,並於1997年對其再次確認。ISO也已經通過了這個標准

12. 1003.10
一個應用於超級計算應用環境框架(Application Environment Profile,AEP)的標准。在1995年,IEEE通過了這個標准

13. 1003.13
一個關於應用環境框架的標准,主要針對使用POSIX接口的實時應用程序。在1998年,IEEE通過了這個標准 

14. 1003.22
一個針對POSIX的關於安全性框架的指南

15. 1003.23
一個針對用戶組織的指南,主要是為了指導用戶開發和使用支持操作需求的開放式系統環境(OSE)框架

16. 2003
針對指定和使用是否符合POSIX標准的測試方法,有關其定義、一般需求和指導方針的一個標准。在1997年,IEEE通過了這個標准

17. 2003.1
這個標准規定了針對1003.1的POSIX測試方法的提供商要提供的一些條件。在1992年,IEEE通過了這個標准

18. 2003.2
一個定義了被用來檢查與IEEE 1003.2(shell和工具API)是否符合的測試方法的標准。在1996年,IEEE通過了這個標准

0x3: POSIX標准的意義

POSIX的意義在於提供了"跨操作系統兼容性編譯"的能力,遵循了POSIX標准的C/C++程序源代碼,可以直接在Linux/BSD環境下用GCC編譯,或者在windows下用Cygwin/MinGW編譯(Cygwin、MinGW提供了跨操作系統的兼容編譯)。這叫跨操作系統的編譯,注意要和"跨平台交叉編譯"區分開來

Relevant Link:

http://zh.wikipedia.org/wiki/POSIX
http://i.linuxtoy.org/docs/guide/ch48s05.html

 

1. 交叉編譯 

0x1: 交叉編譯簡介

從編譯所在的平台和運行所在的平台這點來看,有兩種編譯概念

1. 本地編譯
我們常見的軟件開發,都是屬於"本地編譯"。在當前的PC下,x86的CPU下,直接編譯出來程序,可以運行的程序(或者庫文件),其可以直接在當前的環境,即x86的CPU下,當前電腦中,運行。
此時的編譯,可以叫做"本地編譯",即在當前目標平台下,編譯出來的程序,也只是放到當前平台下,就可以運行的

2. 交叉編譯
這是一個和本地編譯相對應的概念。而所謂的"交叉編譯",就是在一種平台上編譯,編譯出來的程序,是放到別的平台上運行
即編譯的環境,和運行的環境,不一樣,屬於交叉的,此所謂cross交叉編譯,這個概念,主要和嵌入式開發有關

一種最常見的例子就是:

在進行嵌入式開發時,手上有個嵌入式開發板,CPU是arm的,然后在x86的平台下開發,比如Ubuntu的Linux,或者是Win7。然后就需要在x86的平台上,(用交叉編譯器)去編譯你寫好的程序代碼,編譯生成的(可執行的)程序,是放
到目標開發板,arm的CPU上運行的 此所謂:在x86平台上編譯,在ARM平台上運行

交叉編譯,英文常寫作cross compile,也有其他寫法:crosscompile, cross compiling等

0x2: 為何要有交叉編譯

之所以要有交叉編譯,主要原因是:

1. 嵌入式系統中的資源太少
交叉編譯出來的程序,所要運行的目標環境中,各種資源,都相對有限,所以很難進行直接的本地編譯,最常見的情況是:
因為編譯,開發,都需要相對比較多的CPU,內存,硬盤等資源,而嵌入式開發上的那點資源,只夠嵌入式(Linux)系統運行的,沒太多剩余的資源,供你本地編譯。所以需要在別的平台上進行跨平台編譯,然后在其他的平台上運行

0x3: 跨平台編譯和跨操作系統編譯的差別

這里需要注意的是"平台"的概念,實際上包含兩個概念

1. 體系結構(Architecture): 同一個體系結構可以運行不同的操作系統
2. 操作系統(Operating System): 同一個操作系統也可以在不同的體系結構上運行

舉例來說,我們常說的x86 Linux平台實際上是Intel x86體系結構和Linux for x86操作系統的統稱;而x86 WinNT平台實際上是Intel x86體系結構和Windows NT for x86操作系統的簡稱

像crosstool-NG這類交叉編譯器和Cygwin這類跨操作系統平台編譯器的區別在於

1. crosstool-NG跨平台編譯(跨體系結構、操作系統)

2. Cygwin跨平台編譯(提供*inux到windows系統的代碼級編譯兼容性)

Relevant Link:

http://www.crifan.com/files/doc/docbook/cross_compile/release/html/cross_compile.html
http://zh.wikipedia.org/wiki/%E4%BA%A4%E5%8F%89%E7%B7%A8%E8%AD%AF%E5%99%A8
http://baike.baidu.com/view/650389.htm

 

2. Cygwin簡介

0x1: 簡介

Cygwin是許多自由軟件的集合,最初由Cygnus Solutions開發,用於各種版本的Microsoft Windows上,運行類UNIX系統。Cygwin的主要目的是通過"重新編譯"(注意:是重新編譯),將POSIX系統(例如Linux、BSD,以及其他Unix系統)上的軟件移植到Windows上。Cygwin移植工作在Windows NT上比較好,在Windows 95和Windows 98上,相對差勁一些。目前Cygwin由Red Hat等負責維護

首要需要明白的是,Cygwin不是一個跨平台模擬器,它不能讓我們把linux上編譯出來的程序在windows上運行(像wine那樣),而是一個跨平台的編譯器,也就是提供代碼級的跨操作系統兼容性,我們在linux下寫的符合POSIX標准的C程序可以在windows下面進行編譯,Cygwin提供了一套在windows下可以使用的Linux的API

Cygwin包括了一套庫,該庫在Win32系統下實現了POSIX系統調用的API。還有一套GNU開發工具集(比如GCC、GDB),這樣可以進行簡單的軟件開發。還有一些UNIX系統下的常見程序。2001年,新增了X Window System

0x2: Cygwin的特性

Cygwin is:
1. a large collection of GNU and Open Source tools which provide functionality similar to a Linux distribution on Windows.
2. a DLL (cygwin1.dll) which provides substantial POSIX API functionality.

Cygwin is not:
1. a way to run native Linux apps on Windows. You must rebuild your application from source if you want it to run on Windows.
2. a way to magically make native Windows apps aware of UNIX® functionality like signals, ptys, etc. Again, you need to build your apps from source if you want to take advantage 
of Cygwin functionality.

0x3: Cygwin原理

cygnus當初首先把GCC,GDB,GAS等開發工具進行了改進,使他們能夠生成並解釋win32的目標文件。然后,他們要把這些工具移植到windows平台上去。一種方案是基於win32 api對這些工具的源代碼進行大幅修改,這樣做顯然需要大量工作。因此,他們采取了一種不同的方法

1. 他們寫了一個共享庫(就是cygwin.dll),把win32 api中沒有的unix風格的調用(如fork、spawn、signals、select、sockets等)封裝在里面
2. 也就是說,他們基於win32 api寫了一個unix系統庫的模擬層(這個模擬層是一個關鍵,它的底層是win32 api,上層提供unix風格的調用,所以我們才可以在windows下編譯unix風格的C程序)
3. 這樣,只要把這些工具的源代碼和這個共享庫連接到一起,就可以使用unix主機上的交叉編譯器來生成可以在windows平台上運行的工具集
4. 以這些移植到windows平台上的開發工具為基礎,cygnus又逐步把其他的工具(幾乎不需要對源代碼進行修改,只需要修改他們的配置腳本)軟件移植到windows上來。這樣,在windows平台上運行bash和開發工具、用戶工具,感覺好
像在unix上工作

0x4: 使用Cygwin編程

code

#iuclude <stdio.h>

main()
{
    printf("hello world!!\n");
}

編譯

gcc hello.c -o hello.exe

得到hello.exe,這個程序可以在windows上直接點擊運行

Relevant Link:

http://zh.wikipedia.org/wiki/Cygwin
https://www.cygwin.com/
http://www.ibm.com/developerworks/cn/linux/l-cn-cygwin/

 

3. 靜態庫編譯及使用

0x1: 什么是庫

庫是寫好的現有的,成熟的,可以復用的代碼。現實中每個程序都要依賴很多基礎的底層庫,不可能每個人的代碼都從零開始,因此庫的存在是很有必要的
本質上來說庫是一種可執行代碼的二進制形式,可以被操作系統載入內存執行。庫有兩種

1. 靜態庫(.a、.lib)
2. 動態庫(.so、.dll)

所謂靜態、動態是指"鏈接"的過程存在區別

0x2: 動態庫和靜態庫的默認路徑PATH搜索順序

庫文件在連接(靜態庫和共享庫)和運行(僅限於使用共享庫的程序)時被使用,其搜索路徑是在系統中進行設置的

1. 靜態庫的搜索路徑順序
    1) /lib
    2) /usr/lib  
    3) /etc/ld.so.conf文件中添加庫的搜索路徑
    4) /etc/ld.so.conf.d下新建一個.conf文件,這種方法可以很靈活地將不同軟件的庫搜索路徑區分開來
2. 動態庫的搜索路徑順序
    1) LD_LIBRARY_PATH 
    2) /lib
    3) /usr/lib  
    4) /etc/ld.so.cache(使用ldconfig生成的庫路徑緩存)
    5) /etc/ld.so.conf文件中添加庫的搜索路徑
    6) /etc/ld.so.conf.d下新建一個.conf文件,這種方法可以很靈活地將不同軟件的庫搜索路徑區分開來

0x3: 靜態庫

之所以稱之為"靜態庫",是因為在鏈接階段,會將匯編生成的目標文件.o與引用到的庫一起鏈接打包到可執行文件中。因此對應的鏈接方式稱為靜態鏈接。
從本質上來說,一個靜態庫可以簡單看成是一組目標文件(.o/.obj文件)的集合,靜態庫與匯編生成的目標文件(.o/.obj)一起鏈接為可執行文件
靜態庫和.o文件格式相似。即很多目標文件經過壓縮打包后形成的一個文件

靜態庫特點總結:

1. 靜態庫對函數庫的鏈接是放在編譯時期完成的
2. 程序在運行時與函數庫再無瓜葛,移植方便,因為代碼已經嵌入到程序里面了,可以直接跟着程序走,不存在對外部文件的依賴
3. 浪費空間和資源,因為所有相關的目標文件與牽涉到的函數庫被鏈接合成一個可執行文件,會增加原本程序的空間

0x4: 靜態庫編程

我們接下來學習一下如何創建用於C++應用的靜態庫(一個.lib 文件)。 使用靜態庫是重用代碼的一種絕佳方式。 你不必在要求功能的每個應用中重新實現同一例程,而只需將其寫入靜態庫一次,然后從應用引用它們即可。 從靜態庫鏈接的代碼成為了應用的一部分,這樣你就不必安裝另一個文件來使用代碼。

1. VS編譯、使用靜態庫

//創建靜態庫項目
1. 在菜單欄上,依次選擇"文件""新建""項目"
2. 在"新建項目"對話框的左窗格中,依次展開"已安裝""模板""Visual C++",然后選擇"Win32"
3. 在中間窗格中,選擇"Win32 控制台應用程序"
4. 在"名稱"框中為項目指定名稱,例如 MathFuncsLib。 在"解決方案名稱"框中為解決方案指定名稱,例如 StaticLibrary。 選擇"確定"按鈕
5. 在"Win32 應用程序向導"對話框的"概述"頁上,選擇"下一步"按鈕
6. 在"應用程序設置"頁的"應用程序類型"下,選擇"靜態庫"
7. 在"應用程序設置"頁的"附加選項"下,清除"預編譯頭"復選框
8. 選擇"完成"按鈕創建項目

MathFuncsLib.h

// MathFuncsLib.h
#ifndef MATHFUNCSLIB_H  
#define MATHFUNCSLIB_H  
  
namespace MathFuncs
{
    class MyMathFuncs
    {
    public:
        // Returns a + b
        static double Add(double a, double b);

        // Returns a - b
        static double Subtract(double a, double b);

        // Returns a * b
        static double Multiply(double a, double b);

        // Returns a / b
        static double Divide(double a, double b);
    };
}  
  
#endif  

MathFuncsLib.cpp

// MathFuncsLib.cpp
// compile with: cl /c /EHsc MathFuncsLib.cpp
// post-build command: lib MathFuncsLib.obj

#include "MathFuncsLib.h"

#include <stdexcept>

using namespace std;

namespace MathFuncs
{
    double MyMathFuncs::Add(double a, double b)
    {
        return a + b;
    }

    double MyMathFuncs::Subtract(double a, double b)
    {
        return a - b;
    }

    double MyMathFuncs::Multiply(double a, double b)
    {
        return a * b;
    }

    double MyMathFuncs::Divide(double a, double b)
    {
        return a / b;
    }
}

編譯靜態庫文件

//編譯此靜態庫
1. 在菜單欄上依次選擇"生成""生成解決方案"
2. 這將創建一個可供其他程序使用的靜態庫

vs是windows操作系統下的編譯平台,通過vs編譯得到的.lib靜態庫只能在windows的程序代碼中使用,主要是編譯器、匯編器和連接器的不同,因此二者庫的二進制是不兼容的。文章之后會學習到如何將linux下編譯的靜態庫通過跨平台編譯鏈接到windows的程序代碼中

要在其他程序中使用靜態庫中的功能,必須引用靜態庫才能使用其中的例程

//創建引用靜態庫的 C++ 控制台應用
1. 在菜單欄上,依次選擇"文件""新建""項目"2. 在左窗格中的"Visual C++"下,選擇"Win32"3. 在中間窗格中,選擇"Win32 控制台應用程序"4. 在"名稱"框中為項目指定名稱,例如 MyExecRefsLib。 在"解決方案"旁的下拉列表中選擇"添加到解決方案"。 這會將新項目添加到包含此靜5. 態庫的解決方案。 選擇"確定"按鈕。
6. 在"Win32 應用程序向導"對話框的"概述"頁上,選擇"下一步"按鈕。
7. 在"應用程序設置"頁的"應用程序類型"下,選擇"控制台應用程序"8. 在"應用程序設置"頁的"附加選項"下,清除"預編譯頭"復選框。
9. 選擇"完成"按鈕創建項目。
 
//在應用中使用靜態庫的功能
1. 在創建一個控制台應用程序后,一個空的程序已經為你創建好了。 源文件的名稱與你之前選擇的名稱相同。 在此示例中,源文件名為 MyExecRefsLib.cpp。
2. 必須引用靜態庫才能使用其中的算術例程。 為此,請在"解決方案資源管理器"中打開 MyExecRefsLib 項目的快捷菜單,然后選擇"引用"。 在 MyExecRefsLib"屬性頁"對話框中,展開"通用屬性"節點,選擇"框架和引用",然后
選擇"添加新引用"按鈕。 有關"引用"對話框的更多信息,請參見"<Projectname> 屬性頁"對話框 ->"通用屬性"->"框架和引用"3. "添加引用"對話框列出了可以引用的庫。 "項目"選項卡列出了當前解決方案中的所有項目以及它們包含的所有庫。 在"項目"選項卡上,選中"MathFuncsLib"復選框,然后選擇"確定"按鈕。 4. 若要引用 MathFuncsLib.h 頭文件,必須修改包含的目錄路徑。 在 MyExecRefsLib"屬性頁"對話框中,依次展開"配置屬性"節點和"C/C++"節點,然后選擇"常規"。 在"附加包含目錄"旁,指定 MathFuncsLib 目錄的路徑或
瀏覽至該目錄。
5. 若要瀏覽至目錄路徑,請打開屬性值下拉列表框,然后選擇"編輯"。 在"附加包含目錄"對話框中,在文本框中選擇一個空行,然后選擇行尾的省略號按鈕 (…)。 在"選擇目錄"對話框中,選擇 MathFuncsLib 目錄,然后選擇"選擇
文件夾
"按鈕以保存所做選擇並關閉對話框。 在"附加包含目錄"對話框中,選擇"確定"按鈕,然后在"屬性頁"對話框中,選擇"確定"按鈕以保存對該項目進行的更改。

MyExecRefsLib.cpp

// MyExecRefsLib.cpp
// compile with: cl /EHsc MyExecRefsLib.cpp /link MathFuncsLib.lib

#include <iostream>

#include "MathFuncsLib.h"

using namespace std;

int main()
{
    double a = 7.4;
    int b = 99;

    cout << "a + b = " <<
        MathFuncs::MyMathFuncs::Add(a, b) << endl;
    cout << "a - b = " <<
        MathFuncs::MyMathFuncs::Subtract(a, b) << endl;
    cout << "a * b = " <<
        MathFuncs::MyMathFuncs::Multiply(a, b) << endl;
    cout << "a / b = " <<
        MathFuncs::MyMathFuncs::Divide(a, b) << endl;

    return 0;
}

Relevant Link:

http://msdn.microsoft.com/zh-cn/library/ms235627.aspx#BKMK_CreateLibProject

2. GCC編譯、使用靜態庫

靜態庫的后綴是.a(並沒有強制規定),它的產生分兩步

1. 由源文件編譯生成一堆.o,每個.o里都包含這個編譯單元的符號表
2. ar命令將很多.o轉換成.a,成為靜態庫,從這點也可以看出來,庫是很多.o文件的集合

在linux下,庫文件一般放在/usr/lib和/lib下
靜態庫的名字一般為libxxxx.a,其中xxxx是該lib的名稱
動態庫的名字一般為libxxxx.so.major.minor,xxxx是該lib的名稱,major是主版本號,minor是副版本號(如果庫的命名不遵循 libXXXXX.a的格式就找不到相應文件)

ldd命令可以查看一個可執行程序依賴的共享庫 
ldd /bin/ping
    linux-gate.so.1 =>  (0x006cd000)
    libidn.so.11 => /lib/libidn.so.11 (0x005d6000)
    libc.so.6 => /lib/libc.so.6 (0x00927000)
    /lib/ld-linux.so.2 (0x005ac000)

首先,我們先完成函數庫(靜態庫的代碼)的編碼

hello.h: 函數庫(靜態庫)的頭文件

#ifndef HELLO_H
#define HELLO_H

void hello(const char* name);

#endif

hello.c: 函數庫的實現代碼

#include <stdio.h>

void hello(const char* name)
{
    printf("Hello%s!\n", name);
}

現在,我們可以將當前的代碼編譯為靜態庫文件,需要注意的,靜態庫和可執行在本質上都是可執行代碼,但是靜態庫沒有main主程序,所以不能獨立運行,需要被引入到別的程序中進行運行

//將代碼編譯為對象文件.o
gcc -c hello.c
//將.o鏈接為靜態庫文件
ar rcs libhello.a hello.o

編譯好靜態庫文件之后,我們就可以在其他程序中使用靜態庫文件中的函數了

1. 只需要在使用到這些公用函數的源程序中包含這些公用函數的原型聲明(include對應的頭文件)
2. 然后在用gcc命令生成目標文件時指明靜態庫名
3. gcc將會從靜態庫中將公用函數連接到目標文件中
4. 注意,gcc會在靜態庫名前加上前綴lib,然后追加擴展名.a得到的靜態庫文件名來查找靜態庫文件,因此,我們在寫需要連接的庫時,只寫名字就可以,如libhello.a的庫,只寫: -lhello

main.c: 調用靜態庫的程序代碼

#include "hello.h"

int main()
{
    hello("LittleHann");
    return 0;
}

編譯

gcc -o hello main.c -L. -lhello

關於gcc的編譯指令,請參閱另一篇文章

http://www.cnblogs.com/LittleHann/p/3855905.html

Relevant Link:

http://wenku.baidu.com/view/7d8602b265ce050877321301.html

 

4. 動態庫編譯及使用

0x1: 動態庫

動態庫文件名命名規范和靜態庫文件名命名規范類似,也是在動態庫名增加前綴lib,但其文件擴展名為.so。例如:我們將創建的動態庫名為myhello,則動態庫文件名就是libmyhello.so。

接下來我們繼續學習如何創建用於 C++ 應用程序的動態鏈接庫 (DLL)。 使用庫是重用代碼的一種絕佳方式。 您不必在自己創建的每個程序中重新實現同一例程,而只需對這些例程寫入一次,然后從需要該功能的應用程序引用它們即可。 通過將代碼放入 DLL,您節省在引用它的每個應用程序的空間,而且,您可以更新 DLL,而無需重新編譯所有應用程序

0x2: 動態庫編程

1. VS編譯、使用靜態庫

//創建動態鏈接庫 (DLL) 項目
1. 在菜單欄上,依次選擇"文件""新建""項目"2. 在"新建項目"對話框的左窗格中,依次展開"已安裝""模板""Visual C++",然后選擇"Win32"3. 在中間窗格中,選擇"Win32 控制台應用程序"4. 在"名稱"框中為項目指定名稱,例如,MathFuncsDll。 在"解決方案名稱"框中為解決方案指定一個名稱,例如 DynamicLibrary。 選擇"確定"按鈕。
5. 在"Win32 應用程序向導"對話框的"概述"頁上,選擇"下一步"按鈕。
6. 在"應用程序設置"頁上的"應用程序類型"下,選擇"DLL"7. 選擇"完成"按鈕創建項目。

編寫動態庫DLL的頭文件,MathFuncsDll.h

// MathFuncsDll.h

#ifndef MATHFUNCSDll_H
#define MATHFUNCSDll_H

/*
當定義了 MATHFUNCSDLL_EXPORTS 符號時,MATHFUNCSDLL_API 符號將在此代碼中的成員函數聲明中設置 __declspec(dllexport) 修飾符,此修飾符使函數能作為 DLL 導出,以供其他應用程序調用
當 MATHFUNCSDLL_EXPORTS 未定義時,MATHFUNCSDLL_API 會在成員函數聲明中定義 __declspec(dllimport) 修飾符。 此修飾符能夠使編譯器優化從 DLL 導入的用於其他應用程序的函數
默認情況下,生成 MathFuncsDll 項目時會定義 MATHFUNCSDLL_EXPORTS
*/
#ifdef MATHFUNCSDLL_EXPORTS
    #define MATHFUNCSDLL_API __declspec(dllexport) 
#else
    #define MATHFUNCSDLL_API __declspec(dllimport) 
#endif

namespace MathFuncs
{
    // This class is exported from the MathFuncsDll.dll
    class MyMathFuncs
    {
    public: 
        // Returns a + b
        static MATHFUNCSDLL_API double Add(double a, double b); 

        // Returns a - b
        static MATHFUNCSDLL_API double Subtract(double a, double b); 

        // Returns a * b
        static MATHFUNCSDLL_API double Multiply(double a, double b); 

        // Returns a / b
        // Throws const std::invalid_argument& if b is 0
        static MATHFUNCSDLL_API double Divide(double a, double b); 
    };
}

#endif

編寫動態庫DLL的函數實現的.cpp文件

// MathFuncsDll.cpp : Defines the exported functions for the DLL application.
//

#include "stdafx.h"
#include "MathFuncsDll.h"
#include <stdexcept>

using namespace std;

namespace MathFuncs
{
    double MyMathFuncs::Add(double a, double b)
    {
        return a + b;
    }

    double MyMathFuncs::Subtract(double a, double b)
    {
        return a - b;
    }

    double MyMathFuncs::Multiply(double a, double b)
    {
        return a * b;
    }

    double MyMathFuncs::Divide(double a, double b)
    {
        if (b == 0)
        {
            throw invalid_argument("b cannot be zero!");
        }

        return a / b;
    }
}

編譯后可以得到一個.dll文件

編譯得到一個dll文件后,我們就在其他的程序代碼中去引入這個dll文件,並使用其中的函數功能了

//創建引用 DLL 的應用程序
1. 為了創建一個項目引用你剛剛創建好的DLL,在菜單欄中選擇 文件>新建>項目。
2. 在左窗格中的"Visual C++"下,選擇"Win32"3. 在中間窗格中,選擇"Win32 控制台應用程序"4. 在"名稱"框中為項目指定名稱,例如,MyExecRefsDll。 從"解決方案"旁邊的下拉列表中選擇"添加到解決方案"。 這會將新項目添加到包含 DLL 的同一個解決方案中。 選擇"確定"按鈕。
5. 在"Win32 應用程序向導"對話框的"概述"頁上,選擇"下一步"按鈕。
6. 在"應用程序設置"頁的"應用程序類型"下,選擇"控制台應用程序"7. 在"應用程序設置"頁的"附加選項"下,清除"預編譯頭"復選框。
8. 選擇"完成"按鈕創建項目。

//在應用程序中使用類庫的功能
1. 在創建一個控制台應用程序后,一個空的程序已經為你創建好了。 源文件的名稱與你之前選擇的名稱相同。 在本示例中,名為"MyExecRefsDll.cpp"2. 若要使用您 DLL 中創建的算術例程,必須引用 DLL。 為此,請在 解決方案資源管理器 中選擇 MyExecRefsDll 項目,然后在菜單欄上,選擇 項目,引用。 在"屬性頁"對話框中,展開"通用屬性"節點,選擇"框架和引用",然
后選擇"添加新引用"按鈕。 有關"引用"對話框的更多信息,請參見"<Projectname> 屬性頁"對話框 ->"通用屬性"->"框架和引用"3. "添加引用"對話框列出了可以引用的庫。 "項目"選項卡列出了當前解決方案中的所有項目,以及它們包含的所有庫。 在"項目"選項卡上,選中"MathFuncsDll"旁邊的復選框,然后選中"確定"按鈕。 4. 若要引用 DLL 的頭文件,必須修改包含的目錄路徑。 為此,請在"屬性頁"對話框中展開"配置屬性"節點,然后展開"C/C++"節點,並選擇"常規"。 在"附加包含目錄"旁邊,指定 MathFuncsDll.h 頭文件所在位置的路徑。 可以
使用相對路徑(例如 ..\MathFuncsDll\),然后選擇"確定"按鈕。 5. 現在即可在此應用程序中使用 MyMathFuncs 類。 使用以下代碼替換""的內容:

MyExecRefsDll.cpp

// MyExecRefsDll.cpp
// compile with: /EHsc /link MathFuncsDll.lib

#include <iostream>

#include "MathFuncsDll.h"

using namespace std;

int main()
{
    double a = 7.4;
    int b = 99;

    cout << "a + b = " <<
        MathFuncs::MyMathFuncs::Add(a, b) << endl;
    cout << "a - b = " <<
        MathFuncs::MyMathFuncs::Subtract(a, b) << endl;
    cout << "a * b = " <<
        MathFuncs::MyMathFuncs::Multiply(a, b) << endl;
    cout << "a / b = " <<
        MathFuncs::MyMathFuncs::Divide(a, b) << endl;

    try
    {
        cout << "a / 0 = " <<
            MathFuncs::MyMathFuncs::Divide(a, 0) << endl; 
    }
    catch (const invalid_argument &e) 
    {
        cout << "Caught exception: " << e.what() << endl; 
    }

    return 0;
}

2. GCC編譯、使用靜態庫

foo.h:

#ifndef foo_h__
#define foo_h__
 
extern void foo(void);
 
#endif  // foo_h__

foo.c:

#include <stdio.h>
 
void foo(void)
{
    puts("Hello LittleHann, I'm a shared library");
}

編譯動態庫文件.so

gcc -shared -Wall -Werror -fpic -o libfoo.so foo.c

編譯好動態庫文件之后,我們就可以在其他程序中引入這個動態庫文件.so,並使用其中的導出函數

main.c:

#include <stdio.h>
#include "foo.h"
 
int main(void)
{
    puts("This is a shared library test...");
    foo();
    return 0;
}

在main.c中引入了foo.h頭文件

gcc -L. -Wall -o test main.c -lfoo

編譯成功后,還有一件很重要的事,我們回想一下Linux下靜態庫、動態庫的默認搜索順序

1) LD_LIBRARY_PATH 
2) /lib
3) /usr/lib  
4) /etc/ld.so.cache(使用ldconfig生成的庫路徑緩存)
5) /etc/ld.so.conf文件中添加庫的搜索路徑
6) /etc/ld.so.conf.d下新建一個.conf文件,這種方法可以很靈活地將不同軟件的庫搜索路徑區分開來

linux是默認不會去搜索當前目錄的,所以我們必須將.so文件復制到默認路徑下、或者使用LD_LIBRARY_PATH顯示指定

cp libfoo.so /usr/lib
./test
rm -f /usr/lib/libfoo.so

Relevant Link:

http://msdn.microsoft.com/zh-cn/library/ms235636.aspx
http://www.cprogramming.com/tutorial/shared-libraries-linux-gcc.html

 

5. MinGW簡介

0x1: MinGW是什么

MinGW(Minimalist GNU for Windows),又稱mingw32,是將GCC編譯器和GNU Binutils移植到Win32平台下的產物,包括一系列頭文件(Win32API)、庫和可執行文件

GCC支持的語言大多在MinGW也受支持,其中涵蓋

1. C 
2. Objective-C
3. Fortran
4. Ada
5. 對於C語言之外的語言,MinGW使用標准的GNU運行庫,如C++使用GNU libstdc++ 

但是MinGW使用Windows中的C運行庫。因此用MinGW開發的程序不需要額外的第三方DLL支持就可以直接在Windows下運行,而且也不一定必須遵從GPL許可證。這同時造成了MinGW開發的程序只能使用Win32API和跨平台的第三方庫,而缺少POSIX支持 ,大多數GNU軟件無法在不修改源代碼的情況下用MinGW編譯

GCC是一個原本用於Unix系統下編程的編譯器。不過,現在GCC也有了許多Win32下的移植版本,目前GCC在windows下有三個移植版本

1. MinGW
2. Cygwin
3. Djgpp

MinGW是Minimalistic GNU for Windows 的縮寫。它是一個建立在GCC和binutils 項目上的編譯器系統。和其他GCC的移植版相比,它可以說是最接近Win32的一個了。因為,MinGW幾乎支持所有的Win32 API,這也是MinGW的特色之一。它所連接的程序,不需要任何第三方庫就可以運行了。在某種程度上看,MinGW更像是VC的替代品

0x3: MinGW的Linker參數

0x3: 編程示例

Relevant Link:

http://www.mingw.org/
https://code.google.com/p/msys-cn/wiki/ChapterThree
http://wenku.baidu.com/view/c6f71522af45b307e87197a6.html

6. CodeBlocks簡介

Relevant Link:

http://bbs.chinaunix.net/thread-3640636-1-1.html
http://zh.wikipedia.org/wiki/Code::Blocks
http://blog.csdn.net/wtfmonking/article/details/17487705

 

Copyright (c) 2014 LittleHann All rights reserved

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM