DOBON.NET DOBON.NETプログラミング掲示板過去ログ

C#のWindowsAPIでツリーのテキストが取得できません

環境/言語:[WindowsXP,C#]
分類:[.NET]

C#(VisualStadio2008)で、WindowsAPIを使用してツリーのタイトルを取得しようとしています。
ツリーウィンドウのウィンドウハンドルやnodeは取得できているようで、ツリーの展開まではできます。しかし、そこからSendMessageのTVITEMGETを使用してタイトルを取得しようとすると取得してきた変数にはスペースのみが入っており、取得できておりません。
同様の処理をするコードをC++,VB.Net(VS2008)で書いても見ましたが同じ現象です。
以下のコードなのですが、不具合点等ご指摘いただけますと幸いです。
宜しくお願いいたします。

using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;


namespace VaultPanelControl2
{
public class VaultPNCTL2
{
public struct TVITEM
{
public uint mask;
public IntPtr hItem;
public uint state;
public uint stateMask;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)]
public string pszText;
public int cchTextMax;
public int iImage;
public int iSelectedImage;
public int cChildren;
public uint lParam;
public int iIntegral;
}


[DllImport("User32.dll", EntryPoint = "SendMessage")]
public static extern Int32 SendMessage(Int32 hWnd, Int32 Msg, Int32 wParam, Int32 lParam);

[DllImport("User32.dll", EntryPoint = "SendMessage")]
public static extern Int32 SendMessage(Int32 hWnd, Int32 Msg, Int32 wParam, ref TVITEM lParam);


//TreeViewのメッセージコード
public static int TVM_EXPAND = 0x1102; //アイテムを開く・閉じる
public static int TVM_GETNEXTITEM = 0x110A; //指定されたアイテムを取得
public static int TVM_GETITEM = 0x110C; //アイテムの属性を取得


//TVM_EXPANDに指定するwParam = flag
public static int TVE_EXPAND = 0x1102; //子アイテムのツリーを開きます

//TVM_GETNEXTITEM に指定する wParam = flag
public static int TVGN_ROOT = 0x0000; //ツリービューのルート(最も上の階層)のアイテムを取得します。

//TVITEMEXのmask
public static uint TVIF_TEXT = 0x0001; //pszText, cchTextMax


static void Main(string[] args)
{

//TreeViewのウィンドウハンドル(spy++で見て引数として渡しています)を引数で受取り16進数をLong型に変換
      int treewindow = Convert.ToInt32(args[0], 16);

//ノードのタイトルを取得して入れるための変数の宣言
string nodeTitle = Microsoft.VisualBasic.Strings.Space(256);

//トップノードの取得
int TopNode = 0;
TopNode = SendMessage(treewindow, TVM_GETNEXTITEM, TVGN_ROOT, 0);

//ツリーを開く(ここまではうまくいっています)
SendMessage(treewindow, TVM_EXPAND, TVE_EXPAND, TopNode);

//ノードの名称を取得
TVITEM tvitem = new TVITEM();
tvitem.mask = TVIF_TEXT;
tvitem.hItem = (IntPtr)TopNode;
tvitem.pszText = nodeTitle;
tvitem.cchTextMax = nodeTitle.Length;

int ret = SendMessage(treewindow, TVM_GETITEM, 0, ref tvitem);

//↓スペースが20個出力されて終わります。
MessageBox.Show(tvitem.pszText);

}
}
}
TVITEM は実体ではなくメモリアドレスを渡します。
単純に渡した場合、自分のプロセスのメモリアドレスを渡すと言うことになり、アドレス空間の異なる別プロセスではそのメモリアドレスは意味を持ちません。
別プロセスに対してメモリの読み書きを依頼する場合、VirtualAllocEx を使ってその別プロセス上でメモリを確保し、そのアドレスを渡す必要があります。
結構面倒です。
他に、読み書きに ReadProcessMemory / WriteProcessMemory、目的のウィンドウのプロセスハンドルを取得するための GetWindowThreadProcessId / OpenProcess なども必要になるでしょう。
■No29667に返信(Hongliangさんの記事)

なるほど!!そういうことでしたか・・・
ご親切にどうもありがとうございます。
その方法でやってみます。
できましたら、こちらにまた書きこませていただきます。
アドバイスいただいたようにVirtualAllocExを使って相手プロセス内にメモリを割り当て仮想メモリでやり取りしようとしたのですが、
SendMessageがエラーになってしまいます・・・どこが悪いのかお手上げの状態です。以下のコードで間違っている個所ありましたらご指摘いただけますと幸いです。

public struct TVITEM
{
public uint mask;
public IntPtr hItem;
public uint state;
public uint stateMask;
public IntPtr pszText;
public int cchTextMax;
public int iImage;
public int iSelectedImage;
public int cChildren;
public uint lParam;
public int iIntegral;
}

[DllImport("User32.dll", EntryPoint = "SendMessage")]
public static extern IntPtr SendMessage(IntPtr hWnd, Int32 Msg, Int32 wParam, UInt32 lParam);

[DllImport("User32.dll", EntryPoint = "SendMessage")]
public static extern IntPtr SendMessage(IntPtr hWnd, Int32 Msg, Int32 wParam, ref IntPtr lParam);
  [DllImport("kernel32.dll")]
public static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect);

[DllImport("kernel32.dll")]
public static extern bool VirtualFreeEx(IntPtr hProcess, IntPtr lpAddress, uint dwSize, uint dwFreeType);

[DllImport("kernel32.dll")]
public static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, IntPtr lpBuffer, int nSize, ref uint lpNumberOfBytesRead);

[DllImport("kernel32.dll")]
public static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, IntPtr lpBuffer, int nSize, ref uint lpNumberOfBytesWritten);

[DllImport("kernel32.dll")]
public static extern IntPtr OpenProcess(uint dwDesiredAccess, bool bInheritHandle, uint dwProcessId);

[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CloseHandle(IntPtr hObject);

[DllImport("kernel32.dll")]
static extern void MoveMemory(IntPtr dst, IntPtr src, int size);


//(相手)プロセスのオープン
//pidはSpy++で見て直接指定しています
      //TopNodeは取得OKです。

IntPtr pRet = OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE, false, pid);
IntPtr pProcessH = pRet;
if (pRet.Equals(IntPtr.Zero))
{
MessageBox.Show("Inventorのプロセスが取得できませんでした");
return;
}


//pszText設定用の文字列領域確保
int MAX_STR = 260;

//TVITEM構造体を宣言 初期化
TVITEM tvitem = new TVITEM();

int tvitemSize = Marshal.SizeOf(tvitem);
int dwSize = tvitemSize + MAX_STR;

//自プロセスの共有メモリ確保
//IntPtr pLocalShared = VirtualAlloc(IntPtr.Zero, (uint)dwSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
IntPtr pLocalShared = Marshal.AllocHGlobal(dwSize);

//相手プロセス内に共有メモリの確保
IntPtr pSysShared = VirtualAllocEx(pProcessH, IntPtr.Zero, (uint)dwSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (pSysShared.Equals(IntPtr.Zero))
{
Marshal.FreeHGlobal(pLocalShared);
CloseHandle(pProcessH);
return;
}

long pszTextInt = pSysShared.ToInt64() + (long)tvitemSize;
//構造体の初期化
tvitem.mask = TVIF_TEXT;
tvitem.hItem = TopNode;
tvitem.pszText = (IntPtr)pszTextInt;
tvitem.cchTextMax = MAX_STR;

Marshal.StructureToPtr(tvitem, pLocalShared, false);

//相手プロセス内にTVITEM構造体をコピー
uint retWrite = 0;
bool bRet = WriteProcessMemory(pProcessH, pSysShared, pLocalShared, dwSize, ref retWrite);
if (bRet == false)
{
Marshal.FreeHGlobal(pLocalShared);
VirtualFreeEx(pProcessH, pSysShared, (uint)dwSize, MEM_RELEASE);
CloseHandle(pProcessH);
return;
}

//SendMessage発行 ← エラーになってしまいます
pRet = SendMessage(treewindow, TVM_GETITEM, 0, ref pSysShared);
if (pRet == IntPtr.Zero)
{
MessageBox.Show("SendMessageエラー");
Marshal.FreeHGlobal(pLocalShared);
VirtualFreeEx(pProcessH, pSysShared, (uint)dwSize, MEM_RELEASE);
CloseHandle(pProcessH);
return;
}

//共有メモリから情報取得
uint retRead = 0;
bRet = ReadProcessMemory(pProcessH, pSysShared, pLocalShared, dwSize, ref retRead);
if (bRet == false)
{
Marshal.FreeHGlobal(pLocalShared);
VirtualFreeEx(pProcessH, pSysShared, (uint)dwSize, MEM_RELEASE);
CloseHandle(pProcessH);
return;
}

string sBuffer = Microsoft.VisualBasic.Strings.Space(MAX_STR);
IntPtr pBuffer = Marshal.StringToHGlobalUni(sBuffer);

MoveMemory(pBuffer, (IntPtr)(pLocalShared.ToInt64() + tvitemSize), MAX_STR);

//相手プロセス内の共有メモリを解放する
VirtualFreeEx(pProcessH, pSysShared, (uint)dwSize, MEM_RELEASE);

//自プロセス内共有メモリを解放する
//VirtualFree(pLocalShared, 0, MEM_RELEASE);
Marshal.FreeHGlobal(pLocalShared);

//相手プロセスのクローズ
CloseHandle(pProcessH);
エラーが出たのなら、そのエラーの詳細を記述しましょう。

lParam に渡すのはアドレスそのものです。アドレスのアドレスではありません。

後は気になった点として、
・WPARAM/LPARAM は IntPtr(または ref)で定義します。
・Marshal.StringToPtr*** は、バッファを新たに確保してそこに既存文字列をコピーするメソッドです。このコードでは pBuffer がリークする上に sBuffer に書き込まれることもありません。
 ポインタから String にコピーするなら、Marshal.PtrToString*** が使用できます。
他にもあるかもしれませんが、とりあえず目についたもの。
コードを掲載する場合は、投稿時に「図表モード」を選択してください。

■No29742に返信(さるあふろさんの記事)
> MoveMemory(pBuffer, (IntPtr)(pLocalShared.ToInt64() + tvitemSize), MAX_STR);
ToInt64 を使っているということは、x64 ビルドでしょうか?

> [DllImport("User32.dll", EntryPoint = "SendMessage")]
> public static extern IntPtr SendMessage(IntPtr hWnd, Int32 Msg, Int32 wParam, UInt32 lParam);
Int32 wParam, UInt32 lParam ということは、x86 ビルドでしょうか?

wParam は、Win64 では 64bit、Win32 では 32bit、Win16 では 16bitです。
lParam は、Win64 では 64bit、Win32およびWin16 では 32bitです。

それゆえ、wParam lParam に渡される値というのは、一般的に
「IntPtr」「ref 構造体」「out 構造体」「クラス」のいずれかとなります。
(ときには Int32 を渡せる場合もありますが、それは限定的な状況です)


> SendMessageがエラーになってしまいます
実行後、Marshal.GetLastWin32Error() の戻り値を取得し、
その意味を ErrLook.exe などで調べてみましょう。


> pRet = SendMessage(treewindow, TVM_GETITEM, 0, ref pSysShared);
ref IntPtr で渡しているのは何故でしょうか。

今のコードだと、共有可能なアドレスを渡すのではなく、
そのアドレスを示したローカルのアドレスが渡されてしまう気が。


> string sBuffer = Microsoft.VisualBasic.Strings.Space(MAX_STR);
わざわざ VB ランタイムを併用せずとも、
 sBuffer = new String(' ', MAX_STR);
だけで良い気がします。


――コードそのものは試していませんが、とりあえず気になった点のみ。
Hongliang様 魔界の仮面弁士様
さるあふろです。ご回答いただきどうもありがとうございました。

うわわわわ・・・ずいぶんヘタレなコードだったのですね。お恥ずかしい限りです。
Int64は単に大きいほうがいいかな。とか、あちこちのサイトでコピペしてきたものでよく意味がわかっておりませんでした。
ご丁寧に解説いただきどうもありがとうございます。なるほどと納得です。

エラーについてはSendMessageの戻り値が0になってしまう事で、SendMessageがうまくいかなかったという意味でした。
(うまくいくと0以外の戻り値が返るということでしたのでこりゃエラーかと思い込んでしまいました)
分かりにくい説明をしてしまい申し訳ありませんでした。

お陰さまでSendMessageの戻り値が0以外で戻ってくるコードに修正できました。
ご指摘の通り 最初のSendMessageの宣言部分の wParam,lParamの引数を IntPtr型に修正して、
コード内のSendMessageの第4引数にポインタそのものを指定することでうまくいきました。

<修正1>
[DllImport("User32.dll", EntryPoint = "SendMessage")]
public static extern IntPtr SendMessage(IntPtr hWnd, Int32 Msg, IntPtr wParam, IntPtr lParam);

<修正2>
pRet = SendMessage(treewindow, TVM_GETITEM, 0, ref pSysShared);
            ↓
pRet = SendMessage(treewindow, TVM_GETITEM, IntPtr.Zero, pSysShared);

しかしながら、今度は共有メモリから情報取得して表示するところで 文字化けした変な文字が帰ってきてしまいます。
何度も見直してみたのですが、どうしても解決しません・・・もしよろしければご意見を頂けますと至極幸いに存じます。


using System;
using System.Data;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Diagnostics;

namespace VaultPanelControl
{
    public class VaultPNCTL
    {
        public struct TVITEM
        {
            public uint mask;
            public IntPtr hItem;
            public uint state;
            public uint stateMask;
            public IntPtr pszText;
            public int cchTextMax;
            public int iImage;
            public int iSelectedImage;
            public int cChildren;
            public uint lParam;
            public int iIntegral;
        }

        [DllImport("User32.dll", EntryPoint = "SendMessage")]
        public static extern IntPtr SendMessage(IntPtr hWnd, Int32 Msg, IntPtr wParam, IntPtr lParam);

        [DllImport("user32.dll")]
        public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);

        [DllImport("kernel32.dll")]
        public static extern IntPtr VirtualAlloc(IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect);
        [DllImport("kernel32.dll")]
        public static extern bool VirtualFree(IntPtr lpAddress, uint dwSize, uint dwFreeType);
        
        [DllImport("kernel32.dll")]
        public static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect);
        [DllImport("kernel32.dll")]
        public static extern bool VirtualFreeEx(IntPtr hProcess, IntPtr lpAddress, uint dwSize, uint dwFreeType);

        [DllImport("kernel32.dll")]
        public static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, IntPtr lpBuffer, int nSize, ref uint lpNumberOfBytesRead);

        [DllImport("kernel32.dll")]
        public static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, IntPtr lpBuffer, int nSize, ref uint lpNumberOfBytesWritten);

        [DllImport("kernel32.dll")]
        public static extern IntPtr OpenProcess(uint dwDesiredAccess, bool bInheritHandle, uint dwProcessId);

        [DllImport("kernel32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool CloseHandle(IntPtr hObject);

     
        [DllImport("kernel32.dll")]
        static extern void MoveMemory(IntPtr dst, IntPtr src, int size);
        
        private static uint PROCESS_VM_OPERATION = 0x00000008;
        private static uint PROCESS_VM_READ =      0x00000010;
        private static uint PROCESS_VM_WRITE =     0x00000020;
        private static uint MEM_RESERVE =     0x2000;
        private static uint MEM_COMMIT =      0x1000;
        private static uint MEM_RELEASE =     0x8000;
        private static uint PAGE_READWRITE =  0x0040;

         //TreeViewのメッセージコード
        public static int TVM_EXPAND= 0x1102; //アイテムを開く・閉じる 
        public static int TVM_GETITEM= 0x110C; //アイテムの属性を取得 
        public static int  TVM_GETNEXTITEM= 0x110A; //指定されたアイテムを取得 

        //TVM_GETNEXTITEM に指定する wParam = flag
        public static int TVGN_ROOT= 0x0000; //ツリービューのルート(最も上の階層)のアイテムを取得します。
        public static int TVGN_CARET= 0x0009; //現在選択されているアイテムを取得します。

        //TVM_EXPANDに指定するwParam = flag
        public static int TVE_EXPAND= 0x1102; //子アイテムのツリーを開きます

        //TVITEMEXのmask 
        public static uint TVIF_TEXT = 0x0001; //pszText, cchTextMax

        static void Main(string[] args)
        {
            uint pid = 0xED8;                       // 相手アプリケーションのプロセスID(直接指定)
            IntPtr treewindow = (IntPtr)0x20E92;    // ツリーウィンドウのハンドル(直接指定)
            IntPtr hwindow = (IntPtr)0xA0620;       //親ウィンドウハンドル直接指定

            uint result = GetWindowThreadProcessId(hwindow, out pid);
            Process p = Process.GetProcessById((int)pid);

            //カレントのハンドル
            IntPtr lItemWnd = SendMessage(treewindow, TVM_GETNEXTITEM, (IntPtr)TVGN_CARET, IntPtr.Zero);

            //ツリーを開く
            SendMessage(treewindow, TVM_EXPAND, (IntPtr)TVE_EXPAND, lItemWnd);          

            //相手プロセスのオープン
            IntPtr pProcessH = OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE, false, pid);
            if (pProcessH.Equals(IntPtr.Zero))
            {
                MessageBox.Show("相手側のプロセスが取得できませんでした");
                return;
            }

            
            //pszText設定用の文字列領域確保
            int MAX_STR = 260;

            //TVITEM構造体を宣言 初期化
            TVITEM tvitem = new TVITEM();

            int tvitemSize = Marshal.SizeOf(tvitem);
            int dwSize = tvitemSize + MAX_STR;

            //自プロセスの共有メモリ確保
            IntPtr pLocalShared = Marshal.AllocHGlobal(dwSize);

            //Inventor(相手)プロセス内に共有メモリの確保
            IntPtr pSysShared = VirtualAllocEx(pProcessH, IntPtr.Zero, (uint)dwSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
            if (pSysShared.Equals(IntPtr.Zero))
            {
                Marshal.FreeHGlobal(pLocalShared);
                CloseHandle(pProcessH);
                return;
            }
                        
            IntPtr pszTextInt =new IntPtr(pSysShared.ToInt32() + (int)tvitemSize);
            //構造体の初期化
            tvitem.mask = TVIF_TEXT;
            tvitem.hItem = lItemWnd;
            tvitem.pszText = pszTextInt;
            tvitem.cchTextMax = MAX_STR;

            Marshal.StructureToPtr(tvitem, pLocalShared, false);

            //相手プロセス内にTVITEM構造体をコピー
            uint retWrite = 0;
            bool bRet = WriteProcessMemory(pProcessH, pSysShared, pLocalShared, dwSize, ref retWrite);
             
            //SendMessage発行
            IntPtr pRet;
            pRet = SendMessage(treewindow, TVM_GETITEM, IntPtr.Zero, pSysShared);
            
            //共有メモリから情報取得
            uint retRead = 0;
            bRet = ReadProcessMemory(pProcessH, pSysShared, pLocalShared, dwSize, ref retRead);
            
            IntPtr refBuf = (IntPtr)(pLocalShared.ToInt32() + tvitemSize);
            MessageBox.Show(Marshal.PtrToStringUni(refBuf));
            string a = Marshal.PtrToStringUni(refBuf);

            //Inventor(相手)プロセス内の共有メモリを解放する
            VirtualFreeEx(pProcessH, pSysShared, (uint)dwSize, MEM_RELEASE);

            //自プロセス内共有メモリを解放する
            Marshal.FreeHGlobal(pLocalShared);

            //Inventor(相手)プロセスのクローズ
            CloseHandle(pProcessH);
        }}}
すみません。変な文字とは「"獁敳扭祬⸴慩m0.0.0, Culture=neutral, PublicKeyToken=null"」とか「"䕄椮浡"」等です。
  • 題名: Re[7]: 変な文字
  • 著者: Hongliang
  • 日時: 2012/02/02 17:52:42
  • ID: 29750
  • この記事の返信元:
  • この記事への返信:
    • (なし)
  • ツリーを表示
TVM_GETITEM は、実際には二種類存在しています。TVM_GETITEMA と TVM_GETITEMW です。
(文字列を扱う Win32API や Windows メッセージにはほとんどこの A と W の二つが存在します)
この二つは、それぞれ文字列を ANSI(ロケール依存のマルチバイトコード)または Unicode として扱います。
0x110A が指すのは TVM_GETITEMA の方であり、文字列は ANSI(日本語環境では Shift_JIS(CP932))のバイト配列として扱われます。
なので、pszText に格納されるのも ANSI 文字列であり、それに対して PtrToStringUni では当然ながら正しく変換できません。
■No29746に返信(さるあふろさんの記事)
> Int64は単に大きいほうがいいかな。とか、
32bit か 64bit かは、IntPtr.Size で判断できます。
最近は 64bit OS も増えてきたので、それぞれの違いを考慮しましょう。

> public uint lParam;
SendMessage の lParam と同様に、TVITEM.lParam も
IntPtr にしておいた方が良いかと。

> [DllImport("User32.dll", EntryPoint = "SendMessage")]
文字列を扱う API を使うときには、Charset 指定を行うことをお奨めします。
同様に、TVITEM にも StructLayout 属性を明示しましょう。


> IntPtr pszTextInt =new IntPtr(pSysShared.ToInt32() + (int)tvitemSize);
TVITEM の後ろに文字列領域を確保すべきではありません。
この構造体の定義は環境によって異なるため、拡張メンバーの位置に
文字列領域を被せると、その値が破壊される可能性があるためです。

なお手元の SDK では、TVITEM の構造は
    UINT      mask;
    HTREEITEM hItem;
    UINT      state;
    UINT      stateMask;
    LPWSTR    pszText;
    int       cchTextMax;
    int       iImage;
    int       iSelectedImage;
    int       cChildren;
    LPARAM    lParam;
    int       iIntegral;       // (_WIN32_IE >= 0x0400)
    UINT      uStateEx;        // (_WIN32_IE >= 0x0600)
    HWND      hwnd;            // (_WIN32_IE >= 0x0600)
    int       iExpandedImage;  // (_WIN32_IE >= 0x0600)
    int       iReserved;       // (NTDDI_VERSION >= NTDDI_WIN7)
のようになっていました。
Hongliang様 魔界の仮面弁士様

ご回答いただきましてどうも有り難うございました。
不勉強でお恥ずかしい限りです。
AnsiとUnicodeの違いを認識しておりませんでした。
Hongliang様にアドバイスいただいたように  PtrToStringAnsiで取得してみました。
また、魔界の仮面弁士様にアドバイス頂いたように
TVITEM構造体のlParamをIntPtrにし、それ以外の属性も追加して
バッファ用にメモリを別途取得して実行してみたところ、うまく取得できました!!
なるほど、TVITEMの後ろにもまだ情報がある場合があるのですね。

SDKのヘルプは英語でレスポンスが悪いのでつい敬遠していましたが、参照必須にします。
とても勉強になりました。
Hongliang様や魔界の仮面弁士様のようにヘルプに回答できるような技術を身に付けられるように
がんばります。
このたびは誠にどうもありがとうございました。
ご参考までにうまくいったコードを記載いたします。(長いのでこのメッセージの下にコメントとさせていただきました)
  • 題名: Re[8]: 成功したコード!!
  • 著者: さるあふろ
  • 日時: 2012/02/03 11:47:46
  • ID: 29754
  • この記事の返信元:
  • この記事への返信:
    • (なし)
  • ツリーを表示
using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Diagnostics;

namespace VaultPanelControl
{
    public class VaultPNCTL
    {
        [StructLayout(LayoutKind.Sequential)]
        public struct TVITEM
        {
            public uint mask;
            public IntPtr hItem;
            public uint state;
            public uint stateMask;
            public IntPtr pszText;
            public int cchTextMax;
            public int iImage;
            public int iSelectedImage;
            public int cChildren;
            public IntPtr lParam;
            public int iIntegral;
            public uint uStateEx;
            public IntPtr hwnd;
            public int iExpandedImage;
            public int iReserved;
        }

        [DllImport("User32.dll", CharSet = CharSet.Unicode)]
        static extern IntPtr FindWindow(string lpszClass, string lpszWindow);
        [DllImport("user32.dll")]
        static extern IntPtr FindWindowEx(IntPtr hWnd, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);
        [DllImport("User32.dll", CharSet = CharSet.Unicode)]
        public static extern IntPtr SendMessage(IntPtr hWnd, Int32 Msg, IntPtr wParam, IntPtr lParam);
        [DllImport("user32.dll")]
        public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
        [DllImport("kernel32.dll")]
        public static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect);
        [DllImport("kernel32.dll")]
        public static extern bool VirtualFreeEx(IntPtr hProcess, IntPtr lpAddress, uint dwSize, uint dwFreeType);
        [DllImport("kernel32.dll")]
        public static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, IntPtr lpBuffer, int nSize, ref uint lpNumberOfBytesRead);
        [DllImport("kernel32.dll")]
        public static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, IntPtr lpBuffer, int nSize, ref uint lpNumberOfBytesWritten);
        [DllImport("kernel32.dll")]
        public static extern IntPtr OpenProcess(uint dwDesiredAccess, bool bInheritHandle, uint dwProcessId);
        [DllImport("kernel32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool CloseHandle(IntPtr hObject);
        [DllImport("kernel32.dll")]
        static extern void MoveMemory(IntPtr dst, IntPtr src, int size);

        private static uint PROCESS_VM_OPERATION = 0x00000008;
        private static uint PROCESS_VM_READ = 0x00000010;
        private static uint PROCESS_VM_WRITE = 0x00000020;
        private static uint MEM_RESERVE = 0x2000;
        private static uint MEM_COMMIT = 0x1000;
        private static uint MEM_RELEASE = 0x8000;
        private static uint PAGE_READWRITE = 0x0040;

        //TreeViewのメッセージコード
        public static int TVM_EXPAND = 0x1102; //アイテムを開く・閉じる 
        public static int TVM_GETITEM = 0x110C; //アイテムの属性を取得 
        public static int TVM_GETNEXTITEM = 0x110A; //指定されたアイテムを取得 

        //TVM_GETNEXTITEM に指定する wParam = flag
        public static int TVGN_ROOT = 0x0000; //ツリービューのルート(最も上の階層)のアイテムを取得します。
        public static int TVGN_NEXT = 0x0001; //指定されたアイテムの同じグループ内の次のアイテムを取得します。
        public static int TVGN_CARET = 0x0009; //現在選択されているアイテムを取得します。

        //TVM_EXPANDに指定するwParam = flag
        public static int TVE_EXPAND = 0x1102; //子アイテムのツリーを開きます

        //TVITEMEXのmask 
        public static uint TVIF_TEXT = 0x0001; //pszText, cchTextMax

        static void Main(string[] args)
        {
            uint pid = 0x14A8;                       // 相手アプリケーションのプロセスID(直接指定)
            IntPtr treewindow = (IntPtr)0x70F10;    // ツリーウィンドウのハンドル(直接指定)
            IntPtr hwindow = (IntPtr)0x106D8;       //親ウィンドウハンドル直接指定

            uint result = GetWindowThreadProcessId(hwindow, out pid);
            Process p = Process.GetProcessById((int)pid);

            //カレントのハンドル
            IntPtr lItemWnd = SendMessage(treewindow, TVM_GETNEXTITEM, (IntPtr)TVGN_CARET, IntPtr.Zero);

            //ツリーを開く
            SendMessage(treewindow, TVM_EXPAND, (IntPtr)TVE_EXPAND, lItemWnd);

            //ノードの名称を取得******************************************
            //相手プロセスのオープン

            IntPtr pProcessH = OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE, false, pid);
            if (pProcessH.Equals(IntPtr.Zero))
            {
                return;
            }
            try
            {
                //pszText設定用の文字列領域確保
                int MAX_STR = 260;

                //TVITEM構造体を宣言 初期化
                TVITEM tvitem = new TVITEM();

                int tvitemSize = Marshal.SizeOf(tvitem);
                int dwSize = tvitemSize + MAX_STR;

                //自プロセスの共有メモリ確保
                //TVITEM格納分
                IntPtr pLocalShared = Marshal.AllocHGlobal(tvitemSize);
                try
                {
                    //タイトルバッファ格納分
                    IntPtr pLocalShared_Buf = Marshal.AllocCoTaskMem(MAX_STR);
                    try
                    {
                        //相手側プロセスの共有メモリ確保
                        //TVITEM格納分
                        IntPtr pSysShared = VirtualAllocEx(pProcessH, IntPtr.Zero, (uint)tvitemSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
                        if (pSysShared.Equals(IntPtr.Zero))
                        {
                            return;
                        }
                        try
                        {
                            //タイトルバッファ格納分
                            IntPtr pSysShared_Buf = VirtualAllocEx(pProcessH, IntPtr.Zero, (uint)MAX_STR, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
                            if (pSysShared_Buf.Equals(IntPtr.Zero))
                            {
                                return;
                            }
                            try
                            {
                                //構造体の初期化
                                tvitem.mask = TVIF_TEXT;
                                tvitem.hItem = lItemWnd;
                                tvitem.pszText = pSysShared_Buf;
                                tvitem.cchTextMax = MAX_STR;

                                //構造体のポインタを確保したアドレスに移動
                                Marshal.StructureToPtr(tvitem, pLocalShared, false);

                                //相手プロセス内にTVITEM構造体をコピー
                                uint retWrite = 0;
                                bool bRet = false;
                                bRet = WriteProcessMemory(pProcessH, pSysShared, pLocalShared, tvitemSize, ref retWrite);
                                if (bRet == false)
                                {
                                    return;
                                }

                                //SendMessage発行
                                IntPtr pRet;
                                pRet = SendMessage(treewindow, TVM_GETITEM, IntPtr.Zero, pSysShared);
                                if (pRet == IntPtr.Zero)
                                {
                                    return;
                                }

                                //共有メモリから情報取得
                                uint retRead = 0;
                                bRet = ReadProcessMemory(pProcessH, pSysShared_Buf, pLocalShared_Buf, MAX_STR, ref retRead);
                                if (bRet == false)
                                {
                                    return;
                                }

                                MessageBox.Show(Marshal.PtrToStringAnsi(pLocalShared_Buf));
                            }
                            finally
                            {
                                //相手プロセス内の共有メモリを解放する
                                VirtualFreeEx(pProcessH, pSysShared_Buf, (uint)dwSize, MEM_RELEASE);
                            }
                        }
                        finally
                        {
                            //相手プロセス内の共有メモリを解放する
                            VirtualFreeEx(pProcessH, pSysShared, (uint)dwSize, MEM_RELEASE);
                        }
                    }
                    finally
                    {
                        //自プロセス内共有メモリを解放する
                        Marshal.FreeHGlobal(pLocalShared_Buf);
                    }
                }
                finally
                {
                    //自プロセス内共有メモリを解放する
                    Marshal.FreeHGlobal(pLocalShared);
                }
            }
            finally
            {
                //相手プロセスのクローズ
                CloseHandle(pProcessH);
            }
        }
    }
}
解決済み!
■No29753に返信(さるあふろさんの記事)
> AnsiとUnicodeの違いを認識しておりませんでした。
> Hongliang様にアドバイスいただいたように  PtrToStringAnsiで取得してみました。

あら、そう来ましたか…。

その解決方法でも間違いでは無いのですが、ノードテキストに
Shift_JIS に無い文字が使われていた場合に文字化けしてしまいます。

それに、API 側で CharSet.Unicode を指定しているのに、取得法が
TVM_GETITEMA + PtrToStringAnsi というのは不自然に思えます。
TVM_GETITEMW + PtrToStringUni にすることも検討してみてください。


> なるほど、TVITEMの後ろにもまだ情報がある場合があるのですね。
正確に言えば、TVITEM ではなく TVITEMEX ですけれどね。
(元のコードにも、「//TVITEMEXのmask」というコメントがあるようで)

TVITEM は、mask〜lParamメンバーまでのものを指します。
iIntegral およびそれ以降を含むものが TVITEMEX です。


> [StructLayout(LayoutKind.Sequential)]
構造体を利用する場合は、Pack も明示指定した方が安全ですよ。

たとえば No29754 の構造体に Pack 指定を付けてみると、
Marshal.SizeOf の結果は、以下のように変化しました。


Pack = 1, 2 または 4 の場合
 x86ビルド = 60
 x64ビルド = 76 // IntPtr が 4 個あるので、4バイト×4個分の増加

Pack = 8 または Pack 省略時
 x86ビルド = 60
 x64ビルド = 80 // maskとhItemの間に4バイトの余白ができるため
解決済み!
  • 題名: Re[9]: できました!!
  • 著者: さるあふろ
  • 日時: 2012/02/06 13:54:27
  • ID: 29779
  • この記事の返信元:
  • この記事への返信:
    • (なし)
  • ツリーを表示
■No29756に返信(魔界の仮面弁士さんの記事)

> その解決方法でも間違いでは無いのですが、ノードテキストに
> Shift_JIS に無い文字が使われていた場合に文字化けしてしまいます。
>
> それに、API 側で CharSet.Unicode を指定しているのに、取得法が
> TVM_GETITEMA + PtrToStringAnsi というのは不自然に思えます。
> TVM_GETITEMW + PtrToStringUni にすることも検討してみてください。

なるほど。そうですよね。気になったのですがとりあえず文字が取得できたので
よしとしてしまいました… 
以下のようにして Unicodeで取得するように致しました。
これで安心できました!

public static int TVM_GETITEM = 0x110C;

public static int TV_FIRST = 0x1100;
public static int TVM_GETITEM = TV_FIRST + 62;

MessageBox.Show(Marshal.PtrToStringAnsi(pLocalShared_Buf));

MessageBox.Show(Marshal.PtrToStringUni(pLocalShared_Buf));

このTVM_GETITEMWのメッセージコードの定義がなかなかみつからず、探す過程でいろいろ勉強になりました。とりあえずCommctrl.hの中身が見つかったので参考までにリンクを残します↓

http://jaist.dl.sourceforge.net/project/hlanguage/Source/include/commctrl.h

>>[StructLayout(LayoutKind.Sequential)]
> 構造体を利用する場合は、Pack も明示指定した方が安全ですよ。
>
> たとえば No29754 の構造体に Pack 指定を付けてみると、
> Marshal.SizeOf の結果は、以下のように変化しました。

ご丁寧にどうもありがとうございました。
今回のアプリは64ビットPCでも使う予定があるので Pack=8としました。

このたびは誠にどうもありがとうございました。
解決済み!

DOBON.NET | プログラミング道 | プログラミング掲示板