Programming Field - プログラミング Tips

実行可能ファイルからのアイコンの抽出

実行可能ファイルからアイコンを抽出する簡単な方法は、ExtractIcon または ExtractIconEx (Win32 のみ) を使います。その定義は以下の通りです。

[C/C++]

HICON STDAPICALLTYPE ExtractIconA(HINSTANCE hInst,
    LPCSTR lpszExeFileName, UINT nIconIndex);
HICON STDAPICALLTYPE ExtractIconW(HINSTANCE hInst,
    LPCWSTR lpszExeFileName, UINT nIconIndex);
UINT STDAPICALLTYPE ExtractIconExA(LPCSTR lpszFile, int nIconIndex,
    HICON *phiconLarge, HICON *phiconSmall, UINT nIcons);
UINT STDAPICALLTYPE ExtractIconExW(LPCWSTR lpszFile, int nIconIndex,
    HICON *phiconLarge, HICON *phiconSmall, UINT nIcons);
#ifdef UNICODE
#define ExtractIcon     ExtractIconW
#define ExtractIconEx   ExtractIconExW
#else
#define ExtractIcon     ExtractIconA
#define ExtractIconEx   ExtractIconExA
#endif // !UNICODE

[VB 6.0]

Declare Function ExtractIcon Lib "shell32.dll" Alias "ExtractIconA" _
    (ByVal hInst As Long, ByVal lpszExeFileName As String, _
    ByVal nIconIndex As Long) As Long
Declare Function ExtractIconEx Lib "shell32.dll" Alias "ExtractIconExA" _
    (ByVal lpszFile As String, ByVal nIconIndex As Long, _
    ByRef phiconLarge As Long, ByRef phiconSmall As Long, _
    ByVal nIcons As Long) As Long

[VB.NET]

Declare Ansi Function ExtractIcon Lib "shell32.dll" Alias "ExtractIconA" _
    (ByVal hInst As System.IntPtr, ByVal lpszExeFileName As String, _
    ByVal nIconIndex As Integer) As System.IntPtr
Declare Ansi Function ExtractIconEx Lib "shell32.dll" Alias "ExtractIconExA" _
    (ByVal lpszFile As String, ByVal nIconIndex As Integer, _
    ByRef phiconLarge As System.IntPtr, ByRef phiconSmall As System.IntPtr, _
    ByVal nIcons As Integer) As Integer
hInst アプリケーションのインスタンスハンドルを指定します。(取得できない場合は、ExtractIconEx を使ったほうが良いかもしれません。)
lpszExeFileName / lpszFile アイコンを取得するファイル名を指定します。このファイル名は、EXE、DLL、ICO などの拡張子のファイルです。
nIconIndex 取得するアイコンのインデックスを、0 から始まる値で指定します。また、負の値(-100 など)を指定すると、その絶対値(-100 → 100)を ID に持つアイコンを取得します。
ExtractIconEx の場合は、ここで指定した値に当たるアイコンから nIcons で指定した数だけアイコンを取得します。たとえば、ここで 1 を指定し、nIcons で 4 を指定すると、phiconLarge および phiconSmall に、インデックス 1~4 のアイコンが格納されます。
phiconLarge, phiconSmall アイコンを受け取る HICON 型([VB] Long[VB.NET] System.IntPtr) の配列を指定します。この配列のアイテム数は、nIcons の値以上でなければなりません。phiconLarge には大きいアイコン(32 x 32)、phiconSmall には小さいアイコン(16 x 16)が格納されます。
phiconLarge と phiconSmall の両方に NULL (0) を指定し、nIconIndex に -1、nIcons に 0 (これは念のため)を指定することで、ファイルに含まれるアイコンの総数を取得することができます。
nIcons 取得するアイコンの数を指定します。

※ ExtractIcon の戻り値は HICON ([VB] Long[VB.NET] System.IntPtr) です。取得したアイコンのハンドル(HICON)は、使用後必ず DestroyIcon で破棄する必要があります。

これらの関数は、データ内からアイコンデータを取り出し、そのデータを元に自動的にアイコンのハンドルを作成してくれます。

ところが、これらの関数では、リソース ID からアイコンを作成するのに失敗することがあります(どの条件で失敗するかは忘れた・・・)。レジストリのファイル関連付けのデータにあるような、アイコンを含むファイル名と ID (ここでのデータはマイナスになっていて、絶対値が ID の値になってます) からアイコンを作成したり、16x16 や 32x32 以外のアイコンを取得する(ExtractIcon(Ex)ではできない)には、LoadLibraryExFindResourceLookupIconIdFromDirectoryExCreateIconFromResource などの関数を使って取得することが出来ます。

以下に例を示します。


[C/C++]

// EnumResourceNames で使うデータ
struct _CMyExtractIconData
{
    // 取得するアイコンのインデックス
    int nIndex;
    // 現在列挙しているアイコンのインデックス
    int nNowPos;
    // 見つかったかどうか
    bool bFound;
    // true の場合は uID、false の場合は lpszName を使う
    // (リソースの ID が文字列の場合を考慮)
    bool bIsID;
    // リソース ID (数値)
    UINT uID;
    // リソース ID (文字列)
    LPTSTR lpszName;
};

// EnumResourceNames のコールバック関数
extern "C" BOOL CALLBACK _MyEnumResNameProc(HINSTANCE hInstance, LPCTSTR lpszType,
    LPTSTR lpszName, _CMyExtractIconData FAR* pData)
{
    // 現在のインデックスが欲しいアイコンのインデックスと一致するかどうか
    if (pData->nNowPos == pData->nIndex)
    {
        // 一致 → 見つかった
        pData->bFound = true;
        // lpszName の上位ワードが 0 のときは ID が数値
        // (文字列のポインタで上位ワードが 0 のものはまず無い)
        if (pData->bIsID = (HIWORD(lpszName) == 0))
            pData->uID = LOWORD(lpszName);
        else
            // 文字列のコピーを作成する
            pData->lpszName = _tcsdup(lpszName);
        // FALSE を返して列挙を終了する
        return FALSE;
    }
    // 次のインデックスにしておく
    pData->nNowPos++;
    return TRUE;
}

// lpszPathName:  アイコンのデータが入っているアプリケーション/DLL
// nIndexOrID:    正の値(0 含む)ならインデックス、負の値なら ID
// bSmallIcon:    16x16 のサイズのアイコンを取得するかどうか (false なら 32x32)
HICON MyExtractIcon(LPCTSTR lpszPathName, int nIndexOrID, bool bSmallIcon)
{
    HINSTANCE hInstance;
    HICON hIconRet;
    _CMyExtractIconData data;
    HRSRC hRes, hRes2;
    HGLOBAL hMem, hMem2;
    LPVOID lpv, lpv2;

    if (!lpszPathName)
        return NULL;
    // 実行はしないので LOAD_LIBRARY_AS_DATAFILE を指定する
    hInstance = LoadLibraryEx(lpszPathName, NULL,
        LOAD_LIBRARY_AS_DATAFILE | LOAD_WITH_ALTERED_SEARCH_PATH);
    if (!hInstance)
        return NULL;

    hIconRet = NULL;
    if (nIndexOrID < 0)
    {
        // 負の値 = 識別子
        // LoadIcon や LoadImage だと Win95/98/Me で読み込めない・・・
        data.bIsID = true;
        data.uID = (UINT) (-nIndexOrID);
    }
    else
    {
        // 正の値 = インデックス
        // リソースを列挙して読み込み
        data.nIndex = nIndexOrID;
        data.nNowPos = 0;
        data.bFound = false;
        EnumResourceNames(hInstance, RT_GROUP_ICON,
            (ENUMRESNAMEPROC) _MyEnumResNameProc,
            (LPARAM)(_CMyExtractIconData FAR*) &data);
        // 見つからなかったら終了
        if (!data.bFound)
        {
            FreeLibrary(hInstance);
            return NULL;
        }
    }

    // グループアイコン リソースを取得
    // (サイズや色情報が複数含まれている可能性もあるため)
    hRes = FindResource(hInstance,
        data.bIsID ? MAKEINTRESOURCE(data.uID) : (LPCTSTR) data.lpszName,
        RT_GROUP_ICON);
    if (hRes)
    {
        // FindResource のあとは LoadResource、LockResource
        hMem = LoadResource(hInstance, hRes);
        lpv = LockResource(hMem);
        // アイコン本体の ID (指定したサイズ、色を持つアイコン) を取得する関数
        // (第 2 引数が FALSE の場合カーソルを取得)
        // (bSmallIcon が true なら 16x16、false なら 32x32)
        // (色はデフォルトのものを使用する)
        // ※ ここでサイズを任意のものに指定できます。(48x48 など)
        data.uID = LookupIconIdFromDirectoryEx((PBYTE) lpv, TRUE,
            bSmallIcon ? 16 : 32, bSmallIcon ? 16 : 32, LR_DEFAULTCOLOR);
        // アイコン本体のリソースを取得
        hRes2 = FindResource(hInstance, MAKEINTRESOURCE(data.uID), RT_ICON);
        if (hRes2)
        {
            // FindResource のあとは LoadResource、LockResource
            hMem2 = LoadResource(hInstance, hRes2);
            lpv2 = LockResource(hMem2);
            // リソースからアイコンを作成
            hIconRet = CreateIconFromResource((PBYTE) lpv2,
                SizeofResource(hInstance, hRes2),
                TRUE, 0x00030000);
            // Win32 ではロック解除、解放はいらない (呼び出せない)
            //UnlockResource(hMem2);
            //FreeResource(hRes2);
        }
        //UnlockResource(hMem);
        //FreeResource(hRes);
    }

    // 文字列を使用している場合は解放する
    if (!data.bIsID)
        free(data.lpszName);

    FreeLibrary(hInstance);
    return hIconRet;
}

途中で「LoadIcon や LoadImage だと Win95/98/Me で読み込めない・・・」と書いてあるのは、LoadLibraryEx で読み込んだ実行可能ファイルに対して、Win95/98/Me では LoadIcon LoadImage などでリソースを読み込めないため、結局 FindResource LoadResource を使う必要がある、という事です。(これは LoadBitmap などでも同じだと思われます。)

[VB 6.0]

※ コメントがほとんど入っていません。動作自体は上と同じなのでそれを参考にしてください。

Public Const LR_DEFAULTCOLOR     As Long = &H0&
Public Const LR_MONOCHROME       As Long = &H1&
Public Declare Sub CopyMemory Lib "kernel32.dll" Alias "RtlMoveMemory" _
    (ByRef Dest As Any, ByRef Source As Any, ByVal Length As Long)
Public Declare Function lstrlen Lib "kernel32.dll" Alias "lstrlenA" _
    (ByVal lpszString As Any) As Long

Public Type MyExtractIconData
    Index As Long
    NowPos As Long
    Found As Boolean
    IsID As Boolean
    ID As Long
    Name As String
End Type

Public Const DONT_RESOLVE_DLL_REFERENCES   As Long = &H1&
Public Const LOAD_LIBRARY_AS_DATAFILE      As Long = &H2&
Public Const LOAD_WITH_ALTERED_SEARCH_PATH As Long = &H8&
Public Const LOAD_IGNORE_CODE_AUTHZ_LEVEL  As Long = &H10&
Public Declare Function LoadLibraryEx Lib "kernel32.dll" Alias "LoadLibraryExA" _
    (ByVal lpLibFileName As String, ByVal hFile As Long, ByVal dwFlags As Long) As Long
Public Declare Function FreeLibrary Lib "kernel32.dll" (ByVal hModule As Long) As Long
Public Const RT_CURSOR           As Long = 1
Public Const RT_BITMAP           As Long = 2
Public Const RT_ICON             As Long = 3
Public Const RT_MENU             As Long = 4
Public Const RT_DIALOG           As Long = 5
Public Const RT_STRING           As Long = 6
Public Const RT_FONTDIR          As Long = 7
Public Const RT_FONT             As Long = 8
Public Const RT_ACCELERATOR      As Long = 9
Public Const RT_RCDATA           As Long = 10
Public Const RT_MESSAGETABLE     As Long = 11
Public Const RT_GROUP_CURSOR     As Long = 12
Public Const RT_GROUP_ICON       As Long = 14
Public Const RT_VERSION          As Long = 16
Public Const RT_DLGINCLUDE       As Long = 17
Public Const RT_PLUGPLAY         As Long = 19
Public Const RT_VXD              As Long = 20
Public Const RT_ANICURSOR        As Long = 21
Public Const RT_ANIICON          As Long = 22
Public Const RT_HTML             As Long = 23
Public Const RT_MANIFEST         As Long = 24
Public Declare Function EnumResourceNames Lib "kernel32.dll" Alias "EnumResourceNamesA" _
    (ByVal hModule As Long, ByVal lpszType As Any, _
    ByVal lpEnumFunc As Long, ByRef lParam As Any) As Long
Public Declare Function FindResource Lib "kernel32.dll" Alias "FindResourceA" _
    (ByVal hModule As Long, ByVal lpName As Any, ByVal lpType As Any) As Long
Public Declare Function LoadResource Lib "kernel32.dll" _
    (ByVal hModule As Long, ByVal hResInfo As Long) As Long
Public Declare Function LockResource Lib "kernel32.dll" (ByVal hResData As Long) As Long
Public Declare Function SizeofResource Lib "kernel32.dll" _
    (ByVal hModule As Long, ByVal hResInfo As Long) As Long
Public Declare Function LookupIconIdFromDirectoryEx Lib "user32.dll" _
    (ByVal pResBits As Long, ByVal fIcon As Long, ByVal cxDesired As Long, _
    ByVal cyDesired As Long, ByVal Flags As Long) As Long
Public Declare Function CreateIconFromResource Lib "user32.dll" _
    (ByVal pResBits As Long, ByVal dwResSize As Long, ByVal fIcon As Long, _
    ByVal dwVer As Long) As Long

' ここまで宣言部
' ここから関数定義

Public Function MyEnumResNameProc(ByVal hInst As Long, ByVal lpszType As Long, _
    ByVal lpszName As Long, ByRef Data As MyExtractIconData) As Long
    Dim ln As Long
    If Data.NowPos = Data.Index Then
        Data.Found = True
        Data.IsID = ((lpszName And &HFFFF0000) = 0)
        If Not Data.IsID Then
            ln = lstrlen(lpszName)
            Data.Name = VBA.Strings.String$(ln + 1, 0)
            Call CopyMemory(ByVal Data.Name, ByVal lpszName, ln)
            Data.Name = VBA.Strings.Left$(Data.Name, _
                VBA.Strings.InStr(Data.Name, VBA.Chr$(0)) - 1)
            ' EnumResourceNames で MyExtractIconData を渡すときに
            ' 文字列の変換が発生するので、ここで対処しておく
            Data.Name = VBA.Strings.StrConv(Data.Name, vbFromUnicode)
        Else
            Data.ID = (lpszName And &HFFFF&)
        End If
        MyEnumResNameProc = 0
        Exit Function
    End If
    Data.NowPos = Data.NowPos + 1
    MyEnumResNameProc = 1
End Function

Public Function MyExtractIcon(ByVal PathName As String, ByVal IndexOrID As Long, _
    ByVal SmallIcon As Boolean)
    Dim hInstance As Long
    Dim hRes As Long, hRes2 As Long
    Dim hMem As Long, hMem2 As Long
    Dim lpv As Long, lpv2 As Long
    Dim Data As MyExtractIconData

    MyExtractIcon = 0
    hInstance = LoadLibraryEx(PathName, 0, LOAD_LIBRARY_AS_DATAFILE Or _
        LOAD_WITH_ALTERED_SEARCH_PATH)
    If hInstance = 0 Then Exit Function

    If IndexOrID < 0 Then
        Data.IsID = True
        Data.ID = -(IndexOrID + 1)
        Data.Found = True
    Else
        Data.Index = IndexOrID
        Data.NowPos = 0
        Data.Found = False
        Call EnumResourceNames(hInstance, RT_GROUP_ICON, AddressOf MyEnumResNameProc, Data)
        If Not Data.Found Then
            Call FreeLibrary(hInstance)
            Exit Function
        End If
    End If

    If Data.IsID Then
        hRes = FindResource(hInstance, Data.ID, RT_GROUP_ICON)
    Else
        hRes = FindResource(hInstance, Data.Name, RT_GROUP_ICON)
    End If
    If hRes <> 0 Then
        hMem = LoadResource(hInstance, hRes)
        lpv = LockResource(hMem)
        Data.ID = LookupIconIdFromDirectoryEx(lpv, True, _
            VBA.Interaction.IIf(SmallIcon, 16, 32), VBA.Interaction.IIf(SmallIcon, 16, 32), _
            LR_DEFAULTCOLOR)
        If Data.ID <> 0 Then
            hRes2 = FindResource(hInstance, Data.ID, RT_ICON)
            hMem2 = LoadResource(hInstance, hRes2)
            lpv2 = LockResource(hMem2)
            MyExtractIcon = CreateIconFromResource(lpv2, _
                SizeofResource(hInstance, hRes2), 1, &H30000)
        End If
    End If
    Call FreeLibrary(hInstance)
End Function

[VB 6.0] ハンドルをオブジェクト(IPictureDisp、StdPicture など、ピクチャコントロールなどに設定できる型)にするには、「VB 6.0 における画像ハンドルの変換」を参照してください。

[VB.NET]

長くなるのでページを分けました。→ 「実行可能ファイルからのアイコンの抽出 for VB.NET」

最終更新日: 2006/09/13