C++0x 全会一致で承認され C++11 へ

C++0xの最終国際投票はこの水曜日に終わり、さきほど投票結果が届いた… “全会一致で承認する”

我々が”C++0x”と呼んできたC++の次期改正案はついに国際標準になった! ジュネーヴのISOから発行されるのはもう数ヶ月先になるだろうが、それでも今年中には発行されるだろう。ついに大手を振って”C++11”と呼べるようになった。

http://zakkas783.tumblr.com/post/8838927425/c-0x

そんな訳でようやく次期 C++ 改正案である C++0x の規格が正式に承認されたようです.C++0x と言う名前からも分かるように,当初 200X 年のどこかで確定されるはずだった次期規格ですが,デッドラインである 2009 年を余裕で超え,一時は「0x は 16進数と言う意味だったんだよ!」みたいなネタで語られるようにもなった C++0x さん.このたびの承認にともなって、晴れて C++11 と言う名称に変更?になるようです.

実装としては,gcc がかなり頑張ってるらしいですが(VC++ さんは相変わらず散々な言われようのようです)まだまだ実装を完全に完了しているコンパイラは存在しないので(規格が確定していなかったので当たり前ですが・・・),どの辺りから仕事とかでも使われるようになるのかはまだ不透明な感じと言ったところでしょうか.

内容については私自身も大雑把にしか把握していないのですが,auto (型推論) や新しい初期化リスト辺りはほとんどの C++ プログラマにとってもコーディングのストレスを軽減してくれるものになってくれるでしょうし,rvalue (ムーブコンストラクタ) は不要なコピーを抑えるために(場合によっては)必須となってくるでしょうしと,ぼちぼちきちんと把握していかなければなぁと感じるところではあります.

C との互換性のために相変わらずヤバイ (C 的な) 書き方をするとすぐにメモリリーク等を起こしてしまったり,ものによっては「何でそんな挙動になるんだよ><」とか思ったり,エラーメッセージ見て「日本語でおk」と思ったりする事もある「難しい」 C++ さんですが,C++11 でだいぶ改善されるのではと思います.Perl 辺りも「モダンな書き方」の啓蒙でかなり改善された部分はあるようなので,そう言ったモダンな書き方が広まって「よく分からないけど C++ は難しい」みたいな認識が少しでも薄まると良いなと思います.

ウィンドウプロシージャをどうラップするか?

まだメモ書き段階.

Win32 APIGUI プログラミングをする場合,WndProc と呼ばれるコールバック関数に処理が集中してしまうため,注意しておかないとこの関数がカオスになります.この問題への対策として最初に思い浮かぶのが,WM_XXX 毎に関数を分けると言う方法で,これに関してはウィンドウメッセージクラッカーと言うマクロ群が Microsoft から提供されているようです.

例えば上記のコードは、メッセージクラッカを使うと次のようにかけます。

LRESULT CALLBACK WindowProc ( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) {
    switch ( uMsg ) {
    HANDLE_MSG (hwnd, WM_CREATE, OnCreate);
    HANDLE_MSG (hwnd, WM_COMMAND, OnCommand);
    }
    return DefWindowProc (hwnd, uMsg, wParam, lParam);
}

BOOL Cls_OnCreate (HWND hwnd, LPCREATESTRUCT lpCreateStruct) {
    // WM_CREATE の処理
    return TRUE;
}

void Cls_OnCommand (HWND hwnd, int id, HWND hwndCtl, UINT codeNotify) {
    // WM_CREATE の処理
}

注目するところは、(1) WindowProc の switch 文が HANDLE_MSG の並びに変わっているところ、もうひとつはそれぞれのメッセージ (ここでは WM_CREATE と WM_COMMAND) の処理がそれぞれ異なるプロシージャで行われていることです。

メッセージクラッカ - Web/DB プログラミング徹底解説

これである程度は関数を分離する事ができる訳ですが,何らかの状態を保持する必要が出てきた場合に苦しくなります.状態の保持に関しては,Win32 API だとウィンドウ生成時に LPARAM と言う引数に任意のデータを渡せるので,この引数に適当な構造体なりクラス(へのポインタ)を指定して,そこに状態を保存すると言う方法がよく取られるようです.

ただし,この方法を用いた場合,WM_CREATE (もしくは WM_INITDIALOG) で渡された任意のユーザデータをどこかに記憶しておく必要があり,何も考えずにやると関数内の static 変数に記憶すると言う形になってしまいます.モーダルダイアログ等,「同時には一つのウィンドウしか開かない」と言う前提があるようなケースではこれでも良いのですが,そうではない場合には問題になってきます.

この問題への回避策としては,HWND とユーザデータ (構造体やクラスへのポインタ) のマップを保持できる static 変数を関数内に持って,そのマップへ適切なタイミングで追加/削除を行う事で回避すると言うパターンが多いようです.

これをもう少し推し進めて,1つのゲートウェイ的な (static な) WndProc 関数と,インスタンス毎の (static ではない) WndProc を用意して,ユーザ(プログラマ)から見ると WndProc が自分のウィンドウクラスの通常のメンバ関数の一つであるように振舞わせると,記述が楽に書けるようになると予想されます(下記の例はモーダルダイアログの場合).

class CommonDialog {
public:
    virtual ~CommonDialog() {}
    
    /* --------------------------------------------------------------------- */
    //  ShowDialog
    /* --------------------------------------------------------------------- */
    int ShowDialog(HWND owner, const TCHAR* resource_name) {
        return ::DialogBoxParam(::GetModuleHandle(NULL), resource_name, owner, CommonDialog::StaticWndProc, reinterpret_cast<LPARAM>(this));
    }
    
    /* --------------------------------------------------------------------- */
    //  Handle
    /* --------------------------------------------------------------------- */
    HWND& Handle() { return handle_; }
    const HWND& Handle() const { return handle_; }

protected:
    /* --------------------------------------------------------------------- */
    /*
     *  Close
     *
     *  EndDialog を呼び出す際に (HWND, CommonDialog*) のマップから
     *  削除する必要があるので,代わりにこちらを使用する.
     */
    /* --------------------------------------------------------------------- */
    void Close(int result) {
        GetInstanceMap().erase(this->Handle());
        EndDialog(this->Handle(), result);
    }
    
    /* --------------------------------------------------------------------- */
    /*
     *  WndProc
     *
     *  メッセージ毎に対応するメンバ関数へ分岐させる.
     */
    /* --------------------------------------------------------------------- */
    virtual BOOL WndProc(UINT uMsg, WPARAM wParam, LPARAM lParam) {
        switch (uMsg) {
        case WM_INITDIALOG: return this->OnCreate(wParam, lParam);
        case WM_COMMAND:    return this->OnCommand(wParam, lParam);
        // ...
        default: braek;
        }
    }
    
    virtual BOOL OnCreate(WPARAM wParam, LPARAM lParam) { ... }
    virtual BOOL OnCommand(WPARAM wParam, LPARAM lParam) { ... }
    // ...
    
private:
    /* --------------------------------------------------------------------- */
    /*
     *  GetInstanceMap
     *
     *  HWND と CommonDialog クラスのインスタンスの対応関係を保持するため
     *  のマップ.StaticWndProc 関数がこのマップを利用して,該当する
     *  クラスの WndProc へと分岐させる.
     *
    /* --------------------------------------------------------------------- */
    typedef std::map<HWND, EncodingDialog*> instance_map_type;
    static instance_map_type& GetInstanceMap() {
        static instance_map_type v_;
        return v_;
    }
    
    /* --------------------------------------------------------------------- */
    //  StaticWndProc
    /* --------------------------------------------------------------------- */
    static BOOL CALLBACK StaticWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
        instance_map_type& v = GetInstanceMap();
        if (uMsg == WM_INITDIALOG) {
            assert(v.find(hWnd) == v.end());
            
            CommonDialog* instance = reinterpret_cast<CommonDialog*>(lParam);
            instance->Handle() = hWnd;
            v.insert(std::make_pair(hWnd, instance));
            break;
        }
        
        instance_map_type::iterator pos = v.find(hWnd);
        if (pos != v.end()) return pos->second->WndProc(uMsg, wParam, lParam);
        return FALSE;
    }
};

こう言った基底クラスをあらかじめ定義しておき,ユーザはこの基底クラスを継承して必要な OnXXX メンバ関数をオーバーライドすると言う方法を取ることで,WndProc 関数がカオスになると言う状況を回避できる事が期待できます.既にある実装だと Win32++ と言うプロジェクトがこの方針を基に各種ウィンドウ/ダイアログクラスを定義しているようです.使う機会がなかったのでちょっと追っていませんが,MFC もこの方針で実装されていたのでしょうか.

問題としては,WM_COMMAND に必要な処理が集中すると言うケースが多いため,この方針で関数に分離していっても OnCommand() メンバ関数がカオスになりがちです.この辺りは,ユーザ (継承したクラスを書く人)が注意しながら書く位しか今のところは思いつきません.

RegistryKey.GetValue() を C++ でどう表現するか?

Win32 API のうち,よく使うものについてはラッパクラスを書いていこうと思っています.今は,C++ (Win32 API) か C# (.NET Framework) でコードを書くことが多いので,Win32 API のラッパクラスのインタフェースは .NET Framework に似せて定義していく事を考えています.

C# から C++ に移植する上で問題となりやすいものの一つに,「Object 型で返される関数を C++ でどう表現するか?」と言うものがあるように思います.例えば,ある特定のレジストリ値を取得するための C# の関数 (メソッド) の定義は以下のようになっています.

public Object GetValue(string name)

これを C++ で表現する方法は,パッと思いついた限り,以下の 3 つの方法があります.

  1. GetValueXXX() のように型によって別々の名前の関数を定義する.
  2. boost::any を使う.
  3. GetValue() をテンプレート関数にして特殊化する.

GetValueXXX() のように型によって別々の名前の関数を定義する

この方法は,Active Template Library (ATL) でも取られており,最もシンプルな方法になるかと思います.今回は別の解決策を取りましたが,この方法が最も無難かなぁと言う気はします.

boost::any を使う

C# の Object 型の代替手段として boost::any を使うと言うのも思いつきやすい方法の一つです.boost::any を使うと GetValue() の定義,および使用側のコードは以下のような形になります.

class RegistryKey {
public:
    typedef TCHAR char_type;
    typedef std::basic_string<TCHAR> string_type;
    typedef boost::any object_type;
    
    object_type GetValue(const string_type& name) {
        object_type dest;
        
        // 必要な処理 ...
        
        return dest;
    }
};

int main(int argc, char* argv[]) {
    RegistryKey subkey = Registry::CurrentUser().OpenSubKey(_T("foo"));
    DWORD value = boost::any_cast<DWORD>(subkey.GetValue(_T("bar")));
    return 0;
}

使用感は,この方法が最も C# の該当関数に近いものになるような気はします.

GetValue() をテンプレート関数にして特殊化する

最終的に,今回採用した方法はこれでした.この方法は,はじめテンプレート・メンバ関数は特殊化できないものと思い込んでいたので敬遠していたのですが,関数テンプレートの特殊化について - memologue を見ると,クラスのスコープ外に記述すれば可能なようです.

class RegistryKey {
public:
    typedef TCHAR char_type;
    typedef std::basic_string<TCHAR> string_type;
    
    template <class ObjectT>
    ObjectT GetValue(const string_type& name);
};

// DWORD, std::string 等で特殊化していく
template <>
inline DWORD RegistryKey::GetValue(const string_type& name) {
    // ...
}

int main(int argc, char* argv[]) {
    RegistryKey subkey = Registry::CurrentUser().OpenSubKey(_T("foo"));
    DWORD value = subkey.GetValue<DWORD>(_T("bar"));
    return 0;
}

まだ途中ですが,https://github.com/clown/psdotnet でこの方法でのラッパを公開しています.

Typesafe Enum イディオム

enum を使って定数を定義する場合に,それらの値を使うときは「型名::値」と言う形で記述したいなぁとよく思うのですが,普通の記述 (enum 型名 { ... };) だとこの要求を満たせません.それで,これまで enum を使うときは enum の型名にあたる部分を名前空間に書くことでそれっぽい記述ができるようにしていました.

namespace Fruits {
    enum { Apple, Orange, Lemon, Banana };
}

int main(int argc, char* argv[]) {
    int kind = Fruits::Orange;
    
    switch (kind) {
        case Fruits::Apple:  ...;
        case Fruits::Orange: ...;
        case Fruits::Lemon:  ...;
        case Fruits::Banana: ...;
        default: break;
    }
    
    return 0;
}

ただ,この方法だと受け取る変数の型は int や std::size_t 辺りになるのが難点でした.上記の例で言うと,

Fruits kind = Fruits::Orange;

と言う記述ができるのがベストなのですが,その要求に応える方法が思いつきませんでした.

そんな不満を抱きながら Web を漁っていると「Typesafe Enum イディオム」と言うページが引っかかりました.

template<typename def, typename inner = typename def::type>
class safe_enum : public def
{
    typedef typename def::type type;
    inner val;
 
public:
    safe_enum(type v) : val(v) {}
    inner underlying() const { return val; }
 
    bool operator == (const safe_enum & s) const { return this->val == s.val; }
    bool operator != (const safe_enum & s) const { return this->val != s.val; }
    bool operator <  (const safe_enum & s) const { return this->val <  s.val; }
    bool operator <= (const safe_enum & s) const { return this->val <= s.val; }
    bool operator >  (const safe_enum & s) const { return this->val >  s.val; }
    bool operator >= (const safe_enum & s) const { return this->val >= s.val; }
};
More C++ Idioms/Type Safe Enum

これを使って "enum" を定義すると,最初の要求をかなりの部分まで満たしてくれます.

namespace detail {
    struct Fruits_ {
        enum type { Apple, Orange, Lemon, Banana };
    };
}
typedef safe_enum<detail::Fruits_> Fruits;

int main(int argc, char* argv[]) {
    Fruits kind = Fruits::Orange;
    
    switch (kind.underlying()) {
        case Fruits::Apple:  ...;
        case Fruits::Orange: ...;
        case Fruits::Lemon:  ...;
        case Fruits::Banana: ...;
        default: break;
    }
    
    return 0;
}

「Typesafe Enum イディオム」自体は,

  • enum A 型の値と enum B 型の値を比較しようとするとエラーになるようにしたい
  • enum A 型に存在しない値は作れない (enum A 型の変数に代入できない)ようにしたい

辺りの要求が出発点となっているようです.ただ,それ以外にも「switch 文の case に指定できるようにする」や,今回の私の要求である「型名::値と表記したい」などの要求にも応えるにはどうすれば良いかと,いろいろな人がいろいろな方法を検討している(いた?)ようです.

Hello, Haru Free PDF Library!

C++ での PDF ライブラリだと Haru Free PDF Library の評判が良いようなので,このライブラリの使い方を少しずつメモっていこうと思います.取り合えず,最も基本的なところ(PDF ドキュメント作成からファイルへの保存まで)とテキスト処理まで.

基本形

HPDF_New() で初期化して,必要な処理をいろいろやって HPDF_SaveToFile() でファイルに書き出す,と言うのが基本形となります.

#include <cstdio>
#include <stdexcept>
#include <hpdf.h>

/* ------------------------------------------------------------------------- */
/*
 *  trivial_error_handler
 *
 *  最も簡易的なエラーハンドラー.HPDF_*** から渡された 2 種のエラー番号
 *  をメッセージとして std::runtime_error() を投げる.
 */
/* ------------------------------------------------------------------------- */
void trivial_error_handler(HPDF_STATUS error_no, HPDF_STATUS detail_no, void* user_data) {
    char message[1024] = {};
    sprintf(message, "error: error_no = %04X, detail_no = %d", static_cast<unsigned int>(error_no), static_cast<int>(detail_no));
    throw std::runtime_error(message);
}

/* ------------------------------------------------------------------------- */
//  main
/* ------------------------------------------------------------------------- */
int main(int argc, char* argv[]) {
    HPDF_Doc document = HPDF_New(trivial_error_handler, NULL);
    if (!document) return -1;
    
    int status = 0;
    try {
        /*
         * 日本語の場合.
         * 90ms-RKSJ-H, 90ms-RKSJ-V, 90msp-RKSJ-H, EUC-H, EUC-V が利用可能となる.
         */
        HPDF_UseJPEncodings(document);
        
        /*
         * MS-(P)Mincyo, MS-(P)Gothic が利用可能となる.
         * これらのフォントを使用する場合にはコールする(必要なければ呼ばなくても良い?).
         */
        HPDF_UseJPFonts(document);
        
        /*
         * それ以外のフォントを使用する場合,予めフォントファイルをロードしておく必要がある.
         * ロードしたフォントは,関数から返されたフォント名を用いてアクセスする事になるため,
         * このフォント名をどこかで記憶しておく必要がある.
         */
        
        /*
         * 拡張子が *.ttf のファイルの場合.
         * 第 3 引数はフォントを埋め込むかどうか (HPDF_TRUE or HPDF_FALSE).
         */
        // const char* name = HPDF_LoadTTFontFromFile(document, "folder/filename.ttf", HPDF_TRUE);
        
        /*
         * 拡張子が *.ttc のファイルの場合.
         * *.ttc ファイルには複数のフォントが格納されているため,どのフォントを使用するかを
         * 第 3 引数にインデックスで指定する.
         * 第 4 引数はフォント埋め込みの有無 (HPDF_TRUE or HPDF_FALSE) を指定する.
         */
        // const char* name = HPDF_LoadTTFontFromFile2(document, "folder/filename.ttc", 0, HPDF_TRUE);

        /*
         * メイン (?) 処理.
         * 基本的には,HPDF_AddPage() で新しいページを作成し,
         * 返された HPDF_Page ポインタに対して表示したいコンテンツを追加していくと言う形となる.
         */
        {
            HPDF_Page page = HPDF_AddPage(document);
            // ... do something ...
        }
    
        HPDF_SaveToFile(document, "folder/filename.pdf");
    }
    catch (std::runtime_error& err) {
        std::cerr << err.what() << std::endl;
        status = -1;
    }
    
    HPDF_Free(document);
    return status;
}

テキスト処理

テキスト処理に関しては,PDF ファイルに "BT ... ET" と実際に記述する部分が薄くラップされているだけの形になっているので,PDF ファイルの内部 (stream 内部) 構造を知っていると各関数の役割がイメージしやすくなります.PDF ファイルの構造については,PDF by Hand - Kobu.Com 等を参照下さい.

HPDF_Page page = HPDF_AddPage(document);

/*
 * 使用するフォントをフォント名で取得する.
 * 第 2 引数に指定する文字列は,Courie, Helvetica, MS-Gothic, MS-Mincyo などや
 * HPDF_LoadTTFontFromFile() で返されたフォント名.
 * 第 3 引数にはエンコーディングを指定する.
 * 90ms-RKSJ-H は Shift_JIS (正確には CP932 か) の横書き.
 * ここで,例えば 90ms-RKSJ-V と指定すると,これ以降に出力するテキストは縦書きになる.
 */
HPDF_Font font = HPDF_GetFont(document, "font name", "90ms-RKSJ-H");

/*
 * PDF の (テキスト出力の) フォーマットは,"BT ... ET" と言う形になっている.
 * そのため,テキスト出力開始時に HPDF_Page_BeginText() を
 * テキスト出力終了時に HPDF_Page_EndText() を呼ぶ必要がある.
 */
HPDF_Page_BeginText(page);
{
    /*
     * 出力するテキストのフォントを設定する.
     * 第 2 引数にフォント名,第 3 引数にフォントサイズを指定する.
     */
    double font_size = 10.5;
    HPDF_Page_SetFontAndSize(page, font, font_size);
    
    /*
     * 出力するテキストの色を RGB で設定する.
     * ただし,PDF では RGB 各値を [0,1] で指定することになっている.
     * そのため,白 (#ffffff) は (1, 1, 1), 灰色 (#7f7f7f) は (0.5, 0.5, 0.5) となる.
     */
    HPDF_Page_SetRGBFill(page, 0, 0, 0);
    
    /*
     * 文字列を出力する方法は,大きく分けて 3 通りある.
     *
     * 1. HPDF_Page_TextOut() で出力.
     * HPDF_Page_TextOut() には,(x, y)座標,文字列を指定する.
     * この場合は,ここで指定された座標を始点として表示される.
     * ただし,PDF の座標は左下が原点となる.そのため,多くの場合は (0, page_height)
     * のように,y 座標の指定には注意する必要がある.
     *
     * HPDF_Page_TextOut(page, x, y, description);
     *
     *
     * 2. HPDF_Page_MoveTextPos() で移動しながら HPDF_Page_ShowText() で出力.
     * HPDF_Page_MoveTextPos() は,移動させる数値を現在の座標からの相対座標で指定する.
     * 始点となる座標をいったん決めて,(改行などで) 少しずつ移動させながらテキストを
     * 順次出力していく場合には,これら 2 つの関数を使って出力していく.
     * 尚,この際も PDF の原点に注意する.例えば,横書きで改行しながらテキストを
     * 出力する場合は,以下のようになる.
     *
     * HPDF_Page_MoveTextPos(page, 0, -font_size);
     * HPDF_ShowText(page, description);
     *
     *
     * 3. HPDF_Page_SetTextMatrix() を使用.
     * HPDF_Page_SetTextMatrix() は,拡大/縮小/回転/移動を 3x2 行列で指定するためのものである.
     * 例えば,1. の HPDF_Page_TextOut(page, x, y, description) を行列を用いて表すと
     * 以下のようになる.
     *
     * HPDF_Page_SetTextMatrix(page, 1, 0, 0, 1, x, y);
     * HPDF_ShowText(page, description);
     *
     * その他,簡単な例として,
     *
     * テキストを 2倍に拡大する場合:
     * HPDF_Page_SetTextMatrix(page, 2, 0, 0, 2, x, y);
     *
     * テキストを 60 度回転させる場合:
     * double radian = 60 / 180.0 * 3.141592;
     * HPDF_Page_SetTextMatrix(page, cos(radian), sin(radian), -sin(radian), cos(radian), x, y);
     *
     * 等がある.
     *
     */
    double x = 10.0;
    double y = HPDF_Page_GetHeight(page) - font_size;
    HPDF_Page_SetTextMatrix(page, 1, 0, 0, 1, x, y);
    HPDF_ShowText(page, "description");
     
    /*
     * その他,文字間隔の調整,文字の見た目 (中抜きされた文字等) などテキスト出力には
     * 様々なオプションが存在する.詳細は,
     * http://libharu.sourceforge.net/page_handling.html
     * http://libharu.sourceforge.net/demo/text_demo.c
     * などを参照.
     */
    // HPDF_Page_SetTextRenderingMode (page, HPDF_STROKE);
    // HPDF_Page_SetCharSpace(page, 1.5);
    // HPDF_Page_SetWordSpace(page, 2.5);
}
HPDF_Page_EndText(page);

C++ ラッパ

Haru Free PDF Libray を C++ でラッパしたものを作成しようと思ったら POCO C++ Libraries の sandbox にありました.まだ正式版ではないようなのですが,どんな感じになっているのか一度見てみようと思います.

シェルによるファイルコピー

シェルによるファイルコピーで嵌ったのでメモ.

Win32 API でファイルのコピーを行う方法として,CopyFile() の他にシェルを利用すると言うものがあります.今回 CopyFile() を使用せずにシェルの機能を利用した理由は,「コピーに長い時間を要する際にプログレスバーを表示してくれるから」と言うものでした.

シェルによるコピー自体は,SHFILEOPSTRUCT 構造体に情報を設定して SHFileOperation() を実行するだけなのですが,最初にこれを実行したとき何故かエラーが発生してうまくいきませんでした.何故かなぁと思ってリファレンスを読んでいると,以下の一文を見つけました.

Important You must ensure that the source and destination paths are double-null terminated. A normal string ends in just a single null character. If you pass that value in either the source or destination members, the function will not realize when it has reached the end of the string and will continue to read on in memory until it comes to a random double null value. This can at least lead to a buffer overrun, and possibly the unintended deletion of unrelated data.

// WRONG
LPTSTR pszSource = L"C:\\Windows\\*";

// RIGHT
LPTSTR pszSource = L"C:\\Windows\\*\0";

To account for the two terminating null characters, be sure to create buffers large enough to hold MAX_PATH (which normally includes the single terminating null character) plus 1.

http://msdn.microsoft.com/en-us/library/bb759795.aspx

よく分からないですが,SHFILEOPSTRUCT 構造体の pFrom/pTo に指定する文字列は 2連続 NULL 文字で終端していないとダメだそうです.そんな訳で,シェルによるファイルコピーは以下のような感じになります.

#include <string>
#include <vector>
#include <windows.h>
#include <tchar.h>

bool shell_copy(const std::basic_string<TCHAR>& src, const std::basic_string<TCHAR>& dest) {
    // NOTE: pFrom/pTo に指定する文字列は 2連続 NULL 文字で終端していなければならない.
    std::vector<TCHAR> src_buffer(src.begin(), src.end());
    src_buffer.push_back(0);
    src_buffer.push_back(0);
    
    std::vector<TCHAR> dest_buffer(dest.begin(), dest.end());
    dest_buffer.push_back(0);
    dest_buffer.push_back(0);
    
    SHFILEOPSTRUCT op = {};
    op.hwnd   = NULL;
    op.wFunc  = FO_COPY; // 他に,FO_DELETE, FO_MOVE, FO_RENAME が存在する.
    op.pFrom  = reinterpret_cast<const TCHAR*>(&src_buffer[0]);
    op.pTo    = reinterpret_cast<const TCHAR*>(&dest_buffer[0]);
    op.fFlags = FOF_NOCONFIRMATION | FOF_NOCONFIRMMKDIR | FOF_NOERRORUI; // 必要に応じて変える.
    
    if (SHFileOperation(&op)) return false;
    return true;
}

コントロールのサブクラス化

しばらくブログはお休みモード.今,TreeView を持つアプリを書いているのですが,TreeView のアイテムの名前を変更しようとしたときに,Enter キーを押すと OK ボタンにフォーカスを奪われてしまい意図した動きにならない(「名前の変更」の終了ではなく,ダイアログ自体が終了しています)と言う問題が発生しました.

この問題をどうやって回避するのかなぁと Web を漁っていると,どうやら「コントロールのサブクラス化」と呼ばれる方法で回避するようです.

static WNDPROC DefaultTreeViewEditProc; // サブクラス化する前のデフォルトのウィンドウプロシージャ
static LRESULT CALLBACK TreeViewEditProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    switch (msg) {
    case WM_GETDLGCODE:
        return DLGC_WANTALLKEYS; // Enter, Esc キーなどの処理を行った事を通知
    default:
        break;
    }
    return CallWindowProc(reinterpret_cast<WNDPROC>(DefaultTreeViewEditProc), hWnd, msg, wParam, lParam);
}

static LRESULT CALLBACK DialogProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    switch (msg) {
    case WM_NOTIFY:
    {
        LPNMHDR header = reinterpret_cast<LPNMHDR>(lp);
        if (header->code == TVN_BEGINLABELEDIT) {
            HWND edit = (HWND)SendMessage(tree_view_handle, GWLP_WNDPROC, TVM_GETEDITCONTROL, 0, 0);
            DefaultTreeViewEditProc = (WNDPROC)SetWindowLongPtr(edit, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(&EditProc));
        }
        break;
    }
    default:
        break;
    }
    return FALSE;
}

この方法を知る前は,キーボードのメッセージをフックして無理やり処理するなどしていたのですが,サブクラス化する事でだいぶスマートに処理できるようになりました.

Related Pages

Boost.勉強会#5 名古屋の雑感

Boost.勉強会#5 名古屋に行ってきました.Boost.勉強会が「Boost があるかもしれない勉強会になりつつある」と言う噂もちらほら聞きましたが,非常に濃い内容で楽しかったです.発表された方,関係者の方々いつもお疲れ様です.

以下,印象に残ったものをメモ.発表資料ほか関連リンクは,https://sites.google.com/site/boostjp/study_meeting/study5 にあります.

春のLock-Free祭り (@kumagi, id:kumagi)

意義

マルチスレッドプログラミングに置いて古典的な手法は、共有リソースにアクセスするときはロックをかけることである。ミューテックスセマフォといった排他制御ソースコードのある領域を同時に実行しないようにし、それゆえ、共有メモリの構造を破壊しないようにする。もし、他のスレッドが事前に獲得したロックをあるスレッドが獲得しようとするときは、ロックが解放されるまでスレッドの動作は停止する。

スレッドが停止することは多くの理由で望ましくない。まず、スレッドがブロックされている間は、何も出来ない。そして、スレッドが優先順位の高い処理やリアルタイム処理を行っているならば、そのスレッドを停止することは望ましくない。また、複数のリソースにロックをかけることは、デッドロック、ライブロック、優先順位の逆転を起こすことがある。さらに、ロックを使うには、並列処理の機会を減らす粒度の荒いロックを選択するか、バグを生みやすく注意して設計しないといけない粒度の細かいロックを選択するかというトレードオフ問題を生む。

Lock-freeとWait-freeアルゴリズム - Wikipedia

近年の CPU のマルチコア化にともなって,スレッド間でデータを共有する際に「ロックする事」がボトルネックとして無視できなくなりつつあるようです.そう言った問題に対する解決策として,ロックせずにスレッド間でデータ共有できる(異なるスレッドからアクセスがあってもデータ構造が壊れない)アルゴリズムが提案・研究されているそうです.

発表では,Lock-free アルゴリズムを適用したスタック,キュー,ハッシュマップ辺りに触れられていました.ハッシュマップ辺りからだんだんと付いていけなくなったのですがw,STL 辺りもコレクションクラスの次世代の実装としてこう言った事を考慮されながら行われるようになるのかなぁとか思いながら聞いていました.

Boost.Pythonの有能性 (@fate_fox, fatefox)

C++Python の橋渡しをするためのライブラリ.C++ との橋渡しと言うと SWIG 辺りを思い浮かべるのですが,Python はいち早く Boost に採用されています.その他だと id:moriyoshi さんが Boost.PHP と言うライブラリを実装していたのを思い出しました.

C++ と何かの橋渡しをする経験は自分の場合だと ns-2 のシミュレーションプログラム(C++ と tcl)を書く際に使ったくらいなので,なかなか積極的に利用する場面をイメージできないのですが,ユーザスクリプトが書けるようになると便利になるアプリケーションは数多く存在しているはずなので,できるだけ頑張ってイメージしていければなと思いました.

Boost.statechart / Boost.MSM (@PG_kura, id:kura-replace)

状態遷移を表現するためのライブラリ.これも利用用途をイメージできないでいたのですが,最後の「GUI の動きを状態遷移を用いて表現してテストしやすくする」と言う話に凄く興味を惹かれました.一度真面目に考えてみようと思ったお話.

C++0x総復習 (@wraith13, id:wraith13)

C++0x の FDIS をざーっ紹介してくれたセッション.資料が 160ページを超えてると言う力作で,C++0xチュートリアルとして凄く有用な資料でした.分かりやすく書かれているので,C++er な人は一度目を通してみると良いのではと思います.

  • 発表資料 : revise.cpp0x (PPTX, PDF)

懇親会

懇親会会場にもプロジェクタがあってなかなか濃い展開になっていました.取り合えず名古屋怖いってのは分かりましたzzZ 懇親会でしゃべっていると「ブログ読んでますよ」と言ってもらえた事があって,これは素直に嬉しかったです.このブログは気が向かないと C++ のネタを書かないと言うなんちゃって C++ ブログですが,まぁ末永くお付き合いして頂ければと思いますw

ブログに関しては,ちょっと話題になっていた static おじさんの例みたいに「飯の種(金を生むもの)を無料で公開する奴は馬鹿」みたいな記事をぽつぽつ見かけたりしますが,個人的には何だかんだで「ブログを続けておいて良かった」と思える事には度々遭遇します.「情けは人の為ならず」と言う言葉もあるように,巡り巡って自分にプラスになる事もたくさんあったりするので,そう言った記事にめげずにどんどん情報発信されるような流れになれば良いなぁと考えたりしました.

32bit プロセスは 64bit プロセスを見つけられない

所用で「あるプロセスから別のプロセスを終了させるプログラム」を書いていました.終了させる部分の関数は以下のような感じ.

#include <string>
#include <vector>
#include <windows.h>
#include <tchar.h>
#include <psapi.h>

#pragma comment(lib, "psapi.lib")

namespace process {
    std::size_t kill(const std::basic_string<TCHAR>& name) {
        std::size_t result = 0;
        
        std::vector<DWORD> processes(256, 0);
        DWORD procbytes = 0;
        while (1) {
            if (!::EnumProcesses(reinterpret_cast<DWORD*>(&processes[0]), processes.size() * sizeof(DWORD), &procbytes)) return 0;
            if (procbytes < processes.size() * sizeof(DWORD)) break;
            processes.resize(processes.size() * 2);
            procbytes = 0;
        }
        
        for (std::size_t i = 0; i < procbytes / sizeof(procbytes); ++i) {
            HANDLE process = ::OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | PROCESS_TERMINATE, FALSE, processes[i]);
            if (!process) continue;
            
            std::vector<HMODULE> modules(256, 0);
            DWORD modulebytes = 0;
            BOOL status = TRUE;
            while (1) {
                status = ::EnumProcessModules(process, reinterpret_cast<HMODULE*>(&modules[0]), modules.size() * sizeof(HMODULE), &modulebytes);
                if (!status || modulebytes < modules.size() * sizeof(HMODULE)) break;
                modules.resize(modules.size() * 2);
                modulebytes = 0;
            }
            
            if (status) {
                for (std::size_t j = 0; j < modulebytes / sizeof(modulebytes); ++j) {
                    TCHAR buffer[2048] = {}; // バッファサイズは適当
                    if (::GetModuleFileNameEx(process, modules[j], buffer, sizeof(buffer) / sizeof(TCHAR)) == 0) continue;
                    std::basic_string<TCHAR> modulename(buffer);
                    std::basic_string<TCHAR>::size_type pos = modulename.find_last_of(_T('\\'));
                    if (pos != std::basic_string<TCHAR>::npos) modulename = modulename.substr(pos + 1);
                    if (modulename == name) {
                        ::TerminateProcess(process, 0);
                        ++result;
                        break;
                    }
                }
            }
            ::CloseHandle(process);
        }
        
        return result;
    }
}

この関数(を実行するプログラム),32bit 版 Windows では問題なく動作するのですが 64bit 版 Windows ではプロセスを終了させてくれませんでした.何故かなぁと原因を調査していると,「終了させる側のプロセスが 32bit なのに対象プロセスが 64bit」だったからのようです.

Yes, you are correct here. From a 32 bit application it is not possible to enumerate the process on 64 bit Windows. So if you want to run your existing code then you need to compile the code as 64 bit.

Basically the reason why Process Explorer is able to enumerate modules of both 32-bit and 64-bit processes at the same time is, when process explorer launches , it also have a 64 bit version with it .

EnumProcessModules() doesn't work on Windows 2008 Server (64 bit)

Windows Vista 以降だと EnumProcessModulesEx と言う関数が用意されており,この引数に 32bit プロセスを対象とするか 64bit プロセスを対象とするかを指定するフラグがあったので,Vista 以降ならいけるかなぁと期待したのですが 32bit → 64bit の場合は無理でした.

This function is intended primarily for 64-bit applications. If the function is called by a 32-bit application running under WOW64, the dwFilterFlag option is ignored and the function provides the same results as the EnumProcessModules function.

EnumProcessModulesEx function (Windows)

そんな訳で,64bit のプロセスを終了させる方法は,FindWindow() を使ってウィンドウのタイトルからハンドルを取得して終了メッセージを送るか,64bit 版のものを作るしかないようです.

ディレクトリ内に存在するファイル数の取得方法の比較

あるディレクトリ内のファイル(+ディレクトリ)数を取得する必要があったのですが,Boost.Filesystem と FindFirstFile とでどの程度の速度差になるのかなと思い調べてみました.まず,Boost.Filesystem 版のファイル数カウント関数.

#include <string>
#include <boost/filesystem.hpp>

std::size_t file_count_boost(const boost::filesystem::path& root) {
    namespace fs = boost::filesystem;
    if (!fs::exists(root) || !fs::is_directory(root)) return 0;
    
    std::size_t result = 0;
    fs::directory_iterator last;
    for (fs::directory_iterator pos(root); pos != last; ++pos) {
        ++result;
        if (fs::is_directory(*pos)) result += file_count_boost(pos->path());
    }
    
    return result;
}

次に FindFirstFile を使ったファイル数カウント関数.

#include <string>
#include <windows.h>
#include <tchar.h>

std::size_t file_count_native(const std::basic_string<TCHAR>& src) {
    std::basic_string<TCHAR> root = src + _T("\\*.*");
    WIN32_FIND_DATA wfd = {};
    HANDLE handle = FindFirstFile(root.c_str(), &wfd);
    if (handle == INVALID_HANDLE_VALUE) return 0;
    
    std::size_t result = 0;
    do {
        std::basic_string<TCHAR> path(wfd.cFileName);
        if (path != _T(".") && path != _T("..")) {
            ++result;
            if ((wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) result += file_count_native(src + _T('\\') + path);
        }
    } while (FindNextFile(handle, &wfd));
    FindClose(handle);
    
    return result;
}

実行結果

テストは,実行環境にある boost_1_46_1 ディレクトリを指定して調べてみました.実行環境の boost_1_46_1 ディレクトリには,37,834 ファイルと 3,233 ディレクトリが存在するようです.

Boost.Filesystem を使用した場合
$ time ./file-scanning-test.exe
41067

real    0m3.135s
user    0m0.000s
sys     0m0.015s

FindFirstFile を使用した場合
$ time ./file-scanning-test.exe
41067

real    0m0.363s
user    0m0.031s
sys     0m0.000s

実行すると 1桁くらい実行速度が変わっていました.さすがに 1桁も変わるとは思っていなかったので驚きました.やはり変なものを指定される(馬鹿でかいファイルとか)可能性のある部分は,ネイティブ(ここだと Win32 API)で書いておかないとまずい場合があると言う事でしょうか.