Visual Studio でのC++デバッグについて(Python tipsおまけつき)
2014/3/18
Tag: C++,python,VisualStudio
開発室の田村です。
今回はVisual C++のデバッグについて書いていきたいと思います。高度な内容は省いています。コードは一応Mayaプラグインを想定していますが手法についてはMayaプラグインに限ったものではありません。
基本から。まずサンプルプラグインを用意します。コードを簡潔にするために一部エラー処理等省いています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | #include <maya/MFnPlugin.h> #include <maya/MGlobal.h> #include <maya/MPxCommand.h> #include <maya/MArgList.h> class DfTalkCommand : public MPxCommand { public: DfTalkCommand(){} virtual ~DfTalkCommand(){} MStatus doIt(const MArgList& args); bool isUndoable() const{return false;} static void* creator(){return new DfTalkCommand();} }; MStatus DfTalkCommand::doIt(const MArgList& args) { MStatus status; const char* message = "Hello world"; MGlobal::displayInfo(message); return MS::kSuccess; } MStatus initializePlugin(MObject obj) { MFnPlugin plugin(obj, "Digital Frontier", "1.0", "Any"); return plugin.registerCommand("dfTalkCommand", DfTalkCommand::creator); } MStatus uninitializePlugin(MObject obj) { MFnPlugin plugin(obj); return plugin.deregisterCommand("dfTalkCommand"); } |
これをデバッグビルドしMayaでロード、dfTalkCommandコマンドを実行します。
ではこのサンプルプラグインのプログラムに対してデバッガを使ってみましょう。
“Hello world”表示部分は20行目なのでここにブレークポイントをセットします。
Visual Studioでこの行の左側、少し灰色がかった部分をクリックするとブレークポイントが設定され、赤丸アイコンが表示されます。
次にMayaプロセスにアタッチ、Mayaがこの行を実行したときに実行がとまるようにします。
メニューから「デバッグ- プロセスにアタッチ」を選択
「プロセスにアタッチ」ウィンドウが出てくるので「選択可能なプロセス」からmaya.exeを選びます。
Visual Studioのウィンドウ表示状態が若干変わり、更に名前に(実行中)と表示されるようになります。
ここでMayaに移りもう一度dfTalkCommandを実行・・・
するとMayaがこの行(赤丸で示されたブレークポイントのある行)を実行したときにウィンドウフォーカスがVisual Studioに移り実行が中断されます。
20行目、MGlobal::displayInfo(message);
の変数messageの上にカーソルを置くとその変数の値が表示されます。
ここでウォッチを使ってみましょう。
メニューから「デバッグ – ウィンドウ – ウォッチ – ウォッチ1」
「ウォッチウィンドウ」が現れます(画面の状態はここに挙げた画像と異なっている場合があります)
ここで「名前」の欄にmessageとタイプしEnterキーを押すとやはりmessageの値が表示されます。
「ウォッチウィンドウ」では簡単な式を書くこともできます。messageはconst char型の配列なので配列要素を表示してみましょう。
更に値を書き換えることもできます。変更したい値をダブルクリックして新しい値を入力します。
メモリ上にロードされた定数文字列の値を書き換える、かなりきわどいことを行っています。
実行を再開したいときはこのボタン
あるいはファンクションキーのF5を押します。
するとMayaにウィンドウフォーカスが戻り実行が再開、書き換わった変数値が使われていることがわかります。
次にどんな関数を経てこの関数DfTalkCommand::doIt()が呼び出されたか、呼び出し履歴を見てみます。
先ほどと同じようにmessage表示部分でブレーク、中断させ、メニューから「デバッグ – ウィンドウ – 呼び出し履歴」を選択
「呼び出し履歴ウィンドウ」が現れます。
今回はMayaから直接この関数が呼び出されたので見てもあまりよくわかりませんが、自分のコードの中である関数から別の関数が呼び出され、その関数がまた別の関数を呼び出して最終的にブレークポイントのある行で実行中断された場合、その関数が一覧となって表示されます。ソースコードがある場合、一覧の中の行をクリックするとそのソースのあるファイルが開き、該当部分が表示されます。
さて、ブレークポイントの種類について少し詳しく見てみましょう。サンプルコードのDfTalkCommand::doIt()を少し変更します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | MStatus DfTalkCommand::doIt(const MArgList& args) { MStatus status; const char* message; bool isShowDf = args.asBool(0); if (isShowDf) { message = "Hello world"; } else { message = "Hello df"; } isShowDf = false; MGlobal::displayInfo(message); return MS::kSuccess; } |
dfTalkCommand 0;とすると”Hello world”が表示され、dfTalkCommand 1;とすると”Hello df”が表示されるようになります。
先ほどと同様に表示部分の行にブレークポイントを設定し、ブレークポイントの赤丸アイコンを右クリックするとポップアップメニューが出てきます。この中で「条件」を選びます。
「ブレークポイントの条件」ウィンドウが出てくるので
isShowDf == true
とタイプ、OKボタンを押すとブレークポイントの条件が設定され、この条件を満たす場合にのみ実行が中断されるようになります。今回はdfTalkCommand 0;では中断されず、dfTalkCommand 1;実行時にのみ中断されるようになります。
条件の設定されたブレークポイントの赤丸印は少しアイコンのデザインが変わります。
変数がある特定の値になった場合のみバグが発生する場合や、条件がないと何回も中断し毎回実行再開させなければならないのが煩雑な場合等に重宝します。
ここでは挙げませんがブレークポイントには他にも例えば「3回目にこのコードを実行した時にのみ中断する」「ある特定のスレッドがこのコードを実行した時にのみ中断する」などの指定ができます。
処理を中断させて実行状態を調べたいのはあるコードに達した場合だけではありません。例えばある変数の値がいつのまにか変わっているが、いつどのコードが実行されたときに値が変更されたかわからず、その変更タイミングとコードを調べたい場合があります。この場合にはデータブレークポイントを設定します。データブレークポイントを設定すると変数の値、配列要素の値などメモリ上にある特定の場所の値が変更されたときに実行を中断させるこことができます。
データブレークポイントの設定は、ブレークポイントで処理が中断されている状態にて「デバッグ – ウィンドウ – ブレークポイント」を選択、「ブレークポイント」ウィンドウを表示させ、
このウィンドウのメニューにて「新規作成 – 新しいデータブレークポイント」
「ブレークポイントの作成」ウィンドウにてメモリアドレス、あるいはメモリアドレスとして評価される式を入力します。
&isShowDfはメモリアドレスとして評価することができるのでその値が変更されるとブレークポイントにかかり処理が中断します。作成に成功すると「ブレークポイント」ウィンドウに新しい項目が現れます。
このメモリアドレスの値、つまりisShowDf変数の値が変わるとデータブレークポイントにヒットし、処理が中断します。
Visual Studioが変更箇所のソースコードを特定できない場合アセンブラが出てきてギョッとしますが慌てずそっとウィンドウを閉じましょう。
条件やデータブレークポイントの設定は記述を間違えるとエラーになり正しく設定されません。わかりにくいときはウォッチウィンドウで値が表示されるか確認すると良いでしょう。とくにネームスペースを忘れがちなので注意が必要です。ネームスペースがある場合、変数等にはそのネームスペースをつけないと認識されません。
なお、今回は説明を簡単にするためにデータブレークポイントをDfTalkCommand::doIt()内で定義された変数isShowDfに対してセットしましたが、一度関数を抜けるとこの変数はなくなってしまいます。isShowDfに対して与えられていたメモリは再利用され、全く関係のない場所でこのブレークポイントがヒットする可能性があるので通常はブレークポイントの存在中同じ意味で使われ続けることがわかっているメモリ領域に対してセットします。またデバッグビルドの場合はほとんどありませんが、コードに最適化がかかっている場合ブレークポイントは意図したとおりにかからないことがあります。
デバッグ実行の必要がなくなればMayaプロセスからデタッチします「デバッグ- 全てデタッチ」。
——————————————————————————————————————————————–
さて、今回のPython tipsは「Pythonスクリプトのファイル位置を、そのスクリプト自体から調べる方法」です。例えばあるPythonツールがプラグインとして他のPythonスクリプトを読み込む場合、そのスクリプトと同じ場所にpluginsという名前のディレクトリを作っておき、その下にあるスクリプトをプラグインとして認識させる、などの場合に用いられます。
方法いくつかありますがよく使われるのは次の方法
1 2 | import sys print sys.modules[__name__].__file__ |
__name__には現在実行されているコードのモジュール名が入っており、sys.modulesはモジュール名をキー、モジュールオブジェクトを値とした配列なのでsys.modules[__name__]は現在実行されているコードのモジュールとなります。モジュールオブジェクトの__file__アトリビュートにはファイルパスが入っているのでモジュールのファイルパスを取り出すことができます。
私が好きなのは次の方法
1 2 3 | import inspect class _DummyClass(object):pass print inspect.getabsfile(_DummyClass) |
inspectモジュールのgetabsfile()関数はオブジェクトをとり、そのオブジェクトがどのファイルで定義されているかを絶対パスで返します。ダミークラスを作り、すぐにそのクラスオブジェクトをgetabsfile()に渡すとそのクラスオブジェクトが定義されたファイル、つまりこのコードが存在するファイルのパスを得ることができます。最初の方法では絶対パスではなく相対パスで入っていることがあるので場合によって変換が必要ですが、こちらは始めから絶対パスになっているので簡単です。
コメント
コメントフォーム