Written in Japanese(UTF-8)
2014.8.30
INASOFT


/トップ/ListView to CSV/ダウンロード/WebHelp/ヘルプトップ

Icon ListView to CSVを作ってみよう!!


ListView to CSVの簡単なコマンドライン版を作ってみようという、プログラミング解説講座です。
第2回では、他プロセスの所有するリストビュー コントロールから文字列を取得する際の失敗例について述べたいと思います。

第2回

第1回へ←   →第3回へ


■サンプルのソースコード

リストビューから文字列を取得する方法として真っ先に思い浮かぶのは、LVM_GETITEMLVM_GETITEMTEXT メッセージを使って文字列を取得する方法です。

まずは、他プロセスのリストビューから文字列を取得する際の失敗例として、サンプルコードから示したいと思います。このコードはうまくうごきません。


// ListView to CSV for CommandLine (lvcsvcom.cpp)
// Copyright(C) 2005 T.Yabuki

#include <windows.h>
#include <commctrl.h>
#include <stdio.h>

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

#include <iostream>
#include <iomanip>

using namespace std;

#define MAX_GETITEMTEXT_LENGTH   1024
#define MAX_GETWINDOWTEXT        79
#define MAX_GETWINDOWCLASS       79

// --------------------------------------------------------------
// EnumChildWindowsにより呼び出されるコールバック関数
// --------------------------------------------------------------
// 引数 :hWnd = コントロールのハンドル
//     p_lpszTitleBar = タイトルバー文字列へのポインタ
// 戻り値:TRUE(必ずEnumChildWindowsを継続させる)
// --------------------------------------------------------------
BOOL CALLBACK callback_EnumChildWindowsProc(HWND hCtrl, LPARAM p_lpszTitleBar)
{
    char szClassName[MAX_GETWINDOWCLASS+1];
    char *lpszTitleBar = (char *)p_lpszTitleBar;

    // クラス名を取得する
    if (GetClassName(hCtrl, szClassName, MAX_GETWINDOWCLASS)) {
        if ( strcmp(szClassName, WC_LISTVIEW) == 0 ) {
            // クラス名がWC_LISTVIEW(文字列"SysListView32"を表す)であれば
            // リストビューであると見なす→ウィンドウハンドルと要素数を表示。

            // 各ウィンドウにつき1回ずつ、タイトルバー文字列を表示する。
            // タイトルバー文字列を1回表示したら、lpszTitleBarの最初の
            // 1バイトをNULL文字にしておく。これにより、タイトルバー文字列が
            // 表示済みであることを表すことにする。
            if ( lpszTitleBar && lpszTitleBar[0] ) {
                cout << "--------------------" << endl << lpszTitleBar << endl;
                lpszTitleBar[0] = '\0';
            }

            // ウィンドウハンドルを8桁の16進数で表示
            cout << "  ハンドル番号:" << setw(8) << setfill('0') << hex << (UINT)hCtrl;

            // 要素数を表示
            cout << "  アイテム数:" << dec << ListView_GetItemCount(hCtrl) << endl;
        }
    }
    return TRUE;
}

// --------------------------------------------------------------
// EnumWindowsにより呼び出されるコールバック関数
// --------------------------------------------------------------
// 引数 :hWnd=ウィンドウハンドル
// 戻り値:TRUE(必ずEnumWindowsを継続させる)
// --------------------------------------------------------------
BOOL CALLBACK callback_EnumWindowsProc(HWND hWnd, LPARAM)
{
    char szTitleBar[MAX_GETWINDOWTEXT+1] = "????????????????";

    // 調査中のウィンドウのタイトルバーを取得しておく
    // (コマンドプロンプトの窓は横幅80カラムであると仮定するので、
    //  タイトルバーの文字列は最大で79バイトまで取得する)
    GetWindowText(hWnd, szTitleBar, MAX_GETWINDOWTEXT);

    // もし、タイトルバーが空っぽであれば、「タイトルなし」の文字列を代入する。
    if (szTitleBar[0] == '\0') {
        strcpy(szTitleBar, "タイトルなし");
    }

    // コントロールの一覧を列挙する
    // (コントロール1つにつき1回ずつ、callback_EnumChildWindowsProc() が呼ばれる)
    EnumChildWindows(hWnd, (WNDENUMPROC)callback_EnumChildWindowsProc, (LPARAM)szTitleBar);

    // 返却値TRUE:EnumWindowsによるウィンドウハンドルの列挙を継続する
    return TRUE;
}

// --------------------------------------------------------------
// リストビューコントロールの一覧を作成
// --------------------------------------------------------------
// 引数 :なし
// 戻り値:なし
// --------------------------------------------------------------
void showListViewControls()
{
    // ウィンドウの一覧を列挙する
    // (ウィンドウ1つにつき1回ずつ、callback_EnumWindowsProc() が呼ばれる)
    EnumWindows((WNDENUMPROC)callback_EnumWindowsProc, 0);
}

// xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
// 指定されたハンドルのリストビューの内容を出力する(間違い)
// xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
// 引数 :hListView = リストビューのハンドル
//     iColumns  = 最大カラム数
// 戻り値:1=成功  0=失敗
// xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
int showListViewItems(HWND hListView, int iColumns)
{
    char szClassName[MAX_GETWINDOWCLASS+1];
    char szItemText[MAX_GETITEMTEXT_LENGTH+1];

    // クラス名を取得する
    if (GetClassName(hListView, szClassName, MAX_GETWINDOWCLASS)) {
        if ( strcmp(szClassName, WC_LISTVIEW) == 0 ) {
            // クラス名がWC_LISTVIEW(文字列"SysListView32"を表す)であれば
            // リストビューであると見なす

            // アイテム数を取得
            int iItemCount = ListView_GetItemCount(hListView);
            LVITEM lvItem;
            
            ZeroMemory(&lvItem, sizeof(LVITEM));
            lvItem.mask       = LVIF_TEXT;
            lvItem.cchTextMax = MAX_GETITEMTEXT_LENGTH;
            lvItem.pszText    = szItemText;

            // アイテムを表示
            for(lvItem.iItem=0 ; lvItem.iItem<iItemCount ; lvItem.iItem++) {
                lvItem.iSubItem = 0;
                // LVM_GETITEMでアイテムの文字列を取得
                // FALSEが返されたら一番右端の文字列を読んだ後とみなして次のアイテムへ
                for(lvItem.iSubItem=0 ; lvItem.iSubItem<iColumns ; lvItem.iSubItem++) {
                    SendMessage(hListView, LVM_GETITEM, 0, (LPARAM)&lvItem);
                    // 最初のカラムでなければ、区切りのカンマを表示する
                    if (lvItem.iSubItem > 0) {
                        cout << ",";
                    }

                    // 文字列を表示
                    cout << "\"" << lvItem.pszText << "\"";
                }

                // 改行を出力
                cout << endl;
            }
            return 1;
        }
        else {
            cerr << "指定されたハンドルはリストビュー コントロールではありません。" << endl;
            return 0;
        }
    }
    else {
        cerr << "指定されたハンドルのクラス名を取得できません。" << endl;
        return 0;
    }
}

// --------------------------------------------------------------
// メイン関数
// --------------------------------------------------------------
// 引数:1個の場合→環境内のリストビューのハンドル番号一覧を表示
//    2個の場合→エラー
//    2個以上の場合→1つめのパラメタ(16進数)で指定されたハンドル番号のリストビューを出力
//            2つめのパラメタ(10進数)で、リストビューのカラムの数を指定する
// 戻り値:0=成功  1=失敗
// --------------------------------------------------------------
int main(int argc, char* argv[])
{
    // コモンコントロールの初期化
    InitCommonControls();

    // コマンドラインの解析
    if ( argc < 2 ) {
        // コマンドライン引数が何も指定されていない場合は、
        // リストビューコントロールのハンドル番号一覧を表示。
        showListViewControls();
        return 0;
    }
    else if ( argc < 3 ) {
        // エラー。
        cerr << "リストビューのカラム数を指定してください。" << endl;
        return 1;
    }
    else {
        DWORD dwHandle;
        int   iColumns;

        sscanf(argv[1], "%x", &dwHandle);
        sscanf(argv[2], "%d", &iColumns);
        return (!showListViewItems((HWND)dwHandle, iColumns));
    }
}

■どうなってしまうか?

まず、上のサンプルコードのような方法だと、失敗します。以下の説明では、新たに作ったプログラムの実行ファイル名を「lvcsvcom.exe」であるとして扱っています。

上記のコードを実行するためには、まずは何の引数指定もなしでこのプログラムを実行して目的のリストビューのハンドルを調べて、次に、目的のリストビューのハンドルと取得したいかサブ項目数をオプション指定して実行します。


C:\>lvcsvcom
--------------------
いじくるつくーる
  ハンドル番号:000005d4  アイテム数:41
--------------------
エクスプローラ - Windows95(C:)
  ハンドル番号:000002a8  アイテム数:9
--------------------
Program Manager
  ハンドル番号:000000fc  アイテム数:12

C:\>lvcsvcom 5d4 5
(ハンドル番号 5d4 のリストビューから、5カラムのサブアイテムまでを取得する)

lvcsvcom 5d4 5を実行すると、次のような画面になります。

出力結果

なぜ、エラーとなってしまうのでしょう? また、エラーを起こしているのはlvcsvcomではなく、Rnsf7.exe (=いじくるつくーる の実行ファイル名)となっています。なぜ、このようなことになってしまうのでしょう?

■なぜできないのか

まず、次のことを知っておかなければなりません。

つまり、図示すると、こんな状態になっています。

なぜか?

メモリへの書き込み指示を出したのはlvcsvcomですが、実際に書き込みを行ったのはリストビューを所持する Rnsf7.exe であることから、エラーは Rnsf7.exe にて発生することになります。ちなみにこのエラーの詳細を調べると、「ページ違反」となっています。

■次回は?

この問題を解決するためには、他プロセスにメモリ確保を行わせ、さらにリストビューに対してそのメモリに対して書き込みを行わせ、最後に他プロセスのメモリからデータを読み取るという手続きを取らなければなりません。その方法については、次回に述べたいと思います。

第1回へ←   →第3回へ


ここに記載の内容は2005年に書かれたものです。そのため、内容の一部が古くなっていることがありますのでご注意ください。例えば、
  • ユーザーアカウント制御(UAC)下では、一般権限のプログラムは、管理権限のプログラムから情報を取得することができません。サンプルプログラム適用時にはご注意ください。
  • 本サンプルプログラムは、x86環境下での動作を前提としています。x64環境下ではポインタサイズなどを原因とするいくつかの問題が発生しますのでご注意ください。→詳細


※このページは、ソフトウェアに付属のヘルプファイルをWeb用に再構築したものです。大部分に自動変換を施しているため、一部は正しく変換しきれずに表示の乱れている箇所があるかもしれませんが、ご容赦下さい。また、本ドキュメントはアーカイブドキュメントであり、内容は、右上の作成日付の時点のものとなっております。一部、内容が古くなっている箇所があるかと思いますが、あらかじめご了承下さい。
※このページへは、自由にリンクしていただいてかまいません。

■このページに関するご意見をお待ちしております → フィードバックページ

/トップ/ListView to CSV/ダウンロード/WebHelp/ヘルプトップ