DLL with MinGW

代碼倉庫在此,歡迎進(jìn)行生物工程:

Izumi Sakai

GCC - GNU Compiler Collection

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:

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
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末向楼,一起剝皮案震驚了整個(gè)濱河市查吊,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌湖蜕,老刑警劉巖逻卖,帶你破解...
    沈念sama閱讀 207,248評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異昭抒,居然都是意外死亡评也,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門灭返,熙熙樓的掌柜王于貴愁眉苦臉地迎上來盗迟,“玉大人,你說我怎么就攤上這事熙含》B疲” “怎么了?”我有些...
    開封第一講書人閱讀 153,443評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵怎静,是天一觀的道長邮弹。 經(jīng)常有香客問我,道長蚓聘,這世上最難降的妖魔是什么腌乡? 我笑而不...
    開封第一講書人閱讀 55,475評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮夜牡,結(jié)果婚禮上与纽,老公的妹妹穿的比我還像新娘。我一直安慰自己氯材,他們只是感情好渣锦,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,458評(píng)論 5 374
  • 文/花漫 我一把揭開白布硝岗。 她就那樣靜靜地躺著氢哮,像睡著了一般。 火紅的嫁衣襯著肌膚如雪型檀。 梳的紋絲不亂的頭發(fā)上冗尤,一...
    開封第一講書人閱讀 49,185評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼裂七。 笑死皆看,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的背零。 我是一名探鬼主播腰吟,決...
    沈念sama閱讀 38,451評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼徙瓶!你這毒婦竟也來了毛雇?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,112評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤侦镇,失蹤者是張志新(化名)和其女友劉穎灵疮,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體壳繁,經(jīng)...
    沈念sama閱讀 43,609評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡震捣,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,083評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了闹炉。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蒿赢。...
    茶點(diǎn)故事閱讀 38,163評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖渣触,靈堂內(nèi)的尸體忽然破棺而出诉植,到底是詐尸還是另有隱情,我是刑警寧澤昵观,帶...
    沈念sama閱讀 33,803評(píng)論 4 323
  • 正文 年R本政府宣布晾腔,位于F島的核電站,受9級(jí)特大地震影響啊犬,放射性物質(zhì)發(fā)生泄漏灼擂。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,357評(píng)論 3 307
  • 文/蒙蒙 一觉至、第九天 我趴在偏房一處隱蔽的房頂上張望剔应。 院中可真熱鬧,春花似錦语御、人聲如沸峻贮。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽纤控。三九已至,卻和暖如春碉纺,著一層夾襖步出監(jiān)牢的瞬間船万,已是汗流浹背刻撒。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評(píng)論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留耿导,地道東北人声怔。 一個(gè)月前我還...
    沈念sama閱讀 45,636評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像舱呻,于是被迫代替她去往敵國和親醋火。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,925評(píng)論 2 344