代碼倉庫在此,歡迎進(jìn)行生物工程:
GCC - GNU Compiler Collection
- MinGW
- GCC 參數(shù)詳解
- GCC 5 Release Series
- GCC - the GNU Compiler Collection
- mingw-w64 GCC for Windows 64 & 32 bits
- GCC Invocation
GNU 編譯器套件 GNU Compiler Collection 包括 C、C++诫惭、Objective-C初肉、Fortran、Java、Ada 和 Go 語言的前端彼念,也包括了這些語言的庫屡律,如 libstdc++腌逢、libgcj 等等。GCC 的初衷是為GNU操作系統(tǒng)專門編寫的一款編譯器超埋。GNU 系統(tǒng)是徹底的自由軟件搏讶。此處,自由的含義是它尊重用戶的自由霍殴。
此外媒惕,TCC - Tiny C Compiler 是一個(gè)小巧的編譯器,用來研究編譯原理是不錯(cuò)的目標(biāo)来庭。
MinGW 就是 GCC 的 Windows 移植版吓笙。
MinGW - Minimalist GNU on Windows 是將經(jīng)典的開源 C/C++ 語言編譯器 GCC 移植到了 Windows 平臺(tái)下,并且包含了 Win32API 巾腕,因此可以將源代碼編譯為可在 Windows 中運(yùn)行的可執(zhí)行程序面睛。而且還可以使用一些 Windows 不具備的,Linux 平臺(tái)下的開發(fā)工具尊搬。
MinGW 包含 32-bit 和 64-bit 兩種叁鉴,MinGW-w64 可以編譯生成 64-bit 或 32-bit 可執(zhí)行程序,使用 -m32
選項(xiàng)佛寿。
正因?yàn)槿绱嘶夏梗琈inGW 32-bit 版本現(xiàn)已被 MinGW-w64 所取代,且 MinGW 也早已停止了更新冀泻,內(nèi)置的 GCC 停滯在了 4.8.1 版本常侣,而 MinGW-w64 內(nèi)置的 GCC 則持續(xù)更新。
使用 MinGW-w64 的優(yōu)勢(shì):
- MinGW-w64 是開源軟件弹渔,可以免費(fèi)使用胳施。
- MinGW-w64 由一個(gè)活躍的開源社區(qū)在持續(xù)維護(hù),因此不會(huì)過時(shí)肢专。
- MinGW-w64 支持最新的 C/C++ 語言標(biāo)準(zhǔn)舞肆。
- MinGW-w64 使用 Windows 的 C 語言運(yùn)行庫焦辅,因此,可以編譯出無 DLL 依賴的 Windows 程序椿胯。
- 許多開源 IDE 集成 MinGW-w64筷登,如 CodeBlocks,使它擁有友好的圖形化界面哩盲。
MinGW-w64 是穩(wěn)定可靠的前方、持續(xù)更新的 C/C++ 編譯器,使用它可以免去很多麻煩廉油,不用擔(dān)心跟不上時(shí)代镣丑,也不用擔(dān)心編譯器本身有bug,可以放心的去編寫程序娱两。
GCC 有多個(gè) Windows 移植版本,比較出名的就是 MinGW 和 TDM-GCC:
- MinGW:http://www.mingw.org/
- TDM-GCC: http://tdm-gcc.tdragon.net/download
- Cygwin:http://www.cygwin.com/
GCC 環(huán)境變量:
變量名 | 功能 |
---|---|
CPATH |
搜索目錄列表金吗,也可以使用命令選項(xiàng)十兢,如 -I. -I/special/include |
C_INCLUDE_PATH |
搜索目錄列表,分隔符號(hào)由 PATH_SEPARATOR 變量指定摇庙,通常是分號(hào)或冒號(hào) |
CPLUS_INCLUDE_PATH |
搜索目錄列表 |
OBJC_INCLUDE_PATH |
搜索目錄列表 |
DEPENDENCIES_OUTPUT |
非系統(tǒng)依賴的輸出旱物,相當(dāng)命令選項(xiàng) -MM、-MT 和 -MF 結(jié)合 |
SUNPRO_DEPENDENCIES |
類似 DEPENDENCIES_OUTPUT卫袒,除了系統(tǒng)頭文件不被忽略宵呛,相當(dāng) -M 選項(xiàng) |
GCC 命令的常用選項(xiàng):
選項(xiàng) | 解釋 |
---|---|
-ansi | 只支持 ANSI 標(biāo)準(zhǔn)的 C 語法。這一選項(xiàng)將禁止 GNU C 的某些特色夕凝, 例如 asm 或 typeof 關(guān)鍵詞宝穗。 |
-c | 只編譯并生成目標(biāo)文件。 |
-DMACRO | 以字符串"1"定義 MACRO 宏码秉。 |
-DMACRO | DEFN 以字符串"DEFN"定義 MACRO 宏逮矛。 |
-E | 只運(yùn)行 C 預(yù)編譯器。 |
-g | 生成調(diào)試信息转砖。GNU 調(diào)試器可利用該信息须鼎。 |
-IDIRECTORY | 指定 DIRECTORY 為額外的頭文件搜索路徑。 |
-LDIRECTORY | 指定 DIRECTORY 為額外的函數(shù)庫搜索路徑府蔗。 |
-lLIBRARY | 連接時(shí)搜索指定的函數(shù)庫LIBRARY晋控。 |
-m486 | 針對(duì) 486 進(jìn)行代碼優(yōu)化。 |
-o | FILE 生成指定的輸出文件姓赤。用在生成可執(zhí)行文件時(shí)赡译。 |
-O0 | 不進(jìn)行優(yōu)化處理。 |
-O | 或 -O1 優(yōu)化生成代碼不铆。 |
-O2 | 進(jìn)一步優(yōu)化捶朵。 |
-O3 | 比 -O2 更進(jìn)一步優(yōu)化蜘矢,包括 inline 函數(shù)。 |
-shared | 生成共享目標(biāo)文件综看。通常用在建立共享庫時(shí)品腹。 |
-static | 禁止使用共享連接。 |
-UMACRO | 取消對(duì) MACRO 宏的定義红碑。 |
-w | 不生成任何警告信息舞吭。 |
-Wall | 生成所有警告信息。 |
DLL with MinGW
在 Windows 下用 MinGW 編譯 DLL:
/* add_basic.c
Demonstrates creating a DLL with an exported function, the inflexible way.
*/
__declspec(dllexport) int __cdecl Add(int a, int b)
{
return (a + b);
}
只需要添加 -shared
鏈接選項(xiàng):
>gcc -c -o add_basic.o add_basic.c
>gcc -o add_basic.dll -s -shared add_basic.o -Wl,--subsystem,windows
以上分步演示了編譯和鏈接兩個(gè)過程析珊,但是 GCC 可以一步執(zhí)行:
gcc -o add_basic.dll -s -shared add_basic.c -Wl,--subsystem,windows
其中 -Wl,--subsystem,windows
不是必要的參數(shù)羡鸥,因?yàn)椴皇蔷幾g窗口程序。注意 -s
選項(xiàng)忠寻,它清理導(dǎo)出的 DLL 符號(hào)惧浴,通過在發(fā)布 DLL 時(shí)使用。
對(duì)于動(dòng)態(tài)鏈接庫奕剃,用戶在程序中使用時(shí)衷旅,為了程序能正確鏈接,就需要導(dǎo)入庫
Import Library纵朋,即鏈接程序中使用的 .lib
文件柿顶。
下面,試著寫一個(gè)程序來調(diào)用動(dòng)態(tài)鏈接庫的 Add(a, b) 方法:
/* addtest_basic.c
Demonstrates using the function imported from the DLL, the inelegant way.
*/
#include <stdlib.h>
#include <stdio.h>
/* Declare imported function so that we can actually use it. */
__declspec(dllimport) int __cdecl Add(int a, int b);
int main(int argc, char** argv)
{
printf("%d\n", Add(6, 23));
return EXIT_SUCCESS;
}
現(xiàn)在操软,執(zhí)行編譯鏈接:
>gcc -c -o addtest_basic.o addtest_basic.c
>gcc -o addtest_basic.exe -s addtest_basic.o -L. -ladd_basic
>addtest_basic.exe
其它 DLL 使用的高級(jí)知識(shí)點(diǎn):
- Displaying functions exported from a DLL.
- The DllMain function.
- Using a module definition file.
- Exporting undecorated stdcall functions.
- Exporting C++ functions and variables from a DLL.
- Creating JNI DLLs.
- P/Invoking MinGW DLLs in .NET
- Setting the DLL base address.
- Loading and unloading DLLs at runtime.
Dll Information
使用 GNU binutils objdump 查看 DLL 導(dǎo)出函數(shù)符號(hào):
>objdump -p AddLib.dll
There is an export table in .edata at 0x6da46000
The Export Tables (interpreted .edata section contents)
Export Flags 0
Time/Date stamp 4da9a500
Major/Minor 0/0
Name 00006046 AddLib.dll
Ordinal Base 1
Number in:
Export Address Table 00000003
[Name Pointer/Ordinal] Table 00000003
Table Addresses
Export Address Table 00006028
Name Pointer Table 00006034
Ordinal Table 00006040
Export Address Table -- Ordinal Base 1
[ 0] +base[ 1] 1280 Export RVA
[ 1] +base[ 2] 2004 Export RVA
[ 2] +base[ 3] 2000 Export RVA
[Ordinal/Name Pointer] Table
[ 0] Add
[ 1] bar
[ 2] foo
>dumpbin -exports AddLib.dll
Microsoft (R) COFF/PE Dumper Version 9.00.30729.01
Copyright (C) Microsoft Corporation. All rights reserved.
Dump of file AddLib.dll
File Type: DLL
Section contains the following exports for AddLib.dll
00000000 characteristics
4DA9A500 time date stamp Sat Apr 16 15:17:36 2011
0.00 version
1 ordinal base
3 number of functions
3 number of names
ordinal hint RVA name
1 0 00001280 Add
2 1 00002004 bar
3 2 00002000 foo
Summary
1000 .CRT
1000 .bss
1000 .data
1000 .edata
1000 .eh_fram
1000 .idata
1000 .rdata
1000 .reloc
1000 .rsrc
1000 .text
1000 .tls
The DllMain function.
DllMain 是 DLL 入口函數(shù)嘁锯,在加載或卸載時(shí)被系統(tǒng)調(diào)用:
#include <windows.h>
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
switch (fdwReason)
{
case DLL_PROCESS_ATTACH:
/* Code path executed when DLL is loaded into a process's address space. */
break;
case DLL_THREAD_ATTACH:
/* Code path executed when a new thread is created within the process. */
break;
case DLL_THREAD_DETACH:
/* Code path executed when a thread within the process has exited *cleanly*. */
break;
case DLL_PROCESS_DETACH:
/* Code path executed when DLL is unloaded from a process's address space. */
break;
}
return TRUE;
}
Using a module definition file.
除了 __declspec(dllexport)
標(biāo)記一個(gè)導(dǎo)出函數(shù),更方便的做法是使用模塊定義文件 module definition file聂薪,它可以定義 DLL 中導(dǎo)出的變量家乘、函數(shù)等等,如下 AddLib.def
:
LIBRARY AddLib.dll
EXPORTS
Add
foo
bar
對(duì)應(yīng)的 C 文件頭:
/* Define calling convention in one place, for convenience. */
#define ADDCALL __cdecl
/* Make sure functions are exported with C linkage under C++ compilers. */
#ifdef __cplusplus
extern "C"
{
#endif
/* Declare our Add function using the above definitions. */
int ADDCALL Add(int a, int b);
/* Exported variables. */
extern int foo;
extern int bar;
#ifdef __cplusplus
} // __cplusplus defined.
#endif
頭文件中的導(dǎo)出變量藏澳、函數(shù)依然使用了 extern 關(guān)鍵字烤低,確保它們?cè)谑褂弥心苷_鏈接,函數(shù)實(shí)現(xiàn)代碼如下:
#include "add.h"
int ADDCALL Add(int a, int b)
{
return (a + b);
}
/* Assign value to exported variables. */
int foo = 7;
int bar = 41;
在編譯鏈接命令中使用模塊定義文件 AddLib.def
:
>gcc -O3 -std=c99 -Wall -c add.c -o add.o
>gcc -o AddLib.dll add.o AddLib.def -shared -s -Wl,--subsystem,windows,--out-implib,libaddlib.a
Exporting Undecorated stdcall Functions
導(dǎo)出函數(shù)意味著 stdcall 調(diào)用轉(zhuǎn)換笆载,即 int Add(int, int)
這樣的函數(shù)簽名會(huì)導(dǎo)出變成 Add@8
類似格式扑馁,@
符號(hào)后面跟著的數(shù)字表示參數(shù)占據(jù)的空間,而 Microsoft’s Visual C++ 還會(huì)使用其它前綴凉驻,如下劃線 _Add@8
腻要。正因?yàn)?MSVC 和 MinGW 不同編譯器之間的轉(zhuǎn)換不一致,當(dāng)開發(fā)出來的 DLL 被多用戶使用時(shí)涝登,他們使用什么編譯器就受到約束了雄家。
解決辦法就是避免導(dǎo)出時(shí),編譯器對(duì)函數(shù)的重命名胀滚,傳遞 --kill-at
選項(xiàng)給鏈接程序趟济,同時(shí)乱投,需要重建導(dǎo)入庫 import library,否則用戶不能正確鏈接特殊處理過的導(dǎo)出函數(shù)顷编。此時(shí)戚炫,--out-implib
創(chuàng)建的導(dǎo)入庫無效,需要使用 dlltool.exe
工具媳纬,還有模塊定義文件双肤,它包含了函數(shù)正確的導(dǎo)出名字:
>gcc -o AddLib.dll add.o -shared -s -Wl,--subsystem,windows,--output-def,AddLib.def
>gcc -o AddLib.dll add.o -shared -s -Wl,--subsystem,windows,--kill-at
>dlltool --kill-at -d AddLib.def -D AddLib.dll -l libaddlib.a
上面的命令首先會(huì)創(chuàng)建修飾過函數(shù)名稱的 DLL,使用了 --output-def,AddLib.def
鏈接參數(shù)生成模塊定義文件钮惠,它包含了修飾過的函數(shù)名稱茅糜。
第二步還是創(chuàng)建 DLL,但是傳入了 --kill-at
鏈接參數(shù)素挽,導(dǎo)出的函數(shù)名是未修飾過的蔑赘,這一步不能創(chuàng)建模塊定義文件。
最后预明,基于模塊定義文件創(chuàng)建導(dǎo)入庫缩赛,如果你關(guān)心不同編譯器的表現(xiàn),這一步會(huì)很有趣贮庞。事實(shí)上,Win32 API 函數(shù)都是以這種方式導(dǎo)出的究西,沒有任何修飾窗慎。
Exporting C++ functions and variables
在 C++ DLL 的導(dǎo)出符號(hào)中,不同編譯器之間是不通用的卤材,甚至同一個(gè)編譯器不同版本也不通用遮斥。因?yàn)?C++ 的復(fù)雜性,要處理異常扇丛、虛函數(shù)實(shí)現(xiàn)术吗、或 STL 類型的不同內(nèi)存模型等等。為了明確不兼容帆精,編譯器還會(huì)使用名稱變形 name mangling
來處理導(dǎo)出符號(hào)较屿。
導(dǎo)出全局符號(hào),函數(shù)和變量卓练,C/C++ 的做法都是一樣的隘蝎,不同的是 C 語言導(dǎo)出全局變量時(shí),可以作為 C++ 對(duì)象實(shí)例導(dǎo)出襟企,導(dǎo)出函數(shù)時(shí)可以重載嘱么。還可以導(dǎo)出 C++ 的類對(duì)象,這個(gè)導(dǎo)出的類對(duì)象所有靜態(tài)方法和成員不區(qū)分 public顽悼、protected曼振、private 訪問修飾几迄。
示例 Point 頭文件:
#ifndef POINT_HPP
#define POINT_HPP
#ifdef POINT_EXPORTS
#define POINTAPI __declspec(dllexport)
#else
#define POINTAPI __declspec(dllimport)
#endif
#include <ostream>
using std::ostream;
class POINTAPI Point
{
public:
// Constructors.
Point();
Point(int x, int y);
// Getters and setters.
int getX() const;
int getY() const;
void setX(int x);
void setY(int y);
// Friend the overloaded operators, so they can access private Point data.
friend Point operator+(const Point& lhs, const Point& rhs);
friend ostream& operator<<(ostream& os, const Point& pt);
private:
int x, y;
};
// Overloaded operators.
POINTAPI Point operator+(const Point& lhs, const Point& rhs);
POINTAPI ostream& operator<<(ostream& os, const Point& pt);
extern POINTAPI Point foo;
extern POINTAPI Point bar;
#endif
實(shí)現(xiàn)代碼:
#include "point.hpp"
Point::Point()
: x(0), y(0)
{ }
Point::Point(int x, int y)
: x(x), y(y)
{ }
int Point::getX() const { return this->x; }
int Point::getY() const { return this->y; }
void Point::setX(int x) { this->x = x; }
void Point::setY(int y) { this->y = y; }
Point operator+(const Point& lhs, const Point& rhs)
{
return Point(lhs.x + rhs.x, lhs.y + rhs.y);
}
ostream& operator<<(ostream& os, const Point& pt)
{
return os << "(" << pt.x << ", " << pt.y << ")";
}
Point foo(9, 6);
Point bar(3, 19);
編譯生成 C++ 代碼的 DLL,和 C 語言的 DLL 沒有區(qū)別:
>g++ -c -o point.o point.cpp -D POINT_EXPORTS
>g++ -o point.dll point.o -s -shared -Wl,--subsystem,windows,--out-implib,libpoint.a
或者生成靜態(tài)庫冰评,鏈接成無動(dòng)態(tài)鏈接依賴的程序:
>gcc -c src\*.cpp -Iinclude
>ar rcs libpoint.a *.o
>gcc pointTest.cpp -I include/ -L lib/ -l point -o testPoint.exe
打包歸檔命令 ar 將所有 .o 文件打包為靜態(tài)庫映胁,r 將文件插入靜態(tài)庫中,c 創(chuàng)建靜態(tài)庫集索,不管庫是否存在屿愚,s 寫入一個(gè)目標(biāo)文件索引到庫中,或者更新一個(gè)存在的目標(biāo)文件索引务荆。
這時(shí)創(chuàng)建了導(dǎo)入庫 libpoint.a妆距,這是可選的,因?yàn)槌随溄映绦蚝埃€有其它方法調(diào)用 DLL 中的 API娱据。
使用 objdump -p
命令查看導(dǎo)出符號(hào),可以我發(fā)現(xiàn)類似 _ZN5Point4setXEi
盅惜、_ZlsRSoRK5Point
這樣的符號(hào)中剩。使用 c++filt 這個(gè) Demangle 工具可以將導(dǎo)出的 C++ 符號(hào)還原:
>c++filt -n _ZlsRSoRK5Point
operator<<(std::basic_ostream<char, std::char_traits<char> >&, Point const&)
>c++filt -n _ZN5Point4setXEi
Point::setX(int)
>c++filt -n _ZN5Point4setYEi
Point::setY(int)
>c++filt -n _ZN5PointC1Eii
Point::Point(int, int)
>c++filt -n _ZN5PointC1Ev
Point::Point()
>c++filt -n _ZN5PointC2Eii
Point::Point(int, int)
>c++filt -n _ZN5PointC2Ev
Point::Point()
>c++filt -n _ZNK5Point4getXEv
Point::getX() const
>c++filt -n _ZNK5Point4getYEv
Point::getY() const
>c++filt -n _ZlsRSoRK5Point
operator<<(std::basic_ostream<char, std::char_traits<char> >&, Point const&)
>c++filt -n _ZplRK5PointS1_
operator+(Point const&, Point const&)
創(chuàng)建 DLL 后,就可以寫測(cè)試程序:
#include <iostream>
#include "point.hpp"
using namespace std;
int main(int argc, char** argv)
{
Point a;
Point b(2, 7);
Point c;
c.setX(85);
c.setY(24);
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = (" << c.getX() << ", " << c.getY() << ")\n";
cout << "foo + bar = " << foo << " + " << bar << " = " << (foo + bar) << endl;
return 0;
}
編譯鏈接測(cè)試程序 testPoint.cpp:
>g++ -c -o pointtest.o pointtest.cpp
>g++ -o pointtest.exe -s pointtest.o -L. -lpoint
>pointtest.exe a = (0, 0)
b = (2, 7)
c = (85, 24)
foo + bar = (9, 6) + (3, 19) = (12, 25)
發(fā)布 DLL 時(shí)抒寂,需要注意避免破壞其它程序的正常運(yùn)行结啼,應(yīng)該給 DLL 附加一個(gè)版本號(hào)后綴以區(qū)別,如下:
point-mingw-4.5.2.dll
point-msvc-2010.dll
這個(gè)工程目錄結(jié)構(gòu):
─ ${application}
├── example
│ ├── CMakeLists.txt
│ └── testPoint.cpp
├── include
│ └── point.hpp
├── src
│ └── point.cpp
├── CMakeLists.txt
└── DllDemo.sublime-project
為了使用 CMake 自動(dòng)化編譯屈芜,在工程頂級(jí)目錄設(shè)置 CMakeLists.txt:
cmake_minimum_required(VERSION 2.8)
project( dllDemo )
# set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -m64 -g -Wall -O2")
# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -m64 -g -Wall -O2 -std=c++11")
set(CMAKE_CXX_FLAGS "-w" )
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
if (POLICY CMP0054)
cmake_policy(SET CMP0015 NEW)
endif()
include_directories( "./include/" )
# Static Libs
# set(CMAKE_EXE_LINKER_FLAGS "-static-libgcc -static-libstdc++ -static")
set(BUILD_SHARED_LIBS ON)
set(ENV{PATH} C:/CodeBlocks/MinGW/bin)
message($ENV{PATH})
execute_process(COMMAND where g++ )
execute_process(COMMAND where make )
aux_source_directory("src/" src)
add_library( point ${src} )
# message( ${CMAKE_INSTALL_LIBDIR} )
install(TARGETS point DESTINATION "/lib")
install(TARGETS point DESTINATION "${PROJECT_SOURCE_DIR}/bin")
add_subdirectory(example bin EXCLUDE_FROM_ALL)
# add_subdirectory(example bin)
在 example 子目錄下設(shè)置 CMakeLists.txt:
cmake_minimum_required(VERSION 2.8)
include_directories("${PROJECT_SOURCE_DIR}/include/")
link_directories(
"${PROJECT_BINARY_DIR}/lib/"
"${PROJECT_SOURCE_DIR}/lib/"
)
# set(CMAKE_CXX_FLAGS "-Wl,-Bstatic" )
# set(CMAKE_FIND_LIBRARY_SUFFIXES ".a")
# link_libraries(point)
add_executable(PointTest pointTest.cpp)
# dynamic link
add_executable(PointTest pointTest.cpp)
target_link_libraries( PointTest point )
# static linke
# set_property(TARGET point PROPERTY IMPORTED_LOCATION libpoint.a)
# add_executable(PointTest pointTest.cpp)
# target_link_libraries( PointTest libpoint.a )
作為小巧郊愧、功能強(qiáng)大的 SublimeText,用它來編寫 C++ 工程是組好的選擇井佑,工程文件配置如下属铁,Ctrl-Shift-B 調(diào)用設(shè)置好的命令,先執(zhí)行 CMake 生成 MinGW Makefiles 編譯腳本躬翁,再執(zhí)行 Make 或 Make install 生成動(dòng)態(tài)鏈接庫焦蘑,然后生成 PointTest 程序:
{
"build_systems":
[
{
"env":{
"PATH":"c:/CodeBlocks/MinGW/bin/;%PATH%"
},
"encoding": "utf8",
"file_regex": "^ (.+)\\((\\d+)\\)(): ((?:fatal )?(?:error|warning) \\w+\\d\\d\\d\\d: .*) \\[.*$",
"name": "MinGW Build (Windows)",
"quiet": true,
"shell_cmd": "cmake --build .",
"variants":
[
{
"name": "Make help",
"shell_cmd": "make help"
}, {
"name": "MinGW Makefiles",
"shell_cmd": "cmake .. -G \"MinGW Makefiles\""
}, {
"name": "clean",
"shell_cmd": "make clean"
}, {
"name": "clear all",
"shell_cmd": "del * /s /q"
}, {
"name": "Make",
"shell_cmd": "make -j 4 all"
}, {
"name": "Make install",
"shell_cmd": "make install"
}, {
"name": "Make PointTest",
"shell_cmd": "make PointTest"
}
],
"working_dir": "${project_path}/build"
}
],
"folders":
[
{
"path": ".",
"binary_file_patterns":["*.noting"],
"name": "Dll Demo Project",
}
],
"settings":
{
"cmake":
{
"build_folder": "${project_path}/build"
}
}
}
Creating JNI DLLs
MinGW 創(chuàng)建的 DLL 可以和 Java Native Interface 一起使用,JNI 調(diào)用 Win32 函數(shù)使用 stdcall 調(diào)用約定盒发,這種調(diào)用表示函數(shù)參數(shù)入棧順序從右到左例嘱。
因?yàn)椴煌恼Z言想到交互時(shí),需要有一致的函數(shù)調(diào)用和返回行為宁舰,C 語言作為一種歷史悠久的編程語言蝶防,它的函數(shù)調(diào)用方式稱為標(biāo)準(zhǔn)調(diào)用 stdcall,其它常見方式如下:
調(diào)用約定 | 清理堆棧 | 說明 |
---|---|---|
cdecl |
主調(diào)函數(shù) | 參數(shù)從右到左 push 入棧 |
stdcall |
被調(diào)函數(shù) | 同 cdecl
|
fastcall |
被調(diào)函數(shù) | 參數(shù)從右到左 push 入棧明吩,但優(yōu)先使用寄存器傳遞间学,如 EAX、ECX、EDX |
thiscall |
被調(diào)函數(shù) | 參數(shù)從右到左 push 入棧低葫,this 指針通過 ECX 傳遞 |
declspec |
被調(diào)函數(shù) | 用于 DLL 導(dǎo)出函數(shù)详羡,如 __declspec(dllexport)
|
JVM 希望調(diào)用的 DLL 函數(shù)名是未修飾的,或者按 _[function name]@[size of arguments]
這樣的格式修飾嘿悬。錯(cuò)誤的調(diào)用類似以下結(jié)果:
>java Hello
Exception in thread "main" java.lang.UnsatisfiedLinkError: Hello.add(II)I
at Hello.add(Native Method)
at Hello.main(Hello.java:5)
正確導(dǎo)出 JNI 調(diào)用的函數(shù)需要使用 --kill-at
編譯選項(xiàng)实柠,Java 示例如下:
public class Hello
{
public static void main(String[] args)
{
System.out.println("8 + 5 = " + Hello.add(8, 5));
}
static
{
System.loadLibrary("Hello");
}
public static native int add(int a, int b);
}
使用 System.loadLibrary()
加載 DLL,然后使用 Java 提供的命令編譯并生成 C/C++ 頭文件:
>javac Hello.java
>javah Hello
第二個(gè)命令生成 C/C++ 頭文件類似如下:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class Hello */
#ifndef _Included_Hello
#define _Included_Hello
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: Hello
* Method: add
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_Hello_add
(JNIEnv *, jclass, jint, jint);
#ifdef __cplusplus
}
#endif
#endif
接下來創(chuàng)建 C 代碼文件善涨,實(shí)現(xiàn)函數(shù):
#include "Hello.h"
jint JNICALL Java_Hello_add(JNIEnv* env, jclass clazz, jint a, jint b)
{
return (a + b);
}
編譯并運(yùn)行測(cè)試它窒盐,--kill-at
別忘了,還有 JDK 版本和 MinGW 要統(tǒng)一為 32-bit 或 64-bit 版本:
>gcc -c -o Hello.o Hello.c -I "c:\Java\jdk\include\win32" -I "c:\Java\jdk\include"
>gcc -o Hello.dll -s -shared Hello.o -Wl,--subsystem,windows,--kill-at
>java Hello
8 + 5 = 13
在 CMake 編寫腳本時(shí)钢拧,發(fā)現(xiàn)并不能正確使用 --kill-at蟹漓,必須在 target_link_options 命令中使用 LINKER:
才能正確將參數(shù)傳入鏈接程序:
target_link_options( hello PUBLIC --kill-at)
target_link_options( hello PUBLIC LINKER:--kill-at)
最后,注意源内,32-bit JVM 只能加載 32-bit DLL葡粒,64-bit JVM 也只能加載 64-bit DLLs,否則異常:
Can't load IA 32-bit .dll on a AMD 64-bit platform
P/Invoking MinGW DLLs in .NET
MinGW 編譯的 DLL 與 .NET 一起使用要比 JNI 簡單膜钓,因?yàn)椴槐匕?JNI 規(guī)定格式進(jìn)行設(shè)置嗽交。
C# 提供 P/Invoke 即 Platform Invoke 平臺(tái)調(diào)用,調(diào)用非托管 DLL 中的函數(shù)颂斜,和關(guān)鍵字 DllImport 一起使用夫壁。 實(shí)際上,NET 基類庫中定義的類型內(nèi)部調(diào)用 Kernel32.dll沃疮、User32.dll盒让、gdi32.dll 等非托管 DLL 中導(dǎo)出的函數(shù)。
使用 DllImport 將 DLL 導(dǎo)出的 stdcall 函數(shù)聲明為 extern
即可:
using System;
using System.Runtime.InteropServices;
public class Hello
{
public static void Main(string[] args)
{
Console.WriteLine("8 + 5 = {0}", Hello.Add(8, 5));
}
[DllImport("AddLib.dll", CallingConvention = CallingConvention.Cdecl)]
extern public static int Add(int a, int b);
}
還可以指定調(diào)用約定方式忿磅,這就是語言更新?lián)Q代帶來的好處:
CallingConvention = CallingConvention.StdCall
.NET CLR 會(huì)嘗試導(dǎo)入沒有修飾的函數(shù)名糯彬,可以指定 MSVC 函數(shù)名修飾方式凭语,如 _Add@8
:
ExactSpelling = true
當(dāng)然葱她,完全可以顯式指定導(dǎo)入的函數(shù)名:
using System;
using System.Runtime.InteropServices;
public class Hello
{
public static void Main(string[] args)
{
Console.WriteLine("8 + 5 = {0}", Hello.Add(8, 5));
}
[DllImport("AddLib.dll", CallingConvention = CallingConvention.StdCall, EntryPoint = "Add@8", ExactSpelling = true)]
extern public static int Add(int a, int b);
}
注意,程序和 DLL 文件要 32-bit 或 64-bit 對(duì)應(yīng)似扔,否則異常:
>Hello.exe
未經(jīng)處理的異常: System.BadImageFormatException: 試圖加載格式不正確的程序吨些。 (異常來自 HRESULT:0x8007000B)
在 Hello.Add(Int32 a, Int32 b)
在 Hello.Main(String[] args)
作為新式語言,C# 的編譯器提供了平臺(tái)鏈接選項(xiàng):
>csc /platform:x86 /out:Hello.exe Hello.cs
Microsoft (R) Visual C# 2005 Compiler version 8.00.50727.4927
for Microsoft (R) Windows (R) 2005 Framework version 2.0.50727
Copyright (C) Microsoft Corporation 2001-2005. All rights reserved.
在 Visual Studio 中設(shè)置平臺(tái)目標(biāo)炒辉,在工程屬性的 build 選項(xiàng)卡豪墅,這樣就可以在 64-bit 系統(tǒng)編譯 32 bit 目標(biāo)程序,同樣黔寇,可以指定 platform 為 x64偶器。
Using MinGW DLLs with VB6 and VBA
MinGW 編譯的 DLL 可以和 Visual Basic 6 或 VBA 一起使用,只要調(diào)用約定為 stdcall 方式,不支持 cdecl 或其它調(diào)用約定屏轰,并且使用 --kill-at
編譯選項(xiàng):
>gcc -o AddLib.dll add.o -shared -s -Wl,--subsystem,windows,--kill-at
然后颊郎,在 VB 代碼中聲明:
Private Declare Function MyAddFunction Lib "AddLib.dll" Alias "Add" (ByVal a As Long, ByVal b As Long) As Long
Sub Test()
Call MsgBox(MyAddFunction(4, 5))
End Sub
注意,VB 關(guān)鍵字 Alias
導(dǎo)出了 DLL 中的函數(shù)霎苗,并起了個(gè)別名姆吭。Visual Basic 只支持 ANSI 而不支持 Unicode。
如果在 VBA 中唁盏,還需要標(biāo)記 PtrSafe内狸,以確保可以在 64 bit 的 Microsoft Office 上運(yùn)行厘擂,為了向后兼容 Office 2010昆淡,可以進(jìn)行條件判斷:
#If VBA7 Then
Private Declare PtrSafe Function MyAddFunction Lib "AddLib.dll" Alias "Add" (ByVal a As Long, ByVal b As Long) As Long
#Else
Private Declare Function MyAddFunction Lib "AddLib.dll" Alias "Add" (ByVal a As Long, ByVal b As Long) As Long
#End If
Sub Test()
Call MsgBox(MyAddFunction(4, 5))
End Sub
這些代碼使用起來相當(dāng)不舒服,VB 除了在 Office 中用得較多驴党,幾乎沒什么用戶了瘪撇。
Setting the DLL base address
DLL 的基址 base address 是 Windows 系統(tǒng)加載 DLL 的默認(rèn)地址,進(jìn)程的內(nèi)存空間是一個(gè)虛擬空間
virtual address space港庄。程序中使用的 DLL 很多倔既,當(dāng)任意 DLL 的地址出現(xiàn)覆蓋時(shí),就不可能按 DLL 的基址去加載鹏氧,而需要重定位 relocated 加載到不同的地址渤涌。這涉及到加載器的硬編碼補(bǔ)丁操作,比較消耗資源把还。
默認(rèn) MinGW 鏈接程序基于 DLL 名字的哈希分散選擇基址实蓬,這一般不會(huì)有什么問題。也可以通過 --image-base
鏈接參數(shù)設(shè)置基礎(chǔ):
>gcc -o AddLib.dll obj/add.o -shared -s ^
-Wl,--subsystem,windows,--out-implib,libaddlib.a,--image-base,0x10000000
然后再用 objdump 查看 ImageBase:
>objdump -p AddLib.dll
AddLib.dll: file format pei-i386
Characteristics 0x230e
executable
line numbers stripped
symbols stripped
32 bit words
debugging information removed
DLL
Time/Date Tue Apr 19 16:32:45 2011
Magic 010b (PE32)
MajorLinkerVersion 2
MinorLinkerVersion 21
SizeOfCode 00000c00
SizeOfInitializedData 00002200
SizeOfUninitializedData 00000200
AddressOfEntryPoint 000010c0
BaseOfCode 00001000
BaseOfData 00002000
ImageBase 10000000
SectionAlignment 00001000
FileAlignment 00000200
MajorOSystemVersion 4
MinorOSystemVersion 0
MajorImageVersion 1
MinorImageVersion 0
MajorSubsystemVersion 4
MinorSubsystemVersion 0
Win32Version 00000000
SizeOfImage 0000c000
SizeOfHeaders 00000400
CheckSum 0000383c
Subsystem 00000002 (Windows GUI)
DllCharacteristics 00000000
SizeOfStackReserve 00200000
SizeOfStackCommit 00001000
SizeOfHeapReserve 00100000
SizeOfHeapCommit 00001000
LoaderFlags 00000000
NumberOfRvaAndSizes 00000010
Loading and unloading DLLs at runtime
運(yùn)行時(shí)加載 DLL 對(duì)于插件開發(fā)是非常有用的吊履。
這里演示 void __cdecl DoPlugin();
導(dǎo)出函數(shù)安皱,模擬插件的運(yùn)行機(jī)制,程序中只需要調(diào)用 DoPlugin 就可以讓插件運(yùn)行起來艇炎。
需要用到 kernel32.dll 中的 Windows API LoadLibrary
酌伊,調(diào)用此函數(shù)將 DLL 加載到進(jìn)程的地址空間中。Windows 系統(tǒng)自動(dòng)對(duì) DLL 的加載進(jìn)行計(jì)數(shù)缀踪。加載成功計(jì)數(shù)增加一居砖,返回一個(gè)模塊句柄 HMODULE 也即是 DLL 加載到的內(nèi)存地址信息。然后驴娃,通過 GetProcAddress
函數(shù)獲取 DLL 導(dǎo)出函數(shù)的地址奏候,繼續(xù)使用 AddLib.dll
演示如何在運(yùn)行時(shí)調(diào)用 Add
導(dǎo)出函數(shù)。
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
/* Function signature of the function exported from the DLL. */
typedef int (__cdecl *AddFunc)(int a, int b);
int main(int argc, char** argv)
{
HMODULE hAddLib;
AddFunc Add;
/* Attempt to load the DLL into the process's address space. */
if (! (hAddLib = LoadLibrary(TEXT("AddLib.dll"))))
{
fprintf(stderr, "Error loading \"AddLib.dll\".\n");
return EXIT_FAILURE;
}
/* Print the address that the DLL was loaded at. */
printf("Library is loaded at address %p.\n", hAddLib);
/* Attempt to get the memory address of the "Add()" function. */
if (! (Add = (AddFunc) GetProcAddress(hAddLib, "Add")))
{
fprintf(stderr, "Error locating \"Add\" function.\n");
return EXIT_FAILURE;
}
/* Print the address of the "Add()" function. */
printf("Add function is located at address %p.\n", Add);
/* Call the function and display the results. */
printf("7 + 41 = %d\n", Add(7, 41));
/* Unload the DLL. */
FreeLibrary(hAddLib);
return EXIT_SUCCESS;
}
程序有幾點(diǎn)注意:
- LoadLibrary 和 GetProcAddress 返回 NULL 表示失敗唇敞,應(yīng)該進(jìn)行檢查蔗草。
- LoadLibrary 有 ANSI 和 Unicode 兩個(gè)版本咒彤,GetProcAddress 總是使用 ANSI 字符串。
- 使用 C 語言類型定義的函數(shù)指針類型要和 DLL 導(dǎo)出函數(shù)完全匹配咒精,否則會(huì)讓程序崩潰蔼紧。
- 最后,F(xiàn)reeLibrary 函數(shù)應(yīng)該在不需要使用 DLL 時(shí)用來釋放它狠轻,計(jì)數(shù)會(huì)減一奸例,讓系統(tǒng)知道何時(shí)回收內(nèi)存。
運(yùn)行程序測(cè)試:
>DynamicLoad.exe
Library is loaded at address 6DA40000.
Add function is located at address 6DA41280.
7 + 41 = 48