不使用ExtractIconEx,从一个EXE中提取所有图标

6
我需要从一个EXE文件中提取所有图标并将它们保存为磁盘文件,但我不能使用最简单的解决方案(ExtractIconEx),因为我需要以一种保留大尺寸图标的方式提取ICO文件,即使代码在运行Windows XP的系统上也是如此。稍后我将使用其他代码提取所需的图标(128x128和其他vista大小的图标),但我需要一种方法来提取所有图标及其资源,无论我的代码运行在XP、Vista还是Win7上。

到目前为止,我知道图标在RT_GROUP_ICONS中。

我知道有些人必须已经在Delphi中完成了这个任务,因为像IcoFX和resource-explorer这样的工具似乎已经完成了这个任务。我曾经看到过一个完全开源的Delphi工具,可以做到这一点,但那是多年前的事了。

我对ExtractIconEx的问题进行重新阐述 - 它无法访问整个.ico文件,它只能提取支持的图标资源格式,每个平台支持的单个分辨率(大小)。例如,在XP上,它将提取32x32或48x48的图标,但不会提取Vista格式的128x128图标。
更新:这是已接受答案的修改版本,解决了我的未来问题。如果我们调用的函数在未来的Windows版本中从User32.dll中消失,我希望它能更加优雅地失败,而不是无法加载整个应用程序。
unit ExtractIconUtils;

interface

uses Graphics,Forms,Windows;

//----------------------------------------------------------------------------
// ExtractIcons
// Call "private" MS Api to extract Icon file. This calls a publically
// documented function marked as deprecated in the MSDN documentation.
// It was no doubt Not Originally Intended to be documented, or publically
// accessed, but it provides functionality that its hard to live without.
// It exists on Windows 2000, XP, Vista, and Windows7, but might not exist
// in some future Windows version (released after year 2011).
//
// uses global   hUserDll    : THandle;
//----------------------------------------------------------------------------
function ExtractIcons(exeFilename,icoOutFileName:String;icoSize:Integer):Boolean;



var
  hUserDll    : THandle;





implementation



function ExtractIcons(exeFilename,icoOutFileName:String;icoSize:Integer):Boolean;
const
{$ifdef UNICODE}
 ExtractProcName='PrivateExtractIconsW';
{$else}
 ExtractProcName='PrivateExtractIconsA';
{$endif}
type
  TExtractFunc = function(lpszFile: PChar; nIconIndex, cxIcon, cyIcon: integer; phicon: PHANDLE; piconid: PDWORD; nicon, flags: DWORD): DWORD; stdcall;
var
  hIcon   : THandle;
  nIconId : DWORD;
  Icon    : TIcon;
  PrivateExtractIcons:TExtractFunc;
begin
  result := false;
  if (hUserDll<4) then begin
    hUserDll := LoadLibrary('user32.dll');
    if (hUserDll<4) then exit;
  end;

     { PrivateExtractIcons:
        MSDN documentation says that this function could go away in a future windows
        version, so we must try to load it, and if it fails, return false, rather than
        doing a static DLL import.
     }
    PrivateExtractIcons :=     GetProcAddress(hUserDll, ExtractProcName);

    if not Assigned(PrivateExtractIcons) then exit;

    //extract a icoSize x icoSize  icon where icoSize is one of 256,128,64,48,32,16
    if PrivateExtractIcons ( PWideChar(exeFilename),
                            0, icoSize, icoSize, @hIcon, @nIconId, 1, LR_LOADFROMFILE) <>0 then
    try
      Icon:=TIcon.Create;
      try
        Icon.Handle:=hIcon;
        Icon.SaveToFile(icoOutFileName);
        result := true;
      finally
        Icon.Free;
      end;
    finally
      DestroyIcon (hIcon);
    end;
end ;


initialization
  // none

finalization
   if (hUserDll>4) then
      FreeLibrary(hUserDll);

end.

Vista图标是256px的PNG图像,而不是128px的。 - David Heffernan
1
你尝试过PrivateExtractIcons函数吗?http://msdn.microsoft.com/en-us/library/ms648075 - RRUZ
这个功能是可行的,但是我担心它将来会出问题(感谢MSDN!) - Warren P
@WarrenP,非常好的代码更新! :) 我正需要这样的东西。顺便说一下,我认为DestroyIcon不是必要的,因为Icon.Free将通过内部的FImage: TIconImage处理它。 - kobik
@WarrenP,你可以通过编译器开关选择使用 ANSI 或 WIDE 函数调用,但是这样你就必须使用 PChar 而不是 PWideChar;o) 并将 hUserDLL 移动到实现部分,使其成为单元全局而不是全局的,以减少副作用。 - Sir Rufo
2个回答

11

PrivateExtractIcons函数可以帮助你。你可以指定要提取的图标大小:

{$IFDEF UNICODE}
    function PrivateExtractIcons(lpszFile: PChar; nIconIndex, cxIcon, cyIcon: integer; phicon: PHANDLE; piconid: PDWORD; nicon, flags: DWORD): DWORD; stdcall ; external 'user32.dll' name 'PrivateExtractIconsW';
{$ELSE}
    function PrivateExtractIcons(lpszFile: PChar; nIconIndex, cxIcon, cyIcon: integer; phicon: PHANDLE; piconid: PDWORD; nicon, flags: DWORD): DWORD; stdcall ; external 'user32.dll' name 'PrivateExtractIconsA';
{$ENDIF}

procedure TMainForm.Button1Click(Sender: TObject);
var
    hIcon   : THandle;
    nIconId : DWORD;
    Icon    : TIcon;
begin
    //Extract a 128x128 icon
    if PrivateExtractIcons ('C:\Users\Public\Documents\RAD Studio\Projects\2010\Aero Colorizer\AeroColorizer.exe', 0, 128, 128, @hIcon, @nIconId, 1, LR_LOADFROMFILE) <>0 then
    try
        Icon:=TIcon.Create;
        try
            Icon.Handle:=hIcon;
            Icon.SaveToFile(ExtractFilePath(ParamStr(0))+'Aicon.ico');
        finally
            Icon.Free;
        end;
    finally
        DestroyIcon (hIcon);
    end;
end ;

我会谨慎行事,因为页面顶部的警告。 “[此功能不适用于一般使用。在后续版本的Windows中可能会更改或不可用。]” - Bruce McGee
@Bruce,是的,你说得对,但就我个人而言,我在从Windows XP到Windows 7的各种环境中都成功使用了这个函数。 - RRUZ
哎呀,MSDN上的警告真吓人。 - Warren P
抱歉,Warren,除了ExtractIconExExtractIcon之外,这是我所知道的唯一提取图标的Windows函数。 - RRUZ
@Bruce,@Warren - 但是由于其相对于其他解决方案的简单性,我更喜欢这个。在我看来,微软仍然会将许多函数标记为过时的,而现在之所以能够工作,是因为有这种向后兼容性。但是谁知道呢.. - user532231
我测试过了,在XP和Win7上都很好用。我重写了上面建议的代码,调用了LoadLibrary,并且对于至少“尝试”使用这个酷炫的函数并且如果不行就回退到其他技术方案,感觉更有未来可期的特性。 - Warren P

1

这里有一个Delphi Praxis上的工作示例。它读取指定可执行文件的默认RT_GROUP_ICON和相关的RT_ICON资源,并将它们保存为完整的多图像.ICO文件。

在XP上,它似乎会对256像素的图像感到困惑(以无效格式保存),因此可能需要进行一些微调。


如果XP-256x256的故障可以修复,那看起来它可能会提供一个原始的解决方案。 - Warren P
256像素的图像是PNG文件,而不是位图。它们很特殊,因为在图标结构中其高度和宽度值都等于0! - David Heffernan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接