リストビューから文字列を取得する方法として真っ先に思い浮かぶのは、LVM_GETITEM か LVM_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 (=いじくるつくーる の実行ファイル名)となっています。なぜ、このようなことになってしまうのでしょう?
まず、次のことを知っておかなければなりません。
各プロセスは、それぞれ独自の環境で動いているかのように見せかけられます。メモリのアドレスに対して言えば、あるプロセスにて 0x11223344 という数値で表されるメモリ位置と、別のプロセスで 0x11223344 という数値で表されるメモリ位置は、全く別のアドレスを指すことになります。
あるプロセスが他のプロセスからメモリ領域を侵害されることは、通常は起こりえません。強制的行おうとしても失敗します。
他のプロセスにあるリストビューに、メモリへの文字列書き込みを指示した場合、リストビューは他のプロセス内のメモリに対して書き込みを行います。自プロセスにあるメモリに書き込みを行ってはくれません。
前回示したリストビューのアイテム数の取得の場合と違って、文字列データは大きいため、文字列はいったんメモリに格納され、そのメモリの先頭アドレスを引き渡す方法にて、データのやりとりが行われます。
つまり、図示すると、こんな状態になっています。

メモリへの書き込み指示を出したのはlvcsvcomですが、実際に書き込みを行ったのはリストビューを所持する Rnsf7.exe であることから、エラーは Rnsf7.exe にて発生することになります。ちなみにこのエラーの詳細を調べると、「ページ違反」となっています。
この問題を解決するためには、他プロセスにメモリ確保を行わせ、さらにリストビューに対してそのメモリに対して書き込みを行わせ、最後に他プロセスのメモリからデータを読み取るという手続きを取らなければなりません。その方法については、次回に述べたいと思います。