C# IronPython C++/CLI で SHGetFileInfo
C# は P/Invoke を利用して WPF から使う方法。
IronPython は ctypes モジュールから WindowsAPI を呼ぶ方法。
C++/CLI は直接 SHGetFileInfo 関数を使う方法。
で書いていきます、コードはすべてドラッグ&ドロップで表示させる方法に書き換えております。
IronPython は ctypes モジュールから WindowsAPI を呼ぶ方法。
C++/CLI は直接 SHGetFileInfo 関数を使う方法。
で書いていきます、コードはすべてドラッグ&ドロップで表示させる方法に書き換えております。
C# (UNICODE 関数及び x64 Windows 対応コード)
UNICODE 関数を使う為に SHFILEINFOW 構造体及び SHGetFileInfoW 関数を利用します。
SHFILEINFOW 構造体の第二引数は int です、IntPtr では x64 環境では構造体サイズの違いでコケます。
LLP64 である x64 Windows はポインタが 64bit、int は 32bit のままです。
shell32.dll が気になる人もいるかもしれませんが x64 でもこの名前は同じです。
WOW64 では WOW64 用の 32bit shell32.dll が別に存在します。
関数名指定や SHGetFileInfoW を利用するには CharSet = CharSet.Unicode 指定が必要です。
StructLayout, DllImport だけでは Ansi 関数を呼び出したり文字列を Ansi 化してしまいます。
msdn: 文字セットの指定
msdn: 文字列に対する既定のマーシャリング
このコードを使って WPF ウインドウに表示させるコード。
Visual Studio を使わず Build できるようにコードのみで実装してみました。
というか WPF ってこんなに簡単かつ便利なのに何故イマイチ普及しないのか?
まとめた ZIP も置いておきます。
build.bat を W クリックして作成された exe を起動しウインドウにファイルをドロップしてみましょう。
icon_cs.zip
SHFILEINFOW 構造体の第二引数は int です、IntPtr では x64 環境では構造体サイズの違いでコケます。
LLP64 である x64 Windows はポインタが 64bit、int は 32bit のままです。
using System; using System.Windows; using System.Windows.Media.Imaging; using System.Runtime.InteropServices; using System.Windows.Interop; namespace icon_cs { [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] public struct SHFILEINFOW { public IntPtr hIcon; public int iIcon; public uint dwAttributes; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] public string szDisplayName; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)] public string szTypeName; } public enum ShellFileInfoFlags : uint { SHGFI_ICON = 0x000000100, SHGFI_DISPLAYNAME = 0x000000200, SHGFI_TYPENAME = 0x000000400, SHGFI_ATTRIBUTES = 0x000000800, SHGFI_ICONLOCATION = 0x000001000, SHGFI_EXETYPE = 0x000002000, SHGFI_SYSICONINDEX = 0x000004000, SHGFI_LINKOVERLAY = 0x000008000, SHGFI_SELECTED = 0x000010000, SHGFI_ATTR_SPECIFIED = 0x000020000, SHGFI_LARGEICON = 0x000000000, SHGFI_SMALLICON = 0x000000001, SHGFI_OPENICON = 0x000000002, SHGFI_SHELLICONSIZE = 0x000000004, SHGFI_PIDL = 0x000000008, SHGFI_USEFILEATTRIBUTES = 0x000000010 } public enum FileAttributesFlags : uint { FILE_ATTRIBUTE_ARCHIVE = 0x00000020, FILE_ATTRIBUTE_ENCRYPTED = 0x00004000, FILE_ATTRIBUTE_HIDDEN = 0x00000002, FILE_ATTRIBUTE_NORMAL = 0x00000080, FILE_ATTRIBUTE_NOT_CONTENT_INDEXED = 0x00002000, FILE_ATTRIBUTE_OFFLINE = 0x00001000, FILE_ATTRIBUTE_READONLY = 0x00000001, FILE_ATTRIBUTE_SYSTEM = 0x00000004, FILE_ATTRIBUTE_TEMPORARY = 0x00000100 } public class Win32 { // [DllImport("shell32.dll", CharSet = CharSet.Unicode, ExactSpelling = true)] public static extern IntPtr SHGetFileInfoW( string pszPath, FileAttributesFlags dwFileAttributes, ref SHFILEINFOW psfi, uint cbSizeFileInfo, ShellFileInfoFlags uFlags); } public class Win32FileInfo { public static bool GetFileInfo(string fileName, ref CItem item) { if (!System.IO.File.Exists(fileName)) return false; SHFILEINFOW shinfo = new SHFILEINFOW(); Win32.SHGetFileInfoW( fileName, FileAttributesFlags.FILE_ATTRIBUTE_NORMAL, ref shinfo, (uint)Marshal.SizeOf(shinfo), ShellFileInfoFlags.SHGFI_ICON | ShellFileInfoFlags.SHGFI_LARGEICON | ShellFileInfoFlags.SHGFI_TYPENAME | ShellFileInfoFlags.SHGFI_DISPLAYNAME ); item.image = Imaging.CreateBitmapSourceFromHIcon( shinfo.hIcon, new Int32Rect(0, 0, 32, 32), BitmapSizeOptions.FromEmptyOptions() ); item.type = shinfo.szTypeName; item.name = shinfo.szDisplayName; return true; } } public class CItem { public BitmapSource image { get; set; } public string name { get; set; } public string type { get; set; } } }
shell32.dll が気になる人もいるかもしれませんが x64 でもこの名前は同じです。
WOW64 では WOW64 用の 32bit shell32.dll が別に存在します。
関数名指定や SHGetFileInfoW を利用するには CharSet = CharSet.Unicode 指定が必要です。
StructLayout, DllImport だけでは Ansi 関数を呼び出したり文字列を Ansi 化してしまいます。
msdn: 文字セットの指定
msdn: 文字列に対する既定のマーシャリング
このコードを使って WPF ウインドウに表示させるコード。
Visual Studio を使わず Build できるようにコードのみで実装してみました。
というか WPF ってこんなに簡単かつ便利なのに何故イマイチ普及しないのか?
using System; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using icon_cs; namespace iicon { public class FileInfoWindow : Window { Image _image; TextBlock[] _texts; public FileInfoWindow() { this.Title = "Get FileInfo"; this.Drop += new DragEventHandler(OnDrop); this.AllowDrop = true; this.SizeToContent = SizeToContent.WidthAndHeight; this.ResizeMode = ResizeMode.NoResize; // Controls var panel = new StackPanel(); this.Content = panel; var b = new TextBlock(new Bold(new Run("Get FileInfo as Droped File"))); panel.Children.Add(b); _image = new Image(); _image.Height = _image.Width = 32; panel.Children.Add(_image); // Create TextBlock List _texts = new TextBlock[4]; for (int i=0; i<4; i++) { TextBlock tb = new TextBlock(); panel.Children.Add(tb); _texts.SetValue(tb, i); } } void OnDrop(object sender, DragEventArgs e) { var filenames = (string[])e.Data.GetData(DataFormats.FileDrop); var item = new CItem(); if (Win32FileInfo.GetFileInfo(filenames[0], ref item)) { // GetSystem.IO.FileInfo var ioinfo = new System.IO.FileInfo(filenames[0]); // icon _image.Source = item.image; // FileName _texts[0].Text = item.name; // Updated _texts[1].Text = ioinfo.LastWriteTime.ToString("yyyy/MM/dd HH:mm"); // Type Name _texts[2].Text = item.type; // File Size _texts[3].Text = Math.Ceiling(ioinfo.Length / 1024.0).ToString("#,##0 KB"); } } [STAThread] public static void Main() { var app = new Application(); app.Run(new FileInfoWindow()); } } }
まとめた ZIP も置いておきます。
build.bat を W クリックして作成された exe を起動しウインドウにファイルをドロップしてみましょう。
icon_cs.zip
IronPython
ctypes から WindowsAPI を呼ぶには実際の関数名や構造体名で呼ぶ必要があります。
現状では UNICODE 関数を利用したほうが賢明でしょう。
x64 対応は ipyw.exe, ipyw64.exe のどちらを使うかでどちらの shell32.dll かが決まるはず。
文字列は string 型、 Python の str 型、 ctypes の c_wchar が全部自動で相互変換される。
少しの手間で SHGetFileInfoW 関数がそのまま使えるのでわりと簡単。
UTF-8 で保存するのを忘れずに。
これで上記 C# で作成したものと同じウインドウになるはずです、初期化が遅いけど...
現状では UNICODE 関数を利用したほうが賢明でしょう。
x64 対応は ipyw.exe, ipyw64.exe のどちらを使うかでどちらの shell32.dll かが決まるはず。
文字列は string 型、 Python の str 型、 ctypes の c_wchar が全部自動で相互変換される。
少しの手間で SHGetFileInfoW 関数がそのまま使えるのでわりと簡単。
# -*- coding: UTF-8 -*- """ get_file_detailst.py System.IO.FileInfo and SHGetFileInfoW Windows API use both the x86 and x64 editions """ import clr clr.AddReferenceByPartialName("PresentationCore") clr.AddReferenceByPartialName("PresentationFramework") clr.AddReferenceByPartialName("WindowsBase") from System import * from System.IO import * from System.Windows import * from System.Windows.Controls import * from System.Windows.Documents import * from System.Windows.Media.Imaging import * from ctypes import * MAX_PATH = 260 HICON = c_void_p # ShellFileInfoFlags SHGFI_ICON = 0x000000100 SHGFI_DISPLAYNAME = 0x000000200 SHGFI_TYPENAME = 0x000000400 SHGFI_ATTRIBUTES = 0x000000800 SHGFI_ICONLOCATION = 0x000001000 SHGFI_EXETYPE = 0x000002000 SHGFI_SYSICONINDEX = 0x000004000 SHGFI_LINKOVERLAY = 0x000008000 SHGFI_SELECTED = 0x000010000 SHGFI_ATTR_SPECIFIED = 0x000020000 SHGFI_LARGEICON = 0x000000000 SHGFI_SMALLICON = 0x000000001 SHGFI_OPENICON = 0x000000002 SHGFI_SHELLICONSIZE = 0x000000004 SHGFI_PIDL = 0x000000008 SHGFI_USEFILEATTRIBUTES = 0x000000010 # FileAttributesFlags FILE_ATTRIBUTE_ARCHIVE = 0x00000020, FILE_ATTRIBUTE_ENCRYPTED = 0x00004000, FILE_ATTRIBUTE_HIDDEN = 0x00000002, FILE_ATTRIBUTE_NORMAL = 0x00000080, FILE_ATTRIBUTE_NOT_CONTENT_INDEXED = 0x00002000, FILE_ATTRIBUTE_OFFLINE = 0x00001000, FILE_ATTRIBUTE_READONLY = 0x00000001, FILE_ATTRIBUTE_SYSTEM = 0x00000004, FILE_ATTRIBUTE_TEMPORARY = 0x00000100 class SHFILEINFOW(Structure): """ typedef struct """ _fields_ = [("hIcon", HICON), ("iIcon", c_int), ("dwAttributes", c_uint), ("szDisplayName", c_wchar * MAX_PATH), ("szTypeName", c_wchar * 80)] class GetFileInfoWindow(Window): """ Get FileInfo as Droped File """ def __init__(self): """ Window.__init__(self) is No Constructor has been """ self.Title = "Get FileInfo" self.Drop += self.on_drop self.AllowDrop = True self.SizeToContent = SizeToContent.WidthAndHeight self.ResizeMode = ResizeMode.NoResize # Controls panel = StackPanel() self.Content = panel b = TextBlock() b.Inlines.Add(Bold(Run("Get FileInfo as Droped File"))) panel.Children.Add(b) self.image = Image() self.image.Height = 32 self.image.Width = 32 panel.Children.Add(self.image) # Create TextBlock List self.texts = [] for i in range(4): t = TextBlock() panel.Children.Add(t) self.texts.append(t) def on_drop(self, sender, e): """ Get Dropped File Details """ filename = e.Data.GetData(DataFormats.FileDrop)[0].ToString() shinfo = SHFILEINFOW() r = windll.shell32.SHGetFileInfoW( filename, FILE_ATTRIBUTE_NORMAL, byref(shinfo), sizeof(shinfo), SHGFI_ICON | SHGFI_LARGEICON | SHGFI_TYPENAME | SHGFI_DISPLAYNAME ) if r == 0: self.texts[0].Text = "error" else: # Get System.IO.FileInfo ioinfo = IO.FileInfo(filename) # Icon bms = Interop.Imaging.CreateBitmapSourceFromHIcon( IntPtr(shinfo.hIcon), Int32Rect(0, 0, 32, 32), BitmapSizeOptions.FromEmptyOptions() ) self.image.Source = bms # File Name self.texts[0].Text = shinfo.szDisplayName # Updated self.texts[1].Text = ioinfo.LastWriteTime.ToString("yyyy/MM/dd HH:mm") # Type Name self.texts[2].Text = shinfo.szTypeName # File Size self.texts[3].Text = Math.Ceiling(ioinfo.Length / 1024.0).ToString("#,##0 KB") if __name__ == "__main__": Application().Run(GetFileInfoWindow())
UTF-8 で保存するのを忘れずに。
これで上記 C# で作成したものと同じウインドウになるはずです、初期化が遅いけど...
C++/CLI
C++/CLI ならそのまんま WindowsAPI が使えるので簡単だと思ったら大間違いだった。
*wchar_t と System::String を変換しないといけない、marshal_as か marshal_context クラスにて相互変換する。
C++ におけるマーシャリングの概要
marshal_as
marshal_context クラス
C++/CLI は Visual Studio が無いとお手上げなので素直に Visual Studio を利用する。
更に WPF が使えないのでしかたなく WindowsForm という MS がバカの為に残しているゴミを使う。
適当にプロジェクトを作って stdafx.h に以下を追記する。
SHGetFileInfo で shell32.dll、HICON の破棄で user32.dll への参照が必要。
SHGetFileInfo はビルド方法によって SHGetFileInfoA か SHGetFileInfoW に振り分けされる。
のは SDK プログラミングの時と同じようだ。
そして Form に PictureBox と ListBox を貼り付ける。
次に Form のプロパティで AllowDrop を true に。
後はイベントハンドラで
Windows SDK で書くより面倒くさい...
*wchar_t と System::String を変換しないといけない、marshal_as か marshal_context クラスにて相互変換する。
C++ におけるマーシャリングの概要
marshal_as
marshal_context クラス
C++/CLI は Visual Studio が無いとお手上げなので素直に Visual Studio を利用する。
更に WPF が使えないのでしかたなく WindowsForm という MS がバカの為に残しているゴミを使う。
適当にプロジェクトを作って stdafx.h に以下を追記する。
#pragma once #pragma comment(lib, "shell32.lib") #pragma comment(lib, "user32.lib") // TODO: プログラムに必要な追加ヘッダーをここで参照してください。 #using <mscorlib.dll> #include <windows.h> #include <shellapi.h> #include <tchar.h> #include <msclr\marshal.h>
SHGetFileInfo で shell32.dll、HICON の破棄で user32.dll への参照が必要。
SHGetFileInfo はビルド方法によって SHGetFileInfoA か SHGetFileInfoW に振り分けされる。
のは SDK プログラミングの時と同じようだ。
そして Form に PictureBox と ListBox を貼り付ける。
次に Form のプロパティで AllowDrop を true に。
後はイベントハンドラで
// 以下を忘れずに using namespace msclr::interop; private: System::Void Form1_DragDrop(System::Object^ sender, System::Windows::Forms::DragEventArgs^ e) { // C++/CLI の配列はこう宣言するらしい array<System::String ^> ^ filenames; // ドロップファイル名取得 filenames = (array<System::String ^> ^)e->Data->GetData(DataFormats::FileDrop); // 初期化 this->listBox1->Items->Clear(); wchar_t szFileName[1024]; marshal_context ^ context = gcnew marshal_context(); // 変換して szFileName に取り込む // const にする必要があるので ToString() している swprintf_s(szFileName, 1024, context->marshal_as<const wchar_t*>(filenames[0]->ToString())); // もういらないからとっとと削除 delete context; // マネージド System::IO::FileInfo ^ ioInfo = gcnew System::IO::FileInfo(filenames[0]); // アンマネージド ::SHFILEINFO fi; ::SHGetFileInfo(szFileName, FILE_ATTRIBUTE_NORMAL, &fi, sizeof(fi), SHGFI_TYPENAME | SHGFI_ICON | SHGFI_LARGEICON | SHGFI_USEFILEATTRIBUTES); // アイコン System::Drawing::Icon ^ ico = Icon->FromHandle((IntPtr)fi.hIcon); this->pictureBox1->Image = ico->ToBitmap(); // HICON の破棄はコレでいいのかな? // ico->Handle だけじゃ error なんだが // http://msdn.microsoft.com/ja-jp/library/system.drawing.icon.fromhandle.aspx DestroyIcon((HICON)ico->Handle.ToPointer()); // FileName this->listBox1->Items->Add(System::IO::Path::GetFileName(filenames[0])); // Updated this->listBox1->Items->Add(ioInfo->LastWriteTime.ToString("yyyy/MM/dd HH:mm")); // Type Name this->listBox1->Items->Add(marshal_as<System::String ^>(fi.szTypeName)); // File Size System::Int32 nSize = static_cast<System::Int32>(System::Math::Ceiling(ioInfo->Length / 1024.0)); this->listBox1->Items->Add(nSize.ToString("#,##0") + " kb"); } private: System::Void Form1_DragEnter(System::Object^ sender, System::Windows::Forms::DragEventArgs^ e) { if( e->Data->GetDataPresent(DataFormats::FileDrop)) { e->Effect = DragDropEffects::All; } else { e->Effect = DragDropEffects::None; } }
Windows SDK で書くより面倒くさい...
Copyright(C) sasakima-nao All rights reserved 2002 --- 2024.