スクリプトとDLLの間に生きる時のデバッグ
はじめに
4月になって環境が変わって色々と忙しい今日このごろ。
気づけば4月も末日、そろそろGW前だというのに未だに慣れず。
そう、4月末なのに今月はまだ1記事も書いてないのです。
マズイ…
というわけで、今回はちょっとしたデバッグのテクニックをご紹介。
他の人がCでDLL作ったけど、Pythonから関数呼び出して使うとなぜか動かない…でも、他人のソースを全部読むのはダルい…という場合にオススメです。
DLLを作る
とりあえず、今回は最小限の構成でDLLを作るのでヘッダーファイルなしのcppファイルのみです。
CMakeLists.txt
cmake_minimum_required(VERSION 3.3) project(DLL) add_library(${PROJECT_NAME} SHARED DLLFunc.cpp)
DLLFunc.cpp
#include <iostream> #include <random> #define DLL_EXPORT __declspec(dllexport) extern "C" DLL_EXPORT float add(int x, int y) { float value = std::random_device()(); return x + y + value; } extern "C" DLL_EXPORT void print(const char* s) { std::cout << "DLL:" << s << std::endl; }
これをcmakeしてからビルドすればとりあえず、DLLが出来上がります。
この時、DLLのデバッグをしたいので、DLLのビルド時のConfigはReleaseではなく、DebugかRelWithDebInfoを選択してください。
このRelWithDebInfoというのは最適化は掛けるけど、デバッグシンボルを埋め込むため、ブレークポイントを挿入したときにちゃんと止まってくれるようになります。(実際は一部最適化の関係でブレークポイントを挿入できない場所もあります)
DLLのテスト
最初はとりあえず、C++からテスト。というわけで、下記のコードを使います。コマンドライン引数にDLLのパス(Debugビルドのもの推奨)を与えてください
なお、もちろんですが下記のファイルは別のディレクトリに保存してくださいね。
CMakeLists.txt
cmake_minimum_required(VERSION 3.3) project(Main) add_executable(${PROJECT_NAME} main.cpp)
main.cpp
#include <iostream> #include <string> #include <Windows.h> int main(int argc, char** argv) { if (argc < 2) { std::cerr << "Wrong arguments!\nMain.exe <Input dll path>" << std::endl; return -1; } auto dll = LoadLibraryA(argv[1]); auto print_fn = GetProcAddress(dll, "print"); auto add_fn = GetProcAddress(dll, "add"); auto value = reinterpret_cast<float(*)(int x, int y)>(add_fn)(10, 20); reinterpret_cast<void(*)(const char*)>(print_fn)(std::to_string(value).c_str()); FreeLibrary(dll); return 0; }
これを実行すると何か標準出力に出るはずです。
このとき、Mainソリューションを開いた状態でDLLFunc.cppをVisualStudioで開き、ブレークポイントを設定すればちゃんとブレークしてくれるはずです。
Pythonで使う
ここまでだとぶっちゃけあまり意味がないのでPythonスクリプトから呼び出します。
実際に使ったのは下記コード。
import ctypes import sys print('Type input dll') input_dll = input() print('--------------------------------------') dll = ctypes.cdll.LoadLibrary(input_dll) dll.print.argtypes = [ctypes.c_char_p] dll.add.restype = ctypes.c_float dll.add.argtypes = [ctypes.c_int, ctypes.c_int] value = dll.add(10, 20) dll.print(ctypes.c_char_p(str(value).encode('utf-8')))
基本的には先程のテストと同様ですが今回は実行時にちょっとした処理を必要とするため、標準入力からdllファイルのパスを受け取るようにしています。
それから実際に実行して、DLLのパスを入力する前にブレークポイントを入れるためにちょっとした処理を行います。
まず、Visual Studioを起動します。次に「デバッグ>プロセスにアタッチ」もしくはctrl+alt+pで表示されるダイアログの選択可能なプロセスから「python.exe」を探します。pとか押せばpのとこまで飛べるんでそれを活用しつつ選んでから「アタッチ」ボタンを押せばアタッチ完了です。
この状態でDLLFunc.cppを開いてブレークポイントを入れれば、pythonから該当する関数を呼び出すとちゃんとブレークポイントで止まります。