libtool on Linux, Cygwin and MinGW

(2003.11.30新規作成。) (2004.11.07 リンク切れを修正。) (2005.7.10 MinGWを追加。)

(2023-03) 全体的に現在の挙動に更新。libtool v2.4.7. このページのサンプルプログラムの置き場所; cpp-examples/libtool-samples at main · netsphere-labs/cpp-examples · GitHub

libtool は、共有オブジェクト (shared object; so、あるいは動的リンクライブラリ;DLL) を生成するための Makefile を、環境に依存せずに書くためのツール。

利用者 (プログラムの開発者) は、Makefile 上で、仮想的なファイル名でコンパイル、あるいはリンクするように書く。libtoolは、それらのファイルに対する操作を解釈して、実際のファイルに対するコンパイルオプションなどを生成し、実行させる。

Autoconf / Automake と共に使う

こちらの方法のほうが通常。

configure.ac ファイルに LT_INIT を加え、各 Makefile.am ファイルに lib_LTLIBRARIES = libmydll.la のように書く。拡張子 .la が libtool 共有ライブラリ。

以降では、libtool を直接使う方法を解説する。

MinGW: libtool の作り直し

(2023-03) このセクション追加。

MinGW, gcc 12.2, libtool 2.4.7 の環境で, /usr/bin/libtool コマンドをそのまま使うと次のようなエラーが発生する。

libtool --tag=CXX  --mode=link g++   -version-info 3:1:2 -no-undefined -rpath /tmp/hoge/lib   -o libmydll.la  mydll.lo
libtool: link: g++ -shared -nostdlib /usr/lib/gcc/x86_64-pc-msys/11.3.0/crtbeginS.o  .libs/mydll.o   -L/usr/lib/gcc/x86_64-pc-msys/11.3.0 -L/usr/lib/gcc/x86_64-pc-msys/11.3.0/../../../../x86_64-pc-msys/lib/../lib -L/usr/lib/gcc/x86_64-pc-msys/11.3.0/../../../../lib -L/usr/lib/../lib -L/usr/lib/gcc/x86_64-pc-msys/11.3.0/../../../../x86_64-pc-msys/lib -L/usr/lib/gcc/x86_64-pc-msys/11.3.0/../../.. -lstdc++ -lgcc_s -lgcc -lmsys-2.0 -ladvapi32 -lshell32 -luser32 -lkernel32 -lgcc_s -lgcc /usr/lib/gcc/x86_64-pc-msys/11.3.0/crtend.o    -o .libs/msys-mydll-1.dll -Wl,--enable-auto-image-base -Xlinker --out-implib -Xlinker .libs/libmydll.dll.a
C:/git-sdk-64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/12.2.0/../../../../x86_64-w64-mingw32/bin/ld.exe: warning: cannot find entry symbol DllMainCRTStartup; not setting start address
C:/git-sdk-64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/12.2.0/../../../../x86_64-w64-mingw32/bin/ld.exe: .libs/mydll.o: in function `printf':
E:/hhori/repos/netsphere-labs/cpp-examples/libtool-samples/libtool-only/mydll.cpp:372: undefined reference to `__imp___acrt_iob_func'
C:/git-sdk-64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/12.2.0/../../../../x86_64-w64-mingw32/bin/ld.exe: E:/hhori/repos/netsphere-labs/cpp-examples/libtool-samples/libtool-only/mydll.cpp:372: undefined reference to `__mingw_vfprintf'
C:/git-sdk-64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/12.2.0/../../../../x86_64-w64-mingw32/bin/ld.exe: .libs/mydll.o: in function `Base::~Base()':
E:/hhori/repos/netsphere-labs/cpp-examples/libtool-samples/libtool-only/mydll.cpp:15: undefined reference to `operator delete(void*, unsigned long long)'
C:/git-sdk-64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/12.2.0/../../../../x86_64-w64-mingw32/bin/ld.exe: .libs/mydll.o: in function `C::~C()':
E:/hhori/repos/netsphere-labs/cpp-examples/libtool-samples/libtool-only/mydll.cpp:20: undefined reference to `operator delete(void*, unsigned long long)'
C:/git-sdk-64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/12.2.0/../../../../x86_64-w64-mingw32/bin/ld.exe: msys_mydll_1_dll_ertr000001.o:(.rdata+0x0): undefined reference to `_pei386_runtime_relocator'
collect2.exe: error: ld returned 1 exit status
make: *** [Makefile:26: libmydll.la] エラー 1    

正しくは dllcrt2.ocrtbegin.o をリンクしなければならないが, crtbeginS.o をリンクしようとするため。

複雑な話ではない。libtool コマンドはシェルスクリプトで, 実行する環境に合わせて調整しなければならない。しかし MinGW のバイナリパッケージは単にファイルコピーでインストールするのだろう。predep_objects 変数がまずい。

Autoconf を使っている場合は, configure スクリプトが ltmain.sh ファイルを組み込んで $(top_builddir)/config.status を生成する。ビルドの際に config.status スクリプトがローカルに libtool スクリプトを生成し、以降はそれが使われる。そのため、グローバルにインストールされているほうに問題があっても発覚しにくい。

簡単なのは、libtool のsource tarball を取ってきてコンパイルし、生成された libtoolizelibtool/usr/bin のを上書きしてしまう。msys 用に当たっているパッチが巻き戻るが, MinGW では実害なさそう。単に次でよい;

~/src/libtool_build$ ../libtool-2.4.7/configure --prefix=/usr

--disable-ltdl-install は指定しなくてよい (ltdl もインストールする)。MinGW のバイナリパッケージは libtool と libltdl パッケージに分かれている。

次のようにして、バイナリパッケージのファイル一覧を確認しておいてもよい。

$ pacman --query --list libtool
$ pacman --query --owns /usr/include/ltdl.h
/usr/include/ltdl.h は libltdl 2.4.7-3 によって所有されています

libtoolの各コマンド

libtoolは、--mode オプションで実行したい操作を区別する。modeによって、libtoolの動作はまったく異なる。

--mode の値意味
compile コンパイルして, libtool オブジェクトを生成.
link リンク. ライブラリまたは実行ファイルの生成.
install インストール
finish libtool ライブラリのインストールを完了させる。
uninstall アンインストール
clean クリーンアップ. 未インストールのライブラリまたは実行ファイルを削除する。
execute ライブラリをインストールせずに、ライブラリを使用するプログラムを実行する。

Cygwin は, configure.(ac|in) ファイルを見て, 開発版か安定版のいずれかのlibtoolを起動するようになっている。そのため、configure.ac ファイルがない場合は、libtool コマンドを絶対パスで記述する必要がある。

コンパイル

まずはライブラリにするためのソースファイルをコンパイルする。ここでの入力ファイル名は実在のものを使う。出力ファイル名は .lo にする。

$ ./libtool --tag=CXX  --mode=compile g++ -c     -Wall -Wextra -g -o mydll.lo mydll.cpp
libtool: compile:  g++ -c -Wall -Wextra -g mydll.cpp  -DDLL_EXPORT -DPIC -o .libs/mydll.o

ディレクトリ .libs/ が生成され、必要であれば、本物の生成物はこのディレクトリに入れられる。libtoolは、自動的に適当なコンパイルオプションを追加し (-DPIC など)、コンパイルさせる。

カレントディレクトリに mydll.lo ファイルが生成される。Cygwinでは、mydll.lo はただのテキストファイル。MinGW, Linux でも同様。Linux だと次のようになる;

# mydll.lo - a libtool object file
# Generated by libtool (GNU libtool) 2.4.7
#
# Please DO NOT delete this file!
# It is necessary for linking the library.

# Name of the PIC object.
pic_object='.libs/mydll.o'

# Name of the non-PIC object
non_pic_object=none

ライブラリのリンク

ライブラリを生成するのは、libtool --mode=link コマンド。リンクする入力 (オブジェクトファイル) は、*.o ではなく *.lo ファイルを指定する。

gcc-o オプションで出力するライブラリのファイル名を指定する。出力ファイル名は lib で始まらなければならず、拡張子は .la とする。

動的リンクライブラリを出力するときは、libtoolのオプション -rpath を指定しなければならない。このオプションには、ライブラリの最終的なインストール先を指定する。

$ ./libtool --tag=CXX  --mode=link g++   -version-info 3:1:2 -no-undefined -rpath /tmp/hoge/lib   -o libmydll.la  mydll.lo
libtool: link: g++ -shared -nostdlib C:/git-sdk-64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/12.2.0/../../../../lib/dllcrt2.o C:/git-sdk-64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/12.2.0/crtbegin.o  .libs/mydll.o   -LC:/git-sdk-64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/12.2.0 -LC:/git-sdk-64/mingw64/bin/../lib/gcc -LC:/git-sdk-64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/12.2.0/../../../../x86_64-w64-mingw32/lib/../lib -LC:/git-sdk-64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/12.2.0/../../../../lib -LC:/git-sdk-64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/12.2.0/../../../../x86_64-w64-mingw32/lib -LC:/git-sdk-64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/12.2.0/../../.. -lstdc++ -lmingw32 -lgcc_s -lgcc -lmoldname -lmingwex -lmsvcrt -lkernel32 -lpthread -ladvapi32 -lshell32 -luser32 -lkernel32 -lmingw32 -lgcc_s -lgcc -lmoldname -lmingwex -lmsvcrt -lkernel32 C:/git-sdk-64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/12.2.0/crtend.o    -o .libs/libmydll-1.dll -Wl,--enable-auto-image-base -Xlinker --out-implib -Xlinker .libs/libmydll.dll.a
libtool: link: ( cd ".libs" && rm -f "libmydll.la" && cp -pR "../libmydll.la" "libmydll.la" )

これで、libmydll.la が生成される。Cygwin / MinGWでは、これもテキストファイル。Linux でも同様。

Cygwinでは、libtoolのオプション -no-undefined を付けないとリンク時にエラーが発生する。このオプションは、出力ファイルが他のライブラリに依存していないことを宣言するもの。MinGW では, -no-undefined を付けないと, 静的ライブラリだけが生成される。なので、DLL を出力させる -rpath オプションだけがあると、エラーになる。

./libtool --tag=CXX  --mode=link   g++   -rpath /tmp/hoge/lib  -o libmydll.la  mydll.lo
libtool:   error: can't build x86_64-w64-mingw32 shared library unless -no-undefined is specified

Linux では、-no-undefined オプションはなくてもいい (付けても問題ない)。ということで、いつでも付けるようにすればよい。

実行に成功すると, Cygwinでは、次のように *.dll ファイルと静的ライブラリ libmydll.dll.a が生成される。MinGW でも同様。

$ ls .libs/
cygmydll-0.dll  libmydll.a  libmydll.dll.a  libmydll.la  libmydll.lai  mydll.o

libtool のオプション -export-symbols または -export-symbols-regex オプションで, エクスポートするシンボルを指定することもできる。付けなければ、すべてのシンボルがエクスポートされる。

ライブラリのインストール

(2005.7.10 この節追加。)

ライブラリをインストールするには、libtool --mode=install を使う。install または cp コマンドとそのオプションを続けて書く。コピー元は *.la ファイル、コピー先はディレクトリにする。

libtool --mode=install [ install | cp ] ...

Linuxでは、次のようになる。

$ libtool --mode=install cp libmydll.la /home/hori/src/test/share/simple/t
cp .libs/libmydll.so.0.0.0 /home/hori/src/test/share/simple/t/libmydll.so.0.0.0
(cd /home/hori/src/test/share/simple/t && rm -f libmydll.so.0 && ln -s libmydll.so.0.0.0 libmydll.so.0)
(cd /home/hori/src/test/share/simple/t && rm -f libmydll.so && ln -s libmydll.so.0.0.0 libmydll.so)
cp .libs/libmydll.lai /home/hori/src/test/share/simple/t/libmydll.la
cp .libs/libmydll.a /home/hori/src/test/share/simple/t/libmydll.a
ranlib /home/hori/src/test/share/simple/t/libmydll.a
chmod 644 /home/hori/src/test/share/simple/t/libmydll.a

クリーンアップ

オブジェクトファイルなどを一掃するには、libtool --mode=clean とする。

$ /usr/autotool/devel/bin/libtool --mode=clean rm libmydll.la main.lo \
          main.o mydll.lo mydll.o testpg testpg.exe 
rm libmydll.la .libs/libmydll.dll.a .libs/libmydll.a .libs/libmydll.la .libs/libmydll.lai
rm main.lo ./.libs/main.o ./main.o
rm main.o
rm: cannot remove `main.o': No such file or directory
rm mydll.lo ./.libs/mydll.o ./mydll.o
rm mydll.o
rm: cannot remove `mydll.o': No such file or directory
rm testpg .libs/testpg .libs/testpgS.o .libs/lt-testpg
rm: cannot unlink `.libs/testpg': No such file or directory
rm: cannot remove `.libs/testpgS.o': No such file or directory
rm: cannot remove `.libs/lt-testpg': No such file or directory
rm testpg.exe testpg
rm: cannot remove `testpg': No such file or directory
rmdir .libs

む、main.o、mydll.o、testpg は不要だったか。

利用プログラムとのリンク

メインプログラムをコンパイルする。これも libtool --mode=link を使う。

$ g++ -Wall -Wextra -g   -c -o mydll_test.o mydll_test.cpp
./libtool --tag=CXX  --mode=link g++   -o mydll_test.exe mydll_test.o libmydll.la
libtool: link: g++ -o .libs/mydll_test.exe mydll_test.o  ./.libs/libmydll.dll.a -L/tmp/hoge/lib

これで、カレントディレクトリと .libs/mydll_test.exe が生成される。

MinGW: ldd コマンドで確認すると, カレントディレクトリに生成されるほうは静的リンク、.libs/ に生成されるほうは動的リンクになっている。Linux: カレントディレクトリのほうはシェルスクリプト, .libs/ のほうが実体。

Makefile の書き方

これらを踏まえて、Makefile を作成すると、次のようになる。libtool の場所と拡張子が環境依存になる。完全に差異を吸収するのはなかなか難しい。 Cygwin では LIBTOOL = /usr/autotool/devel/bin/libtool とする。

EXEEXT = .exe

# mingw
LIBTOOL = ./libtool


# 共用ライブラリは拡張子 .la; 'lib' で始まること.
TARGETS = libmydll.la mydll_test$(EXEEXT)

CXXFLAGS = -Wall -Wextra -g
CXXLD = g++

%.lo: %.cpp
	$(LIBTOOL) --tag=CXX $(LIBTOOLFLAGS) --mode=compile $(CXX) -c $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(CPPFLAGS) $(CXXFLAGS) -o $@ $<



all: $(TARGETS)

mydll.lo: mydll.cpp mydll.h

# -version-info, -no-undefined および -rpath は libtool のオプション.
libmydll.la: mydll.lo
	$(LIBTOOL) --tag=CXX $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(LDFLAGS) $(LDLIBS) -version-info 3:1:2 -no-undefined -rpath /tmp/hoge/lib   -o $@  $^

mydll_test$(EXEEXT): mydll_test.o libmydll.la
	$(LIBTOOL) --tag=CXX $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(LDFLAGS) $(LDLIBS) -o $@ $^

mydll_test.o: mydll_test.cpp mydll.h


clean:
	$(LIBTOOL) --mode=clean rm -f $(TARGETS) *.o *.lo

エクスポートするシンボルを指定する

(2003.12.03)

上記の例では、すべての関数をエクスポートしていた。特定の関数だけエクスポートする場合は、シンボル一覧ファイルを別に用意する。

次のファイルからライブラリを作り、このうち exp_func() だけエクスポートしてみる。

#include <stdio.h>

extern "C" {
void exp_func();
void no_exp_func();
}

void exp_func() {
    printf("ok.\n");
}

void no_exp_func() {
    printf("failed.\n");
}

次の内容のシンボルファイル dll2.sym を作る。エクスポートするシンボルを1行1シンボルにて記述する。シンボルの先頭に'_'などを付ける必要はない。このシンボル名は、CygwinでもLinuxでも同じものが使える。

exp_func

Makefile では、次のように書く。libtool の -export-symbols オプションでこのファイルを指定する。

libdll2.la: dll2.lo
	$(LIBTOOL) --mode=link gcc -o $@ $^ -no-undefined -rpath /usr/local/lib \
                   -export-symbols dll2.sym

libtoolは、Cygwinの場合は内部で次のファイルを生成し、これを gcc に与えることで特定のシンボルだけエクスポートする。

EXPORTS
exp_func

外部リンク

Using static and shared libraries across platforms
いろいろな環境での、ライブラリ生成、使用のためのコンパイルオプション、環境変数など。