网络安全 频道

谈Delphi编程中文件格式的应用

三:图标文件ICO格式程序编程
Windows下的可执行文件大多都有一个图标的.如果这个程序是你自己编写的,那么你可以轻松的在编译为EXE之前把图标更换掉.但是如果这个EXE不是你的呢?怎么办?当然是用工具了.ResHacker(资源黑客)就是一个很好的工具.但是如果想自己编写程序来实现呢?
图标在可执行文件里面实际上是一项资源.Windows提供了一个API函数来取出EXE里面的图标.函数原型为:HICON ExtractIcon(HINSTANCE hInst,LPCTSTR lpszExeFileName,UINT nIconIndex);其中第一个参数为实例句柄,第二个参数为需要操作的EXE,DLL,BMP或ICON等包含有图标资源的文件名,第三个参数为需要取出的图标在该EXE里面的索引(因为一个EXE文件里面可能含有多个图标).如果这个参数为0,那么将返回第一个图标,如果这个参数为-1,将返回该文件可能含有的所有图标数.如果该文件含有该索引图标,函数将返回该图标的句柄,否则返回值NULL.
那么,到底我们应该怎么样才能更换一个EXE的图标呢?如果你熟悉PE文件结构的话就很简单了.不过PE文件格式是比较复杂的,讲述它的话要费很大篇幅.实际上,你可以这样简单的看一个EXE文件的组成:EXE文件=文件头之类+图标资源+文件尾.也就是说,你不用管它的文件头和文件尾之类,只要找到图标在该EXE里面的位置,然后用你的图标覆盖它即可.
不过需要注意的是,图标是有多种格式的,比如说16X16的16色,32X32的16色,16X16的32色等等.用这种方法更换图标的话必须注意格式要一致.另外,ExtractIcon函数返回的将是32X32的16色图标.这是个很有趣的地方.也就是说,无论你操作的文件或图标格式是怎么样,它取出的都是32X32的16色图标.而Delphi默认的那个图标就是这个格式的.
我们打开Delphi,新建一个工程.直接编译后退出.这个得到的EXE我们将用来做"实验品".再新建一个工程,这个才是我们真正要写的程序.往窗口添加两个名字分别为Next_Icon, Prev_Icon的TSpeedButton.作用是枚举图标的.添加一个Image1用来显示图标.一个名为Edit_SourceFile的TEdit用来显示选择要取出的EXE之类的文件名称.一个OpenDialog,一个SaveDialog和三个Button.最后记得在Use部分添加ShellApi.全部代码如下:
unit Unit1;

interface

uses
ShellApi{必须添加此单元}, Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, ExtCtrls, Buttons;

type
TForm1 = class(TForm)
Next_Icon: TSpeedButton;
Prev_Icon: TSpeedButton;
Image1: TImage;
Edit_SourceFile: TEdit;
Button1: TButton;
Button2: TButton;
Button3: TButton;
OpenDialog1: TOpenDialog;
SaveDialog1: TSaveDialog;
procedure Button1Click(Sender: TObject);
procedure Prev_IconClick(Sender: TObject);
procedure Next_IconClick(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure Button3Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
procedure Extract_Icon;
function ChangeExeIcon(ExeFile,IconFile:string;Index:Integer=0):Boolean;
{ Private declarations }
public
{ Public declarations }
end;

var
Form1: TForm1;
Icon_Index: integer;
implementation

{$R *.DFM}
procedure TForm1.FormCreate(Sender: TObject);
begin
Button1.Caption:='取出图标';
Button2.Caption:='保存图标';
Button3.Caption:='更换图标';
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
OpenDialog1.Filter := '所有支持类型(*.EXE,*.DLL,*.OCX,*.ICL,*.ICO,*.BMP)|*.exe;*.dll;*.ocx;*.icl;*.ico;*.bmp|所有文件 (*.*)|*.*';
if OpenDialog1.Execute
then
begin
Edit_SourceFile.Text := OpenDialog1.Filename;
Icon_Index := 0;
Extract_Icon;
end;
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
SaveDialog1.Filter :='图标文件(*.ICO)|*.ico';
if SaveDialog1.Execute then
begin
if Copy(SaveDialog1.FileName, Length(SaveDialog1.FileName)-3, 1) = '.' then
Image1.Picture.Icon.SaveToFile(SaveDialog1.FileName)
else
Image1.Picture.Icon.SaveToFile(SaveDialog1.FileName + '.ico');
end;
end;

procedure TForm1.Button3Click(Sender: TObject);
var
ExeFile:String;
begin
OpenDialog1.Filter := 'EXE文件(*.EXE)|*.exe';
OpenDialog1.Title:='请选择需要更换图标的EXE';
if OpenDialog1.Execute then
begin
ExeFile:=OpenDialog1.FileName;
OpenDialog1.Filter := '图标文件(*.ICO)|*.ico';
OpenDialog1.Title:='请选择需要更换的图标文件';
OpenDialog1.FileName:='';{Clear the Old Filename}
if OpenDialog1.Execute then
if ChangeExeIcon(ExeFile,OpenDialog1.FileName) then
Application.MessageBox('更换图标成功!',Pchar(Application.Title),MB_ICONINFORMATION+MB_OK)
else
Application.MessageBox('更换图标失败!',Pchar(Application.Title),MB_ICONERROR+MB_OK)
else
Exit; {Not Select Icon File}
end;
end;

procedure TForm1.Prev_IconClick(Sender: TObject); //枚举前一个图标
begin
if not (FileExists(Edit_SourceFile.Text)) or (Icon_Index <= 0) then Exit;
Icon_Index := Icon_Index - 1;
Extract_Icon;
end;

procedure TForm1.Next_IconClick(Sender: TObject);//枚举下一个图标
begin
if not (FileExists(Edit_SourceFile.Text)) then Exit;
Icon_Index := Icon_Index + 1;
Extract_Icon;
end;

procedure TForm1.Extract_Icon;
var
icon_handle: Longint;
buffer: array[0..1024] of Char;
begin
if not (FileExists(Edit_SourceFile.Text)) then Exit;

StrPCopy(Buffer, Edit_SourceFile.Text);
icon_handle := ExtractIcon(self.Handle, buffer, icon_index);

if Icon_Handle = 0 {Did we get a valid handle back?}
then
begin {No}
if Icon_Index = 0 {Is this the first icon in the file?}
then {Yes. There can't be any icons in this file}
begin
Application.MessageBox('这个文件没有发现图标,请重新选择!','信息',MB_ICONINFORMATION+MB_OK);
Image1.Visible := False;
end
else {No. We must have gone beyond the limit. Step back}
Icon_Index := Icon_Index - 1;
Exit;
end;
{We now have our extracted icon. Save it to a temp file in readiness for the modifocation}
Image1.Picture.Icon.Handle := icon_handle;
Image1.Visible := True;
end;

function TForm1.ChangeExeIcon(ExeFile,IconFile:string;Index:Integer=0):Boolean;
var
TempStream,NewIconMemoryStream:TMemoryStream;
OldIconStrings,ExeStrings,ExeIconStrings:TStringStream;
ExeIcon:TIcon;
IconPosition,IconLength,IconHeadLength:Integer;
IconHandle:HICON;
ExeFileStream,IconFileStream:TFileStream;
begin
Result:=False;
IconHeadLength:=126;
if (not FileExists(ExeFile)) or (not FileExists(IconFile)) then Exit;
try
ExeFileStream:=TFileStream.Create(ExeFile,fmOpenReadWrite+fmShareDenyWrite);
ExeStrings:=TStringStream.Create('');
ExeStrings.Position:=0;
ExeFileStream.Position:=0;
ExeStrings.CopyFrom(ExeFileStream,0);
ExeIcon:=TIcon.Create;
IconHandle:=ExtractIcon(Application.Handle,Pchar(ExeFile),Index);
if IconHandle<=1 then
begin
Application.MessageBox('EXE中没有找到该序列的图标!',Pchar(Application.Title),MB_ICONERROR+MB_OK);
Exit;
end;
ExeIcon.Handle:=IconHandle;
ExeIconStrings:=TStringStream.Create('');
ExeIcon.SaveToStream(ExeIconStrings);
ExeIcon.Free;
ExeIcon:=nil;
IconLength:=ExeIconStrings.Size-IconHeadLength;
ExeIconStrings.Position:=IconHeadLength;
OldIconStrings:=TStringStream.Create('');
OldIconStrings.Position:=0;
ExeIconStrings.Position:=IconHeadLength;
OldIconStrings.CopyFrom(ExeIconStrings,IconLength);
ExeIconStrings.Free;
IconPosition:=Pos(OldIconStrings.DataString,ExeStrings.DataString);
ExeStrings.Free;
ExeStrings:=nil;
OldIconStrings.Free;
IconFileStream:=TFileStream.Create(IconFile,fmOpenRead+fmShareDenyNone);
NewIconMemoryStream:=TMemoryStream.Create;
IconFileStream.Position:=IconHeadLength;
NewIconMemoryStream.Position:=0;
NewIconMemoryStream.CopyFrom(IconFileStream,IconFileStream.Size-IconHeadLength);
IconFileStream.Free;
if IconPosition<=0 then
begin
Application.MessageBox('EXE中没有找到该图标的数据!',Pchar(Application.Title),MB_ICONERROR+MB_OK);
Exit;
end;

if IconLength<>NewIconMemoryStream.Size then
begin
TempStream:=TMemoryStream.Create;
ExeFileStream.Position:=IconPosition+IconLength-1;
TempStream.Position:=0;
TempStream.CopyFrom(ExeFileStream,ExeFileStream.Size-ExeFileStream.Position);
ExeFileStream.Position:=IconPosition-1;
NewIconMemoryStream.Position:=0;
ExeFileStream.CopyFrom(NewIconMemoryStream,0);
TempStream.Position:=0;
ExeFileStream.CopyFrom(TempStream,0);
ExeFileStream.Position:=0;
ExeFileStream.Size:=IconPosition+IconLength-1+TempStream.Size;
TempStream.Free;
end
else
begin
ExeFileStream.Position:=IconPosition-1;
NewIconMemoryStream.Position:=0;
ExeFileStream.CopyFrom(NewIconMemoryStream,0);
end;
NewIconMemoryStream.Free;
Result:=True;
finally
ExeFileStream.Free;
end;
end;
end.
运行程序,点"取出图标",选择一个EXE,然后点"保存图标"将其ICO保存为文件.然后点"更换图标",选择我们刚才编译得到的"实验品"和取出的图标,即可将图标更改掉了.
自定义函数ChangeExeIcon的实现过程如下:先用ExtractIcon将图标释放出来保存为文件,然后将其与EXE比较在该EXE里面找到图标的位置,然后将新图标的内容覆盖原来的图标.实际上,这个查找过程还不够完美,因为它将两者都转化为TStringStream再比较,如果EXE文件很大的话是很费内存的.Delphi本身提供了一个例子用来查找位置的,该例子位于Delphi5DemosResxplor下,读者可以结合它来作出高效的图标更换工具.

0
相关文章