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 --- 2025.