質問者
デスクトップアイコンの情報取得 [ちょっと急いでます]

質問
-
こんにちは。すこし急いでいるので早速本題に入らせていただきますね。デスクトップアイコンの情報取得について教えていただきたいことがあります。listBox1と2があるとして、アイコンの座標は、
using System; using System.Drawing; using System.Windows.Forms; using System.Runtime.InteropServices; namespace Sample { public partial class Form1 : Form { public const uint PROCESS_VM_OPERATION = 0x8; public const uint PROCESS_VM_READ = 0x10; public const uint PROCESS_VM_WRITE = 0x20; public const uint MEM_RESERVE = 0x2000; public const uint MEM_COMMIT = 0x1000; public const uint PAGE_READWRITE = 0x4; public const int LVM_GETITEMPOSITION = 0x1010; public const int LVM_GETITEMCOUNT = 0x1004; public const uint MEM_RELEASE = 0x8000; [DllImport("user32.dll")] public static extern IntPtr FindWindow(string strclassName, string strWindowName); [DllImport("user32", EntryPoint = "FindWindowEx")] public static extern IntPtr FindWindowEx(IntPtr hWnd1, IntPtr hWnd2, string lpsz1, string lpsz2); [DllImport("user32.dll")] public static extern int SendMessage(IntPtr hWnd, int wMsg, int wParam, IntPtr lParam); [DllImport("user32.dll")] public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId); [DllImport("kernel32.dll")] public static extern IntPtr OpenProcess(uint dwDesiredAccess, bool bInheritHandle, uint dwProcessId); [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 ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, IntPtr lpBuffer, int nSize, ref uint vNumberOfBytesRead); [DllImport("kernel32.dll")] public static extern bool VirtualFreeEx(IntPtr hProcess, IntPtr lpAddress, uint dwSize, uint dwFreeType); [DllImport("kernel32.dll")] public static extern bool CloseHandle(IntPtr hObject); public Form1() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { GetIconPosition(); } public static bool ListView_GetItemPosition(IntPtr hwnd, int i, IntPtr ppt) { return SendMessage(hwnd, LVM_GETITEMPOSITION, i, ppt) != 0; } public static int ListView_GetItemCount(IntPtr AHandle) { return SendMessage(AHandle, LVM_GETITEMCOUNT, 0, IntPtr.Zero); } private void GetIconPosition() { IntPtr hWnd; IntPtr hProcess; IntPtr pnt; Point[] iconPoint = new Point[1]; int iconCount; uint dwProcessId; uint vNumberOfBytesRead = 0; hWnd = FindWindow("Progman", "Program Manager"); hWnd = FindWindowEx(hWnd, IntPtr.Zero, "SHELLDLL_DefView", null); hWnd = FindWindowEx(hWnd, IntPtr.Zero, "SysListView32", null); //アイコンの数を取得 iconCount = ListView_GetItemCount(hWnd); GetWindowThreadProcessId(hWnd, out dwProcessId); hProcess = OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE, false, dwProcessId); pnt = VirtualAllocEx(hProcess, IntPtr.Zero, 4096, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); for (int i = 0; i < iconCount; i++) { //アイコンの位置を取得 ListView_GetItemPosition(hWnd, i, pnt); ReadProcessMemory(hProcess, pnt, Marshal.UnsafeAddrOfPinnedArrayElement(iconPoint, 0), Marshal.SizeOf(typeof(Point)), ref vNumberOfBytesRead); listBox1.Items.Add(string.Format("x:{0}, y:{1}", iconPoint[0].X, iconPoint[0].Y)); } VirtualFreeEx(hProcess, pnt, 0, MEM_RELEASE); CloseHandle(hProcess); } } }
(コピペですけどねf(^_^;;))で取得、デスクトップアイコンのパス一覧は、string userName = Environment.UserName.ToString(); foreach (string stFilePath in System.IO.Directory.GetFiles(@"C:¥Users¥Public¥Desktop¥")) { listBox2.Items.Add(string.Format(stFilePath)); } foreach (string stFilePath in System.IO.Directory.GetFiles(@"C:¥Users¥"+userName+@"¥Desktop¥")) { listBox2.Items.Add(string.Format(stFilePath)); } foreach (string stFilePath in System.IO.Directory.GetDirectories(@"C:¥Users¥"+userName+@"¥Desktop¥")) { listBox2.Items.Add(string.Format(stFilePath)); } foreach (string stFilePath in System.IO.Directory.GetDirectories(@"C:¥Users¥Public¥Desktop¥")) { listBox2.Items.Add(string.Format(stFilePath)); }
(desktop.iniが含まれますが。また、もしかするとWin7だけかもしれませんが)で取得できますよね。しかし、これではどのパスのアイコンがどの座標にあるのかが分かりません。いろいろ考えた結果、2番目の方法は使わず、1番目の方法を応用して取得するのではないかという結論に至りました。しかしその方法が全く分かりません。ググっても見たのですが、これというものがヒットしませんでした。どなたかこの方法をご存知の方、いらっしゃいませんでしょうか?よろしくお願いします。
- 編集済み mountT 2011年5月30日 15:36
すべての返信
-
ちょっと急いで答えますと…ひじょーに難しい問題です。1番目の方法=アイコンの座標取得、2番目の方法=ファイル名の取得、ですか? そこが書かれてないと質問本文が読み取れないことにはお気づきでしょうか。
ともあれ目的の実現までのアプローチをどう考えているのかがわからないことには。1番目の方法を使うとして、アイコンが置かれている座標がわかったら、次は1ピクセルずつ色を読み込んで行くことをお考えでしょうか? 半透明アイコンがあれば背景色と合成されてしまうのは些細な問題だとは思います。
1番目の方法も欲している座標データが正しく取得できているのか疑問です。自プロセスでAPI呼び出しを行って、相手プロセスのメモリ空間から結果を読み込んでいるようにも見えますが…。
-
返信ありがとうございます!
>1番目の方法=アイコンの座標取得、2番目の方法=ファイル名の取得、ですか?
そうです。すみません。分かりづらかったですね。
しかし方法というよりは、1番目のプログラム, 2番目のプログラム、といった感じだったでしょうか。
>1番目の方法を使うとして、アイコンが置かれている座標がわかったら、次は1ピクセルずつ色を読み込んで行くことをお考えでしょうか? 半透明アイコンがあれば背景色と合成されてしまうのは些細な問題だとは思います。
いえ。そういうつもりではないです。
なんとかWindowsからそのまま座標とパスをセットでを取得できないものかと考えております。
>1番目の方法も欲している座標データが正しく取得できているのか疑問です。自プロセスでAPI呼び出しを行って、相手プロセスのメモリ空間から結果を読み込んでいるようにも見えますが…。
すみません。ちょっと分からないのですが、多分そうではないかと思います。
programan.exeから読み込んでいると思われます。
このままついでにPrograman.exeからパスも取得する、なんてことは出来ませんか?
できないならどうすれば良いでしょうか?
また、これが間違った取得の仕方で、正しい方法があるのでしたら教えていただけないでしょうか?
質問ばかりですみません。
-
「デスクトップアイコンの情報取得」というのはアイコン画像が欲しいのではなく、どのファイルがどの座標に置かれているのか? がほしいのでしょうか。
ListView_GetItemPosition(hWnd, i, pnt);
ReadProcessMemory(hProcess, pnt, Marshal.UnsafeAddrOfPinnedArrayElement(iconPoint, 0), Marshal.SizeOf(typeof(Point)), ref vNumberOfBytesRead);というのは、クズリさんの作ったプログラム・プロセス内でListView_GetItemPosition()関数を呼び出しておいて、それが終了したらPrograman.exeのプロセス内のメモリの値を読み込んでいるんですよね?
-
正しいかどうかは知りませんが、Windows XP 以降なら IFolderView を使う方法はあるようですね(IFolderView2 / Windows Vista が必要にあるかも?)。バリバリの COM なので C# からの使用は面倒ですが。idl ファイルとか tlb ファイルとか見あたらないし。
とりあえず手順としては、
- SHGetSpecialFolderLocation 関数などでデスクトップの IDL を取得する。
- IShellWindows オブジェクトを生成し、IShellWindows::FindWindowSW メソッドでデスクトップを識別するオブジェクトを取得する。このオブジェクトは IServiceProvider を公開しているので、IServiceProvider にキャスト(※ .NET のライブラリにある System.IServiceProvider とは別物)。
- IServiceProvider::QueryService で、IFolderView を取得する。サービス ID は SID_SFolderView で、IFolderView の GUID と同じ。
- IFolderView の各メソッドを使用してアイテムの位置を取得する。名前またはパスは、IFolderView::Item メソッドで取得した IDL をもとに SHGetNameFromIDList 関数で。
となります。COM インターフェイスの定義方法については、とりあえず pinvoke.net に IServiceProvider が載ってるのでそれを参考にしてください。
IFolderView::GetItemPosition が返すのがアイコンの座標のようなので、アイテムの座標としては IFolderView::GetSpacing とか使って調整する必要がありそうです。
// DPI 設定が 96dpi じゃないと面倒そう。
-
1ができました!と言いましても合っているのかどうかは分かりませんが...
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using System.Runtime.InteropServices; namespace WindowsFormsApplication1 { public partial class Form1 : Form { [DllImport("Shell32.DLL")] public static extern int SHGetSpecialFolderLocation(IntPtr hwndOwner, int nFolder, out IntPtr ppidl); public Form1() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { IntPtr pidlRoot = IntPtr.Zero; SHGetSpecialFolderLocation(this.Handle, 0x0000, out pidlRoot); } } }
-
えと、、、>Hongliangさん>とりあえず pinvoke.net に IServiceProvider が載ってるのでとはこれのことですよね?↓
[ComImport] [Guid("6d5140c1-7436-11ce-8034-00aa006009fa")] [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] interface IServiceProvider { void QueryService(ref Guid guidService, ref Guid riid, [MarshalAs(UnmanagedType.Interface)] out object ppvObject); }
では、IShellWindowsのほうもこれと同じようにしていいのでしょうか?もしそうであれば、Guidの値や、FindWindowSWの引数はどうすればいいでしょうか?Hongliangさんか、どなたか分かる方、ご教授いただけますでしょうか?- 編集済み mountT 2011年5月30日 15:35
-
ありがとうございます。
>SHDocVw.dll に IShellWindows が入っているならそれを使えばいいのでは。
分かりました。そうします。
しかし、やはり他は分かりません...。
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using System.Runtime.InteropServices; namespace WindowsFormsApplication1 { public partial class Form1 : Form { [DllImport("Shell32.DLL")] public static extern int SHGetSpecialFolderLocation(IntPtr hwndOwner, int nFolder, out IntPtr ppidl); public Form1() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { IntPtr pidl = IntPtr.Zero; SHGetSpecialFolderLocation(this.Handle, 0x0000, out pidl); IShellWindows sw = new ShellWindows(); IntPtr pvarLocRoot = IntPtr.Zero; int pHwnd; sw.FindWindowSW(pidl, pvarLocRoot, 0x00000008, out pHwnd, 0x00000004);
} }
いろいろなサイトを巡りに巡った結果がこれです。
ここまで書いてみたものの、合っているのか間違っているのかもさっぱり分からないという状態です。
-
SHDocVw.dllは利用せず、自分でP/Invokeの記述をしたほうが良いでしょう。
C++で記述していますので、後はC#向けに手直しすればよいです。
Shell InterfaceはOS(UI)と密着していますので、OSにより動作が変わる可能性があります。最初に記述された1番目の方法でも良いかもしれません。
HRESULT result; IShellWindows *shellWindows; IWebBrowserApp *webBrowserApp; IDispatch *dispatch; IServiceProvider *serviceProvider; IShellBrowser *shellBrowser; IShellView *shellView; IFolderView *folderView; IEnumIDList *enumIDList; VARIANT var; ITEMIDLIST *childpidl; long hwnd; POINT pos; result = CoCreateInstance(CLSID_ShellWindows, nullptr, CLSCTX_ALL, IID_IShellWindows, (void**)&shellWindows); VariantInit(&var); result = shellWindows->FindWindowSW(&var,&var,SWC_DESKTOP,&hwnd,SWFO_NEEDDISPATCH,&dispatch); result = dispatch->QueryInterface(IID_IWebBrowserApp,(void**)&webBrowserApp); result = webBrowserApp->QueryInterface(IID_IServiceProvider,(void**)&serviceProvider); result = serviceProvider->QueryService(SID_STopLevelBrowser,IID_IShellBrowser, (void**)&shellBrowser); result = shellBrowser->QueryActiveShellView(&shellView); result = shellView->QueryInterface(IID_IFolderView,(void**)&folderView); result = folderView->Items(SVGIO_FLAG_VIEWORDER,IID_IEnumIDList,(void**)&enumIDList); while(enumIDList->Next(1,&childpidl,nullptr) == S_OK) { result = folderView->GetItemPosition(childpidl,&pos); ILFree(childpidl); } //you should check if you need to clean up variables //this code will work on vista or later
-
P/InvokeとCOM Interopでしたね。
確認です。
COMそのものの仕組みはご存知でしょうか?
COMと.NET Frameworkの関係や仕組みはご存知でしょうか?
Shellの仕組みはご存知でしょうか?
どこまで何をご存知でしょうか?もしそれらを知らないならば、難易度は高く先に進むのは困難でしょう。
一朝一夕には説明できませんし、同じく理解できるものではないです。[COM Interop Part 1: C# Client Tutorial]
http://msdn.microsoft.com/en-us/library/aa645736(VS.71).aspx[Interoperability Overview (C# Programming Guide)]
http://msdn.microsoft.com/en-us/library/ms173185.aspx[Advanced COM Interoperability]
http://msdn.microsoft.com/en-us/library/bd9cdfyx.aspx[COM 相互運用機能の概要]
http://msdn.microsoft.com/ja-jp/magazine/cc163494.aspx[マネージ コードとアンマネージ コード間でマーシャリングする]
http://msdn.microsoft.com/ja-jp/magazine/cc164193.aspxC++をご存じなら案として、C++/CLIの案内も出来たのですが、それも難しそうですね。
補足です。
>SHDocVw.dllは利用せず、自分でP/Invokeの記述をしたほうが良いでしょう。
SHDocVw.dllはTypeLibを持っているためReferenceに追加することができるのですが、FindWindowSW Methodがありません。
(自分でIDLを編集する手が使えるかもしれませんが、少し面倒です)>//this code will work on vista or later
SWC_DESKTOP flagはVista以降でSupportされます。Codeは位置の取得までしか記述していません。
どのFileなのか特定するには追加Codeが必要です。
MSDNでIFolderViewを調べてみてください。>それなら何というdllを使うのでしょう?
COMはGUIDで区別します。
そのGUIDの先にDLL(in-process server)やEXE(out-process server)が紐付けされていると考えてください。
従ってCOMの場合、DLL云々は気にしなくて良いです。EXEの場合もありますしね。ILFreeなどのAPIはMSDNで調べてみましょう。
[ILFree Function]
http://msdn.microsoft.com/en-us/library/bb776441(VS.85).aspx
下の方にDLL Shell32.dll (version 5.0 or later) と書かれていますね。 -
お忙しいところありがとうございます。お世話になります。
COMそのものの仕組みはご存知でしょうか?
COMと.NET Frameworkの関係や仕組みはご存知でしょうか?
Shellの仕組みはご存知でしょうか?
どこまで何をご存知でしょうか?COMの仕組みはなんとなくですが分かります。
ですがその後は分かりません...
もしそれらを知らないならば、難易度は高く先に進むのは困難でしょう。
一朝一夕には説明できませんし、同じく理解できるものではないです。やはりそうですか...
ではどうしましょう。どうしても完成させたい作品なので、諦めるのは絶対に嫌なんです。
急ぐのをやめて一生懸命勉強した方がいいでしょうか...
補足です。
いろいろな情報、本当にありがとうございます!
感謝です!
COMはGUIDで区別します。
IServiceProviderやIFolderViewはExplorerBrowserIIDGuidで取得できますが、IShellWindowsはどうすればいいのでしょうか?
下の方にDLL Shell32.dll (version 5.0 or later) と書かれていますね。
ほんとですね!ではこれをDllImportすればよさそうですね。
あと、リンク貼ってくださってありがとうございます!
勉強になります。
-
-
>IDispatch
同じようにWindows SDKのHeaderから検索してください。定義が必要とのことですが、上記Link内の以下についてはご理解されていますか?
その上で定義が必要とのことでしたら、気にしないでください。
COM interfaces declared in C# must include declarations for all members of their base interfaces with the exception of members of IUnknown and IDispatch — the .NET Framework automatically adds these. COM interfaces which derive from IDispatch must be marked with the InterfaceType attribute.>ITEMIDLIST
構造体の中身は可変サイズであり、直接中身を除く必要もないでしょうからC#ではIntPtrでよいでしょう。- 編集済み kozz 2011年6月13日 9:36 補足
-
ありがとうございます!
>IDispatch
同じようにWindows SDKのHeaderから検索してください。Dispatch.hを見ると、
your code should #include oleauto.h instead of dispatch.h
と書いてあったので、oleauto.hを見たのですが載っていませんでした...
定義が必要とのことですが、上記Link内の以下についてはご理解されていますか?
その上で定義が必要とのことでしたら、気にしないでください。
COM interfaces declared in C# must include declarations for all members of their base interfaces with the exception of members of IUnknown and IDispatch — the .NET Framework automatically adds these. COM interfaces which derive from IDispatch must be marked with the InterfaceType attribute.すみません。いまいち理解できずにいます。
なので何故定義しなくてもいいことがあるのかわかりません...
.NET Frameworkが自動的に定義してくれるということでしょうか?
>ITEMIDLIST
構造体の中身は可変サイズであり、直接中身を除く必要もないでしょうからC#ではIntPtrでよいでしょう。