FindPackageで探索してほしくないライブラリを指定する方法
CMakeを使っていて、FindPackageで探索してほしくないパッケージがある時の解決方法。
Anacondaとか入れてると稀に良く困る割に、あんまり紹介されてないなーと思うので、一応メモも兼ねて。
DLIB入れる時にLIBJPEGとLIBPNGについてAnacondaのものを探索してきて困るので以下の2つを足してcmakeしました。
cmake .. -DCMAKE_DISABLE_FIND_PACKAGE_PNG=ON -DCMAKE_DISABLE_FIND_PACKAGE_JPEG=ON
DLIBのコンパイル For Debugビルド
はじめに
今回ビルドするのは、DLIBというC++で書かれた機械学習用のライブラリです。
dlib C++ Library
どこが問題?
Windows環境にて、VisualStudioを使ってDLIBをDebugビルドでコンパイルしようとすると、幾つかの問題に直面します。
中でも気になるのが以下の3つ。
- Windowsでは、DLLの生成ができない(__declspec(dllexport)が定義されていない)。
- もし、CMakeLists.txt、add_library内のSTATICをSHAREDに置換するとlibpngのEXPORTマクロのおかげでDLLにはlibpngの内容が書き込まれる。
- Debugビルドすると以下のリンクエラーが出る。
_USER_ERROR__missing_dlib_all_source_cpp_file__OR__inconsistent_use_of_DEBUG_or_ENABLE_ASSERTS_preprocessor_directives_
それで、1、2については諦めました……が3は諦めきれないので何とかします。
解決法
まあ、出てくるエラーを読めば書いているのですが。
ENABLE_ASSSERTSマクロをONにしろと、そういうことだそうです。
以下の様なディレクトリ構造でビルドするときのコマンドを一応載せておきます
dlib |-- dlib-19.0 `-- build
cmake -DDLIB_ENABLE_ASSERS=TRUE .. cmake --build . --target INSTALL --config=Debug cmake --build . --target INSTALL --config=Release
番外編
上のコマンドのまんま実行するとDebugとReleaseでlibファイルの名前が同じなので爆死します。
ということでdlib-19.0\CMakeLists.txtと<インストールされたディレクトリ>\lib\cmake\dlibにあるdlib-
cmake_minimum_required(VERSION 2.8.4) IF (CMAKE_SIZEOF_VOID_P EQUAL 8) # Architectureの判定 MACだったらもっとスマートにかけるのになあ… SET(__INTERNAL_ARCHITECTURE_STRING__ x64) ELSE() SET(__INTERNAL_ARCHITECTURE_STRING__ x86) ENDIF() SET(CMAKE_DEBUG_POSTFIX d_${__INTERNAL_ARCHITECTURE_STRING__}) SET(CMAKE_RELEASE_POSTFIX _${__INTERNAL_ARCHITECTURE_STRING__}) add_subdirectory(dlib)
#---------------------------------------------------------------- # Generated CMake target import file for configuration "Release". #---------------------------------------------------------------- # Commands may need to know the format version. set(CMAKE_IMPORT_FILE_VERSION 1) IF (CMAKE_SIZEOF_VOID_P EQUAL 8) SET(__INTERNAL_ARCHITECTURE_STRING__ x64) ELSE() SET(__INTERNAL_ARCHITECTURE_STRING__ x86) ENDIF() # Import target "dlib::dlib" for configuration "Release" set_property(TARGET dlib::dlib APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE) set_target_properties(dlib::dlib PROPERTIES IMPORTED_LINK_INTERFACE_LANGUAGES_RELEASE "CXX" IMPORTED_LINK_INTERFACE_LIBRARIES_RELEASE "なんか文字列" IMPORTED_LOCATION_RELEASE "${_IMPORT_PREFIX}/lib/dlib_${__INTERNAL_ARCHITECTURE_STRING__}.lib" ) list(APPEND _IMPORT_CHECK_TARGETS dlib::dlib ) list(APPEND _IMPORT_CHECK_FILES_FOR_dlib::dlib "${_IMPORT_PREFIX}/lib/dlib_${__INTERNAL_ARCHITECTURE_STRING__}.lib" ) # Commands beyond this point should not need to know the version. set(CMAKE_IMPORT_FILE_VERSION)
追記
テストコード貼るの忘れてました。
入力画像からSelectiveSearchで物体っぽい領域を抽出するプログラムです。
本来、閾値処理などで論外な矩形を除外するべきなのですが、それをしていないので、山程矩形が出てきます。
まだ、DLIBの使い方をよく理解していないので、間違っていたらよしなに。
#include <dlib/gui_widgets.h> #include <dlib/image_io.h> #include <dlib/image_transforms.h> #include <string> #if defined(_DEBUG) || defined(DEBUG) #pragma comment(lib, R"(dlibd_x86.lib)") #else #pragma comment(lib, R"(dlib_x86.lib)") #endif int main(int argc, char** argv) { if(argc < 2) { std::cerr << "Wrong arguments.\n<Exe name> <Input image>" << std::endl; return -1; } char const* INPUT_FILENAME = argv[1]; dlib::array2d<dlib::rgb_pixel> image, rect_image; dlib::load_image(image, INPUT_FILENAME); dlib::assign_image(rect_image, image); // コピーの方法がわかったので修正しました std::vector<dlib::rectangle> rectangles; dlib::find_candidate_object_locations(image, rectangles); std::cout << rectangles.size() << std::endl; for (auto&& rect : rectangles) { dlib::draw_rectangle(rect_image, rect, dlib::rgb_pixel(255, 0, 0), 1); } dlib::image_window original_window(image, "Original"), find_rectangles(rect_image, "Result"); original_window.wait_until_closed(); find_rectangles.wait_until_closed(); return 0; }
追記2
番外編で書いてることが微妙に嘘だったんで近日中に修正します。
CMAKE_DEBUG_POSTFIXでライブラリファイルの名前を変えた場合にFindPackageで適切なlibファイルを選択させるためには、dlibConfig.cmakeファイルを編集する必要があります。
具体的には38行目周辺のfind_libraryを以下のようにする必要があります。
IF(WIN32) find_library(dlib_DEBUG_X86_LIBRARIES dlibd_x86 HINTS ${dlib_INSTALL_PATH}/lib) find_library(dlib_DEBUG_X64_LIBRARIES dlibd_x64 HINTS ${dlib_INSTALL_PATH}/lib) find_library(dlib_RELEASE_X86_LIBRARIES dlib_x86 HINTS ${dlib_INSTALL_PATH}/lib) find_library(dlib_RELEASE_X64_LIBRARIES dlib_x64 HINTS ${dlib_INSTALL_PATH}/lib) IF(CMAKE_SIZEOF_VOIDP EQUAL 8) set(dlib_RELEASE_LIBRARIES optimized ${dlib_RELEASE_X64_LIBRARIES}) set(dlib_DEBUG_LIBRARIES debug ${dlib_DEBUG_X64_LIBRARIES}) ELSE() set(dlib_RELEASE_LIBRARIES optimized ${dlib_RELEASE_X86_LIBRARIES}) set(dlib_DEBUG_LIBRARIES debug ${dlib_DEBUG_X86_LIBRARIES}) ENDIF() set(dlib_LIBRARIES ${dlib_RELEASE_LIBRARIES} ${dlib_DEBUG_LIBRARIES}) ELSE() find_library(dlib_LIBRARIES dlib HINTS ${dlib_INSTALL_PATH}/lib) ENDIF()
SFINAEやテンプレートの部分特殊化で気をつけたいこと
はじめに
久々にSFINAE(Substitution Failure Is Not An Error)やテンプレートの部分特殊化(Partial Specialization)を久々に使ったら見事にハマったので、まとめときます。
ただし、用語の間違えについては自分の中でもあやふやなんで気づいた人は指摘お願いします。
環境について
多分、どの環境でも同じだけれども、ボクはVisual Studio 2015 Update2を使ってるんで他の環境ついては知りません。
本題
主に気をつけたいことは以下の3つです。いずれもコンパイルエラーの原因です。
- 解決可能なオーバーロードが複数ある。
- 実は lvalue もしくは rvalue な参照がテンプレート引数になっている(のに気が付いていない)
- 配列を受け取りたいのに何故かエラーになる
どれも解決までに結構悩んだものです。
解決可能なオーバーロードが複数ある
これは最もやらかしやすいと思う。
まあ、コンパイルエラーを読めば、そのまんま書いてあるからその通りに直せば良いのだけれども、量が増えるとそうは行かなくなる。
以下は実例。
#include <iostream> #include <type_traits> template <class Tp, class = void> struct type_impl { static void print() { std::cout << "default" << std::endl; } }; // 本来はポインタ型だけを受け取りたい特殊化 template <class Pointer> struct type_impl<Pointer, std::enable_if_t<std::is_pointer<Pointer>::value>> { static void print() { std::cout << "Pointer" << std::endl; } }; // 本来はPOD型を受け取りたい特殊化 template <class POD> struct type_impl<POD, std::enable_if_t<std::is_pod<POD>::value> > { static void print() { std::cout << "POD" << std::endl; } }; template <class Tp> struct type : public type_impl<Tp> {}; int main() { type<int*>::print(); type<int>::print(); return 0; }
これ、エラーになります。
見ればわかるんですけど、is_podはポインタを受け取ってもtrueになってしまうからですね。
当然、解決策の検討が必要になるんだけれども、最も安直な方法はこれ。
16行目にポインタを受け取らないようにチェックを足してやる
template <class POD> struct type_impl<POD, std::enable_if_t< std::is_pod<POD>::value && !std::is_pointer<POD>::value // ポインタは受け取りませんと明示する > > { static void print() { std::cout << "Literal" << std::endl; } };
実は lvalue もしくは rvalue な参照がテンプレート引数になっている(のに気が付いていない)
以下がその例。っていうかさっきと同じコードのmain関数に1行だけ足した
#include <iostream> #include <type_traits> template <class Tp, class = void> struct type_impl { static void print() { std::cout << "default" << std::endl; } }; template <class Pointer> struct type_impl<Pointer, std::enable_if_t<std::is_pointer<Pointer>::value>> { static void print() { std::cout << "Pointer" << std::endl; } }; template <class POD> struct type_impl<POD, std::enable_if_t< std::is_pod<POD>::value && !std::is_pointer<POD>::value> > { static void print() { std::cout << "POD" << std::endl; } }; template <class Tp> struct type : public type_impl<Tp> {}; int main() { type<int*>::print(); type<int>::print(); type<int&>::print(); // 足した部分 return 0; }
さてこれの出力はどうなるでしょうか?
まあ、当たり前なんだけど
Pointer POD default
になります。これがどういう時に困るかというと、以下の様な関数を足した時。
template <class Tp, class Traits = type<Tp>> void print(Tp&& value) { Traits::print(/* std::forward<Tp>(value) */); // 実際にはvalueを渡すことが多かろう } // in int main() int hoge = 0; print(10); // 多分 lvalue reference print(std::move(hoge)); // 間違いなく rvalue reference print(&hoge); // 間違いなくポインタ print(hoge); // ?
で、これの出力が以下のようになって欲しいのに、
POD POD Pointer POD
以下のようになってしまいます。
POD default Pointer default
一瞬うーんと成るかも知れませんが、参照はPOD型ではありません。
それ故、これを解決するためには remove_reference を使い以下のようにPOD型の判定を行う際に参照を外す必要があります。
template <class POD> struct type_impl<POD, std::enable_if_t< std::is_pod<std::remove_reference_t<POD>>::value && !std::is_pointer<POD>::value> > { static void print() { std::cout << "POD" << std::endl; } };
配列を受け取りたいのに何故かエラーになる
これが一番、厄介、というか一番気づくまでに時間がかかった。
さて、ここで言う配列というのは一次元、それも固定長の配列だ。
この配列だが一般的に以下の方法で配列のサイズを取得することが可能とされている
template <class Array, std::size_t Size> constexpr std::size_t size(Array(&)[Size]) { return Size; } int hoge[5] = {}; std::cout << size(hoge) << std::endl; // 5
この方法を応用して以下の様なコードを考える。
#include <iostream> #include <type_traits> template <class Tp, class = void> struct type_impl { static void print(Tp&&) { std::cout << "default" << std::endl; } }; template <class Array, std::size_t Size> struct type_impl<Array[Size], std::enable_if_t<std::is_array<Array>::value>> { static void print(Array (&& value)[Size]) { std::cout << "Array:(" << value << "," << Size << ")" << std::endl; } }; template <class POD> struct type_impl<POD, std::enable_if_t< std::is_pod<std::remove_reference_t<POD>>::value && !std::is_array<POD>::value> > { static void print(POD&& p) { std::cout << "POD:" << p << std::endl; } }; template <class Tp> struct type : public type_impl<Tp> {}; template <class Tp, class Traits = type<Tp>> void print(Tp&& value) { Traits::print(std::forward<Tp>(value)); } int main() { int hoge = 20; int array[10] = {}; print(10); print(hoge); print(array); return 0; }
これを実行すると期待的には以下のとおりにしたいのだが、
POD:10 POD:20 Array:(<arrayのアドレス>,10)
実際は、
POD:10 POD:20 POD:<arrayのアドレス>
となる。
実は、ここで犯しているのはテンプレートの置き換え云々の問題だけではない。
以下がその問題と修正したコードだ。
- 配列は rvalue referenceで受け取れない。
- 特殊化するのはArrayだけではなく、Array[Size]。
#include <iostream> #include <type_traits> template <class Tp, class = void> struct type_impl { static void print(Tp&&) { std::cout << "default" << std::endl; } }; template <class Array, std::size_t Size> struct type_impl<Array[Size], std::enable_if_t<std::is_array<Array[Size]>::value>> // 型の渡し方を変える { // Array (&&)[Size] ではなく Array(&)[Size] static void print(Array (&value)[Size]) { std::cout << "Array:(" << value << "," << Size << ")" << std::endl; } }; template <class POD> struct type_impl<POD, std::enable_if_t< std::is_pod<std::remove_reference_t<POD>>::value && !std::is_array<POD>::value> > { static void print(POD&& p) { std::cout << "POD:" << p << std::endl; } }; template <class Tp> struct type : public type_impl<Tp> {}; template <class Tp, class Traits = type<Tp>> void print(Tp&& value) { Traits::print(std::forward<Tp>(value)); } // オーバーロードする template <class Tp, std::size_t Size, class Traits = type<Tp[Size]>> void print(Tp (&value)[Size]) { Traits::print(value); } int main() { int hoge = 20; int array[10] = {}; print(10); print(hoge); print(array); return 0; }
こうすることで期待通りの結果得られる筈だ。
おわりに
未だにSFINAE機構を使うとやらかすことが多いので、メモを兼ねて記した。
もし、SFINAEやテンプレートの特殊化で困ったことがあれば、この話を思い出して欲しい。
どうでもいいのだけれども
かなりどうでもいい話です。
過去の僕が書いた記事を見ていて、ふと、眼気眼ってなんだと思って。
google検索にかけてみると、なぜか以下のページがトップに出てきた。
なぜだなぜだ、と見返してみると、眠気眼と書いていたらしい。
実際、Google IMEで「ねむけまなこ」で変換すると眠気眼が出てくる。
が、ここで気になるのが、「ねむけまなこ」と「ねぼけまなこ」の違い。
まあ、結果だけ言うと、「ねむけまなこ」なんて言葉は存在しないらしい。
笑われる
なんとまあ、そんな誤用、変換させるなやとGoogleIMEに八つ当たりしたい所だ。
PythonでDLLを使う時の話
DLLを作ろう
まずは、適当な方法でDLLを作ろう。
個人的には何で作ってもいいと思うけど今回はマルチプラットフォームを意識してcmakeを使ってみます。
使うソースは以下3つ。
config.hpp
#ifndef CONFIG_HPP #define CONFIG_HPP #ifdef Test_EXPORTS # define DLL_API extern "C" __declspec(dllexport) #else # define DLL_API extern "C" __declspec(dllexport) #endif #endif CONFIG_HPP
test.hpp
#include "config.hpp" DLL_API void print(char const* str); DLL_API int sum(int lhs, int rhs);
test.cpp
#include "test.hpp" #include <iostream> DLL_API void print(char const* str) { std::cout << str << std::endl; } DLL_API int sum(int lhs, int rhs) { return lhs + rhs; }
CMakeLists.txt
cmake_minimum_required(VERSION 3.3) project(Test) add_executable(Test SHARED ${CMAKE_CURRENT_LIST_DIR}/test.cpp ${CMAKE_CURRENT_LIST_DIR}/test.hpp ${CMAKE_CURRENT_LIST_DIR}/config.hpp )
今回のポイントとしては、DLL_APIマクロに __declspec(dllexport) だけではなく extern "C" も含んでいる所。これがないと、C++の関数として扱われて後述する問題にぶちあたります。
それと、cmakeでDLLを作った経験がない人にとっては馴染みがないと思いますが、cmakeでビルドする場合、謎のWinMainを定義する必要も、(モジュール名)_EXPORTSマクロを自分で定義する必要はありません。cmakeが勝手にやってくれます。
ついでに言うと、ぶっちゃっけヘッダーはadd_executableには不要なんだけど、VisualStdio信者な僕には必須です。
DLLの中身を確認しよう
ここはWindows限定(Linuxとかでもできるかもだけれども、やり方は知らん)。
まず、VisualStudioのコマンドツールを起動しよう。
それからビルドしたDLLに対して以下のコマンドを実行。
$ dumpbin /EXPORT Test.dll
すると、自分で定義した関数の一覧が出るはず。
これのname欄に@Xsdwsda@見たいな関数名以外のシグネチャが含まれていたらおそらく、extern "C"が抜けているのだと思う。
extern "C"なしのC++の関数は呼び出せないという訳ではないのだけれども、今回の説明では扱わないのでとりあえずextern "C" を付けましょう。
Dump of file Test.dll File Type: DLL Section contains the following exports for Test.dll 00000000 characteristics 577278AE time date stamp Tue Jun 28 22:16:30 2016 0.00 version 1 ordinal base 2 number of functions 2 number of names ordinal hint RVA name 1 0 00001020 print 2 1 00001010 add Summary 1000 .data 1000 .gfids 1000 .rdata 1000 .reloc 1000 .rsrc 2000 .text
Pythonからの呼び出し
後は簡単、ctypesモジュールを使えばちゃちゃっとできます。
# coding:utf-8 import ctypes def main(): dll = ctypes.cdll.LoadLibrary('Test.dll') c_print = dll.print c_print.argtypes = [ctypes.c_char_p] c_print.restype = None c_sum= dll.sum c_sum.argtypes = [ctypes.c_int, ctypes.c_int] c_sum.restype = ctypes.c_int c_print(ctypes.c_char_p('utf-8'.encode('utf-8'))) print(int(c_sum(ctypes.c_int(10), ctypes.c_int(20)))) if __name__ == '__main__': main()
これを実行すると出力は以下のようになるはずです.
utf-8 30
まず、LoadLibraryで読み込んで、その後は、dll.(関数名)でdll内の関数オブジェクトを取得することができます。
その後、戻り値(restype)と引数の型(argtypes)を設定しやりましょう。
その時の注意事項は以下の4つ
- 文字列はencodeでbyte型に変換してからc_char_p型に変換する
- 引数にポインタ型を使いたいときは ctypes.POINTER(ctypes.何らかの型) という風にしてやりましょう。
- 同様にアドレスを渡す時もctypes.addressofでアドレスを渡すことができる。
- たまに、アドレスの型変換をしたい時もあると思うけど、そのときは、ctypes.cast を使いましょう
おわりに
もっと知りたければ、Pythonの公式ドキュメントを見よう。
もし僕に間違いがあっても多分書いてるだろう。
16.16. ctypes — Pythonのための外部関数ライブラリ — Python 3.5.1 ドキュメント
なんとなく
いつのまにやら、ブログをほったらかしにして、2年が立ってました。
改めて自分の書いた、未熟な過去(今も大概ですが)を見てみるとニマーとすることも多いですが、更新を再開しようかなと思います。
動機はなんとなくなので長く続くとは限りませんがねー
ちなみにブログ名を変えた理由は心機一転というわけではなく、単純に書く内容をプログラミングに絞らずに趣味のことも書きたいなと思ったからです。
プログラミングだけだと、ブログ書いてる暇あったらプログラム書こうかなとか思って、ブログの更新をしなくなるしね。
latexmkのメモ
latexmkを使う時の自分用のメモです
前提として今回利用するlatexmkはWindows版であってcygwin版ではないということです
以下のページを参考にしつつlatexmkの設定ファイルを書いてみたけど、動かない
天地有情 latexmk と ptex2pdf
latexmkのドキュメントを読んでみると、どうやらWindowsでlatexmkがプロファイルを読む順番は以下の通りらしい
http://users.phys.psu.edu/~collins/software/latexmk-jcc/latexmk-439.txt
- "C:\latexmk\LatexMk"(LatexMkがプロファイルを書き込んだファイル)
- "%HOME%/.latexmkrc"
- "%USER_PROFILE%/.latexmkrc"
- latexmkの実行ディレクトリ
- -rオプションで与えられたrcファイル
ここで何が問題かというと、cygwinを使用している環境では%HOME%が既に使用されているためWindowsのホームディレクトリが指定されないという点です。
そのためUnix形式のパスである"/home/(ユーザー名)"とか指定しているともうどうしようもない。
とりあえず、手っ取り早い解決策として、1番の方法で解決することに
自分が思うに-rで与えられるプロファイルの順位がもっと高ければなあとは思うが、まあ何かしらの歴史があったのだろう。