実行ファイルにアイコンを設定することができますが、このアイコンを実行ファイルから抜き出すことはできないのでしょうか?
またこのようにdllのなかにもアイコンがあります。これらを抜き出すことはできないのでしょうか?
実はExtractIconEx関数というAPI関数を使えば可能です。
では以下のような説明がされています。
ExtractIconEx関数は
1 2 3 4 5 6 7 |
UINT ExtractIconExA( PCTSTR pszFile, // ファイル名 UINT nIconIndex, // アイコンのインデックス HICON *phIconLarge, // 大きなアイコンのハンドルを格納する変数 HICON *phIconSmall, // 小さなアイコンのハンドルを格納する変数 UINT nIcons // 取得するアイコンの数 ); |
指定された実行可能ファイル、ダイナミックリンクライブラリ(DLL)、またはアイコンファイルからアイコンのハンドルを取得し、配列変数に格納する。
アイコンの総数を調べたいときはnIconIndex パラメータに -1 、phIconLarge パラメータに 0 (NULL) 、phIconSmall パラメータに 0 (NULL) を指定する。
大きいアイコンと小さいアイコンも取得できる。
実行ファイルやdllからアイコンを抜きとるプログラム
まず指定されたファイルにアイコンが存在するのかを調べます。
1 |
int iconCnt = ExtractIconEx(dllPath, -1, IntPtr.Zero, IntPtr.Zero, 1); |
これが0以外を返せば戻り値の値だけアイコンが存在することがわかります。
次にアイコンを取得します。16×16のアイコンと32×32のアイコンを取得できるのですが、32×32のアイコンだけで十分なので、
1 2 |
IntPtr[] hIcon = new IntPtr[iconCnt]; uint getCnt = ExtractIconEx(dllPath, 0, hIcon, IntPtr.Zero, iconCnt); |
第4引数はIntPtr.Zeroにしています。16×16のアイコンは取得できません。
アイコンのハンドルが配列のなかに取得できたらループ文でアイコンのハンドルからBitmapを取得してpngファイルにして保存しています。
1 2 3 4 5 6 7 |
using(Icon icon = Icon.FromHandle(hIcon[idx])) { string filePath = outputFolder + "\\" + idx.ToString() + ".png"; Bitmap bitmap = icon.ToBitmap(); bitmap.Save(filePath, System.Drawing.Imaging.ImageFormat.Png); bitmap.Dispose(); } |
処理が終わったらアイコンオブジェクトも破棄しないといけないのですが、上記処理の場合、
1 2 3 |
using(Icon icon = Icon.FromHandle(hIcon[idx])) { } |
とやっているのでアイコンオブジェクトは破棄されます。またアイコンのハンドルから取得されたBitmapもDisposeされています。しかしアイコンのハンドルはそのままでは破棄されません。そこでAPI関数のDestroyIcon関数を使って後始末しなければなりません。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
public partial class Form1 : Form { public Form1() { InitializeComponent(); } [DllImport("Shell32.dll", CharSet = CharSet.Unicode)] private static extern uint ExtractIconEx(string lpszFile, int nIconIndex, IntPtr[] phiconLarge, IntPtr phiconSmall, uint nIcons); [DllImport("Shell32.dll", CharSet = CharSet.Unicode)] private static extern uint ExtractIconEx(string lpszFile, int nIconIndex, IntPtr phiconLarge, IntPtr phiconSmall, uint nIcons); [DllImport("user32.dll", CharSet = CharSet.Auto)] extern static bool DestroyIcon(IntPtr handle); public static int ImageAddIcon(string dllPath, string outputFolder) { // 指定ファイルに格納されたアイコン総数の取得 uint iconCnt = ExtractIconEx(dllPath, -1, IntPtr.Zero, IntPtr.Zero, 1); if(iconCnt == 0) { return 0; } IntPtr[] hIcon = new IntPtr[iconCnt]; try { // アイコンハンドル&ハンドル数の取得 uint getCnt; getCnt = ExtractIconEx(dllPath, 0, hIcon, IntPtr.Zero, iconCnt); if(getCnt < 1) { return 0; // アイコンがなければ終了 } for(int idx = 0; idx < getCnt; ++idx) { if(hIcon[idx] != IntPtr.Zero) { using(Icon icon = Icon.FromHandle(hIcon[idx])) { string filePath = outputFolder + "\\" + idx.ToString() + ".png"; Bitmap bitmap = icon.ToBitmap(); bitmap.Save(filePath, System.Drawing.Imaging.ImageFormat.Png); bitmap.Dispose(); } } } return (int)getCnt; } finally { foreach(IntPtr ptr in hIcon) { if(ptr != IntPtr.Zero) DestroyIcon(ptr); } } } } |
アイコンを所得して保存するメソッドができたので、あとは呼び出すだけです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public partial class Form1 : Form { private void buttonGetIcon_Click(object sender, EventArgs e) { // 実行ファイルがあるフォルダにOutputというフォルダを作成して、 // そこにアイコンを保存する string folder = Application.StartupPath + "\\Output"; // フォルダがないならつくる if(!Directory.Exists(folder)) Directory.CreateDirectory(folder); int i= ImageAddIcon(textBoxForFilePath2.Text, folder); // 取得できた数をメッセージボックスで表示 MessageBox.Show(i.ToString()); } } |
取得できたアイコン画像
ところで C:\Windows\System32\imageres.dll ならアイコンが取得できるのですが、このファイルをコピーして別のフォルダ内でアイコンを取り出そうとしてもうまくいきません。なぜだろう・・・
動きました.
ありがとー!