概要
C++ で書いているコードを、どうしてもPythonで呼べる形のライブラリにしたい。
ただ、そこにはCythonという大きな壁が立ちはだかっていた。
これは、Pythonを爆速にする「Cython」チュートリアル: C++のコードがライブラリに依存しているとき。準備編 の続きになります。
コードは「ここのgithub」にあげてあるので、ぜひご覧ください。
ミニマムなフォルダ構成として、こんな風になっていました。
状況説明
(myenv) user@~/Documents/cython_practice[master]> tree .
.
├── README.md
├── cpp_library
│ ├── TestClass1.cpp
│ └── TestClass1.h
├── cython
│ ├── my_library.pxd
│ ├── my_library.pyx
│ ├── test_class1.pxd
│ └── test_class1.pyx
└── setup.py
さらに前回の最後に、
wget https://gmplib.org/download/gmp/gmp-6.1.2.tar.xz &&\
tar xvf gmp-6.1.2.tar.xz &&\
cd gmp-6.1.2 &&\
./configure --prefix=/usr/local/gmp/6_1_2 &&\
make && make check && make install
として、gmp
ライブラリをインストールしました。
今回何をするか
今回は、まず
- gmpライブラリを使った簡単なコードを書き、コマンドでコンパイルできることを確かめる。
- そもそもC++のライブラリがどのようにインストールされており、どのように参照しているのか。
- 依存関係を持つプログラムのコンパイルはCMakeを使ってどのように実行されるのか。
- それがcython化するときにsetup.pyにどのように書かれるのか。
の、3まで解説していきます。
4に関しては、実際にCythonを使い依存関係を持ったC++プログラムをPythonから呼べるようにコンパイルしていきますが、それは次の記事で書きます。
##C++の依存関係
そもそもC++のライブラリがどのようにインストールされており、どのように参照しているのか。
gmp
ライブラリをインストールした時のコマンドですが、
./configure --prefix=/usr/local/gmp/6_1_2
これは、のちのmake
でコンパイルした際にできたオブジェクトファイル、またプロジェクト内の大元となっているヘッダーファイルを、make install
の際にどこに格納するか指定しているコマンドになります。
もし指定しない場合、オブジェクトファイルは基本的には/usr/local/lib
に保管され、必要なヘッダーファイル(基本的にライブラリに必要なプログラムファイルを全て含んだ一つのヘッダーファイル)は/usr/local/include
に保管されます。
この後、make
によりコンパイルが行われ、make install
によりコンパイルされたオブジェクトファイル、ヘッダーファイルが格納されます。
ここでは、実際にgmpライブラリのオブジェクトファイル、ヘッダーファイルが所定のフォルダに格納されていることを確かめます。
root@e96f489c2395:/# ls /usr/local/gmp/6_1_2/lib/
libgmp.a libgmp.la libgmp.so libgmp.so.10 libgmp.so.10.3.2
root@e96f489c2395:/# ls /usr/local/gmp/6_1_2/include/
gmp.h
たしかに、オブジェクトファイルが/usr/local/gmp/6_1_2/lib
下に、
ヘッダーファイルが/usr/local/gmp/6_1_2/include
下に格納されています。
##cmake
###依存関係を持つプログラムのコンパイルはCMakeを使ってどのように実行されるのか。
したがって、これらを使用するプログラムを作成し、コンパイルするためには、これらを参照するようにしてコンパイルする必要があるのです。
まずは、単純にg++を用い、簡単なプログラムを書き、それをコンパイルして見ます。
#include <iostream>
#include <gmp.h>
using namespace std;
void print_test(){
mpz_t test;
mpz_init(test);
mpz_set_ui(test, 1);
gmp_printf("print : %Zd \n", test);
}
int main(){
print_test();
}
gmpライブラリについて知る必要はないですが、gmpは大きな整数の計算をするライブラリなので、これは単純にtestというオブジェクトに1を代入し、プリントするだけのプログラムです。
オプションを使ってライブラリを明示し、コンパイルします。もちろん、何もライブラリについて書かずにコンパイルしようとすると、参照エラーが出ます。
root@e96f489c2395:/from_local# g++ test.cpp
/tmp/ccZnVmvP.o: In function `main':
test.cpp:(.text+0x1f): undefined reference to `__gmpz_init'
test.cpp:(.text+0x30): undefined reference to `__gmpz_set_ui'
test.cpp:(.text+0x48): undefined reference to `__gmp_printf'
collect2: error: ld returned 1 exit status
となります。これはもちろん依存ライブラリを明示していないからですが、それを解決することで、
root@e96f489c2395:/from_local# g++ test.cpp -L/usr/local/gmp/6_1_2/lib -I/usr/local/gmp/6_1_2/include -lgmp
root@e96f489c2395:/from_local# ./a.out
print : 1
のようにもちろんコンパイルできます。
この場合、-L
はライブラリ(オブジェクトファイル)へのパスを明示しているものであり、
-I
はヘッダーファイルへのパスを明示するコンパイルオプションです。
しかしながら、依存するライブラリが複数ある時や、書いているプログラムが複数に渡る時、このようにg++
でコンパイルするのはかなり面倒なことになります。そこで、CMakeLists.txtを用意し、コンパイルを簡単に行います。
それを実行するために、以下のようなCMakelists.txt
を作り、
cmake . && make
を使ってコンパイルするのが望ましいです。
ここでは、CMakelists.txt
を次のように作成し、コマンドでg++
とオプションを使ってコンパイルする代わりに、 cmake . && make
を使ってコンパイルしてみます。
cmake_minimum_required(VERSION 3.10)
project(TEST VERSION 1.1.0 LANGUAGES CXX)
# Executable will be in ../bin
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR})
set(CMAKE_CXX_FLAGS "-g -O0 -lgmp")
add_executable(test1
test.cpp
)
target_sources(test1
PRIVATE
)
target_include_directories(test1 PRIVATE /usr/local/gmp/6_1_2/include/)
target_link_libraries(test1 gmp /usr/local/gmp/6_1_2/lib)
root@e96f489c2395:/from_local# cmake .
-- Configuring done
WARNING: Target "test1" requests linking to directory "/usr/local/gmp/6_1_2/lib". Targets may link only to libraries. CMake is dropping the item.
-- Generating done
-- Build files have been written to: /from_local
root@e96f489c2395:/from_local# make
[100%] Built target test1
root@e96f489c2395:/from_local# ./test1
print : 1
##まとめ
C++のプログラムをcython化するときに必要な知識として、以下のことを行い、C++側で依存関係をもつプログラムのcython化を達成した。
- C++のプログラムを書くときにつまづきやすい、依存関係について解説した。
- CMakeを使ったC++のコンパイルについて解説した。
次の記事では、
- 依存関係を踏まえたsetup.pyの書き方について解説します。
今回はこの辺で。
おわり。