vb替换exe文件图标的正确姿势
最近研究使用vb替换exe文件图标资源。然而网络上所谓的“已修正方法”,在win10下根本无法正常工作,替换后图标不能正常显示。

使用Resource Hacker查看,是这样的。


经过研究,vb完美替换exe文件图标终于得以实现。以下给出正确替换exe文件图标的姿势,并将在末尾贴出源码。
首先,介绍一下图标在exe文件与图标(ico)文件中的存储方式。
1.可执行文件资源
图标(ICON)是资源的一种类别。在资源文件中,其内容仅包含图片像素信息。一个可执行文件可包含多个图标资源。
图标组(GROUP_ICON)是资源的一种类别,描述各个图标的大小、调色板等信息,并通过序号指向图标(像素信息)。一个可执行文件仅包含一个图标组。
2.图标文件(ico)
一个图标文件中可以存储若干个图标。在图标文件的文件头(ICONDIR),存储了图标个数等信息。紧接着,就是各个图标的信息(ICONDIRENTRY),其中#一项指向像素信息的起始位置。
接下来,是几个用于替换资源的API。
1.BeginUpdateResource
开始资源替换,返回一个句柄。
2.UpdateResource
增加、替换、删除资源。其操作会被存储于类似缓冲区的位置,不会立即生效。
3.EndUpdateResource
结束资源替换。此时累积的修改会被写入文件。
此时,我们已经可以分析“已修正方案”的问题所在了。
PrivateType ICONDIRENTRY
bWidth As Byte
bHeight As Byte
bColorCount As Byte
bReserved As Byte
wPlanes As Integer
wBitCount As Integer
dwBytesInRes As Long
dwImageOffset As Long
EndType
PrivateType ICONDIR
idReserved As Integer
idType As Integer
idCount As Integer
'idEntries As ICONDIRENTRY
EndType
PrivateType GRPICONDIRENTRY
bWidth As Byte
bHeight As Byte
bColorCount As Byte
bReserved As Byte
wPlanes As Integer
wBitCount As Integer
dwBytesInRes As Long
nID As Integer
EndType
PrivateType GRPICONDIR
idReserved As Integer
idType As Integer
idCount As Integer
idEntries As GRPICONDIRENTRY
EndType
PrivateFunction ChangeExeIcon(ByVal IconFile As String, ByVal ExeFile As String) AsBoolean
Dim stID As ICONDIR
Dim stIDE As ICONDIRENTRY
Dim stGID As GRPICONDIR
Dim hFile As Long
Dim pIcon() As Byte, pGrpIcon() As Byte
Dim nSize As Long, nGSize As Long
Dim dwReserved As Long
Dim hUpdate As Long
Dim ret As Long
hFile = CreateFile(IconFile, GENERIC_READ,0, ByVal 0&, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0)
If hFile = INVALID_HANDLE_VALUE Then Exit Function
ret = ReadFile(hFile, stID, Len(stID),dwReserved, ByVal 0&)
ret = ReadFile(hFile, stIDE, Len(stIDE),dwReserved, ByVal 0&)
nSize = stIDE.dwBytesInRes
ReDim pIcon(nSize - 1)
SetFilePointer hFile, stIDE.dwImageOffset,ByVal 0&, FILE_BEGIN
ret = ReadFile(hFile, pIcon(0), nSize,dwReserved, ByVal 0&) '仅读取了第一个图标
With stGID
.idType = 1
.idCount = stID.idCount '按照图标文件总图标数建立图标组
.idReserved = 0
CopyMemory stGID.idEntries, stIDE, 12
.idEntries.nID = 0 '将图标组第一项指向了0号图标
End With
nGSize = Len(stGID)
ReDim pGrpIcon(nGSize - 1)
CopyMemory pGrpIcon(0), stGID, nGSize
hUpdate = BeginUpdateResource(ExeFile,False)
ret = UpdateResource(hUpdate,RT_GROUP_ICON, 1, 0, pGrpIcon(0), nGSize)
ret = UpdateResource(hUpdate, RT_ICON, 1,0, pIcon(0), nSize) '将图标作为序号1放入
EndUpdateResource hUpdate, False
CloseHandlehFile
End Function
(代码已部分简化)
其实,这份源码已经做了正确的操作,却没有将其完善。
代码仅将图标文件中的首个图标替换至exe文件中,构造图标组数据,却是按照图标文件中总图标数,也没有正确指向图标。这就导致图标组中可能会出现空图标,并且win10下不能正确显示(XP等或许会对这错误的资源信息自动修正使其能够正常显示)。
因此,我们只需要将这些操作加以修改,完整地将图标数据导入即可。
以下给出源码:
PrivateDeclare Function CreateFile Lib "kernel32" Alias"CreateFileA" (ByVal lpFileName As String, ByVal dwDesiredAccess AsLong, ByVal dwShareMode As Long, lpSecurityAttributes As Any, ByValdwCreationDisposition As Long, ByVal dwFlagsAndAttributes As Long, ByValhTemplateFile As Long) As Long
PrivateDeclare Function ReadFile Lib "kernel32" (ByVal hFile As Long,lpBuffer As Any, ByVal nNumberOfBytesToRead As Long, lpNumberOfBytesRead AsLong, lpOverlapped As Any) As Long
PrivateDeclare Function SetFilePointer Lib "kernel32" (ByVal hFile As Long,ByVal lDistanceToMove As Long, lpDistanceToMoveHigh As Long, ByVal dwMoveMethodAs Long) As Long
PrivateDeclare Function BeginUpdateResource Lib "kernel32" Alias"BeginUpdateResourceA" (ByVal pFileName As String, ByValbDeleteExistingResources As Long) As Long
PrivateDeclare Function UpdateResource Lib "kernel32" Alias"UpdateResourceA" (ByVal hUpdate As Long, ByVal lpType As Long, ByVallpName As Long, ByVal wLanguage As Long, lpData As Any, ByVal cbData As Long)As Long
PrivateDeclare Function EndUpdateResource Lib "kernel32" Alias"EndUpdateResourceA" (ByVal hUpdate As Long, ByVal fDiscard As Long)As Long
PrivateDeclare Function CloseHandle Lib "kernel32" (ByVal hObject As Long)As Long
PrivateDeclare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory"(Destination As Any, Source As Any, ByVal Length As Long)
PrivateDeclare Function GetLastError Lib "kernel32" () As Long
PrivateConst INVALID_HANDLE_VALUE = -1
PrivateConst GENERIC_READ = &H80000000
PrivateConst FILE_ATTRIBUTE_NORMAL = &H80
PrivateConst FILE_BEGIN = 0
PrivateConst OPEN_EXISTING = 3
PrivateConst RT_ICON = 3&
PrivateConst RT_RCDATA = 10&
PrivateConst DIFFERENCE As Long = 11
PrivateConst RT_GROUP_ICON As Long = (RT_ICON + DIFFERENCE)
PrivateType ICONDIRENTRY
bWidth As Byte
bHeight As Byte
bColorCount As Byte
bReserved As Byte
wPlanes As Integer
wBitCount As Integer
dwBytesInRes As Long
dwImageOffset As Long
EndType
PrivateType ICONDIR
idReserved As Integer
idType As Integer
idCount As Integer
'idEntries As ICONDIRENTRY
EndType
PrivateType GRPICONDIRENTRY
bWidth As Byte
bHeight As Byte
bColorCount As Byte
bReserved As Byte
wPlanes As Integer
wBitCount As Integer
dwBytesInRes As Long
nID As Integer
EndType
PrivateType GRPICONDIR
idReserved As Integer
idType As Integer
idCount As Integer
'idEntries As GRPICONDIRENTRY
EndType
PrivateSub UpdataIcon(ByVal ExeFile As String, ByVal IconFile As String)
Dim pIcon() As Byte
Dim pGI() As Byte
Dim pGID() As Byte
Dim nSize As Long
Dim nIcon As Long
Dim hFile As Long
Dim dwReserved As Long
'打开图标文件
hFile = CreateFile(IconFile, GENERIC_READ,0, ByVal 0&, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0)
If hFile = INVALID_HANDLE_VALUE Then ExitSub
'打开EXE文件
Dim hUpdate As Long
hUpdate = BeginUpdateResource(ExeFile,False)
'读取图标文件头
Dim ID As ICONDIR
Call ReadFile(hFile, ID, Len(ID),dwReserved, ByVal 0&)
nIcon = ID.idCount
Dim i As Long
'读取各个图标信息(入口)
Dim IDE() As ICONDIRENTRY
ReDim IDE(nIcon - 1)
For i = 1 To nIcon
Call ReadFile(hFile, IDE(i - 1),Len(IDE(i - 1)), dwReserved, ByVal 0&)
Next
'读取图标文件内容并写入EXE
For i = 1 To nIcon
nSize = IDE(i - 1).dwBytesInRes
ReDim pIcon(nSize - 1)
SetFilePointer hFile, IDE(i -1).dwImageOffset, ByVal 0&, FILE_BEGIN
Call ReadFile(hFile, pIcon(0), nSize,dwReserved, ByVal 0&)
Call UpdateResource(hUpdate, RT_ICON,i, 0, pIcon(0), nSize)
Next
'构建EXE图标组文件头
Dim GID As GRPICONDIR
With GID
.idReserved = 0
.idType = 1
.idCount = ID.idCount
End With
Dim GIDE As GRPICONDIRENTRY
Dim sGID As Long: sGID = Len(GID)
Dim sGIDE As Long: sGIDE = Len(GIDE)
ReDim pGID(sGID + nIcon * sGIDE - 1)
'构建图标组文件头字节数据
CopyMemory pGID(0), GID, sGID
'构建图标组各图标数据与对应的字节数据
For i = 1 To nIcon
CopyMemory GIDE, IDE(i - 1), 12
GIDE.nID = i
CopyMemory pGID(sGID + (i - 1) *sGIDE), GIDE, sGIDE
Next
'写入图标组数据
Call UpdateResource(hUpdate, RT_GROUP_ICON,1, 0, pGID(0), sGID + nIcon * sGIDE)
'将所有数据跟提交至EXE文件
EndUpdateResource hUpdate, False
CloseHandle hFile
EndSub

最近研究使用vb替换exe文件图标资源。然而网络上所谓的“已修正方法”,在win10下根本无法正常工作,替换后图标不能正常显示。

使用Resource Hacker查看,是这样的。


经过研究,vb完美替换exe文件图标终于得以实现。以下给出正确替换exe文件图标的姿势,并将在末尾贴出源码。
首先,介绍一下图标在exe文件与图标(ico)文件中的存储方式。
1.可执行文件资源
图标(ICON)是资源的一种类别。在资源文件中,其内容仅包含图片像素信息。一个可执行文件可包含多个图标资源。
图标组(GROUP_ICON)是资源的一种类别,描述各个图标的大小、调色板等信息,并通过序号指向图标(像素信息)。一个可执行文件仅包含一个图标组。
2.图标文件(ico)
一个图标文件中可以存储若干个图标。在图标文件的文件头(ICONDIR),存储了图标个数等信息。紧接着,就是各个图标的信息(ICONDIRENTRY),其中#一项指向像素信息的起始位置。
接下来,是几个用于替换资源的API。
1.BeginUpdateResource
开始资源替换,返回一个句柄。
2.UpdateResource
增加、替换、删除资源。其操作会被存储于类似缓冲区的位置,不会立即生效。
3.EndUpdateResource
结束资源替换。此时累积的修改会被写入文件。
此时,我们已经可以分析“已修正方案”的问题所在了。
PrivateType ICONDIRENTRY
bWidth As Byte
bHeight As Byte
bColorCount As Byte
bReserved As Byte
wPlanes As Integer
wBitCount As Integer
dwBytesInRes As Long
dwImageOffset As Long
EndType
PrivateType ICONDIR
idReserved As Integer
idType As Integer
idCount As Integer
'idEntries As ICONDIRENTRY
EndType
PrivateType GRPICONDIRENTRY
bWidth As Byte
bHeight As Byte
bColorCount As Byte
bReserved As Byte
wPlanes As Integer
wBitCount As Integer
dwBytesInRes As Long
nID As Integer
EndType
PrivateType GRPICONDIR
idReserved As Integer
idType As Integer
idCount As Integer
idEntries As GRPICONDIRENTRY
EndType
PrivateFunction ChangeExeIcon(ByVal IconFile As String, ByVal ExeFile As String) AsBoolean
Dim stID As ICONDIR
Dim stIDE As ICONDIRENTRY
Dim stGID As GRPICONDIR
Dim hFile As Long
Dim pIcon() As Byte, pGrpIcon() As Byte
Dim nSize As Long, nGSize As Long
Dim dwReserved As Long
Dim hUpdate As Long
Dim ret As Long
hFile = CreateFile(IconFile, GENERIC_READ,0, ByVal 0&, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0)
If hFile = INVALID_HANDLE_VALUE Then Exit Function
ret = ReadFile(hFile, stID, Len(stID),dwReserved, ByVal 0&)
ret = ReadFile(hFile, stIDE, Len(stIDE),dwReserved, ByVal 0&)
nSize = stIDE.dwBytesInRes
ReDim pIcon(nSize - 1)
SetFilePointer hFile, stIDE.dwImageOffset,ByVal 0&, FILE_BEGIN
ret = ReadFile(hFile, pIcon(0), nSize,dwReserved, ByVal 0&) '仅读取了第一个图标
With stGID
.idType = 1
.idCount = stID.idCount '按照图标文件总图标数建立图标组
.idReserved = 0
CopyMemory stGID.idEntries, stIDE, 12
.idEntries.nID = 0 '将图标组第一项指向了0号图标
End With
nGSize = Len(stGID)
ReDim pGrpIcon(nGSize - 1)
CopyMemory pGrpIcon(0), stGID, nGSize
hUpdate = BeginUpdateResource(ExeFile,False)
ret = UpdateResource(hUpdate,RT_GROUP_ICON, 1, 0, pGrpIcon(0), nGSize)
ret = UpdateResource(hUpdate, RT_ICON, 1,0, pIcon(0), nSize) '将图标作为序号1放入
EndUpdateResource hUpdate, False
CloseHandlehFile
End Function
(代码已部分简化)
其实,这份源码已经做了正确的操作,却没有将其完善。
代码仅将图标文件中的首个图标替换至exe文件中,构造图标组数据,却是按照图标文件中总图标数,也没有正确指向图标。这就导致图标组中可能会出现空图标,并且win10下不能正确显示(XP等或许会对这错误的资源信息自动修正使其能够正常显示)。
因此,我们只需要将这些操作加以修改,完整地将图标数据导入即可。
以下给出源码:
PrivateDeclare Function CreateFile Lib "kernel32" Alias"CreateFileA" (ByVal lpFileName As String, ByVal dwDesiredAccess AsLong, ByVal dwShareMode As Long, lpSecurityAttributes As Any, ByValdwCreationDisposition As Long, ByVal dwFlagsAndAttributes As Long, ByValhTemplateFile As Long) As Long
PrivateDeclare Function ReadFile Lib "kernel32" (ByVal hFile As Long,lpBuffer As Any, ByVal nNumberOfBytesToRead As Long, lpNumberOfBytesRead AsLong, lpOverlapped As Any) As Long
PrivateDeclare Function SetFilePointer Lib "kernel32" (ByVal hFile As Long,ByVal lDistanceToMove As Long, lpDistanceToMoveHigh As Long, ByVal dwMoveMethodAs Long) As Long
PrivateDeclare Function BeginUpdateResource Lib "kernel32" Alias"BeginUpdateResourceA" (ByVal pFileName As String, ByValbDeleteExistingResources As Long) As Long
PrivateDeclare Function UpdateResource Lib "kernel32" Alias"UpdateResourceA" (ByVal hUpdate As Long, ByVal lpType As Long, ByVallpName As Long, ByVal wLanguage As Long, lpData As Any, ByVal cbData As Long)As Long
PrivateDeclare Function EndUpdateResource Lib "kernel32" Alias"EndUpdateResourceA" (ByVal hUpdate As Long, ByVal fDiscard As Long)As Long
PrivateDeclare Function CloseHandle Lib "kernel32" (ByVal hObject As Long)As Long
PrivateDeclare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory"(Destination As Any, Source As Any, ByVal Length As Long)
PrivateDeclare Function GetLastError Lib "kernel32" () As Long
PrivateConst INVALID_HANDLE_VALUE = -1
PrivateConst GENERIC_READ = &H80000000
PrivateConst FILE_ATTRIBUTE_NORMAL = &H80
PrivateConst FILE_BEGIN = 0
PrivateConst OPEN_EXISTING = 3
PrivateConst RT_ICON = 3&
PrivateConst RT_RCDATA = 10&
PrivateConst DIFFERENCE As Long = 11
PrivateConst RT_GROUP_ICON As Long = (RT_ICON + DIFFERENCE)
PrivateType ICONDIRENTRY
bWidth As Byte
bHeight As Byte
bColorCount As Byte
bReserved As Byte
wPlanes As Integer
wBitCount As Integer
dwBytesInRes As Long
dwImageOffset As Long
EndType
PrivateType ICONDIR
idReserved As Integer
idType As Integer
idCount As Integer
'idEntries As ICONDIRENTRY
EndType
PrivateType GRPICONDIRENTRY
bWidth As Byte
bHeight As Byte
bColorCount As Byte
bReserved As Byte
wPlanes As Integer
wBitCount As Integer
dwBytesInRes As Long
nID As Integer
EndType
PrivateType GRPICONDIR
idReserved As Integer
idType As Integer
idCount As Integer
'idEntries As GRPICONDIRENTRY
EndType
PrivateSub UpdataIcon(ByVal ExeFile As String, ByVal IconFile As String)
Dim pIcon() As Byte
Dim pGI() As Byte
Dim pGID() As Byte
Dim nSize As Long
Dim nIcon As Long
Dim hFile As Long
Dim dwReserved As Long
'打开图标文件
hFile = CreateFile(IconFile, GENERIC_READ,0, ByVal 0&, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0)
If hFile = INVALID_HANDLE_VALUE Then ExitSub
'打开EXE文件
Dim hUpdate As Long
hUpdate = BeginUpdateResource(ExeFile,False)
'读取图标文件头
Dim ID As ICONDIR
Call ReadFile(hFile, ID, Len(ID),dwReserved, ByVal 0&)
nIcon = ID.idCount
Dim i As Long
'读取各个图标信息(入口)
Dim IDE() As ICONDIRENTRY
ReDim IDE(nIcon - 1)
For i = 1 To nIcon
Call ReadFile(hFile, IDE(i - 1),Len(IDE(i - 1)), dwReserved, ByVal 0&)
Next
'读取图标文件内容并写入EXE
For i = 1 To nIcon
nSize = IDE(i - 1).dwBytesInRes
ReDim pIcon(nSize - 1)
SetFilePointer hFile, IDE(i -1).dwImageOffset, ByVal 0&, FILE_BEGIN
Call ReadFile(hFile, pIcon(0), nSize,dwReserved, ByVal 0&)
Call UpdateResource(hUpdate, RT_ICON,i, 0, pIcon(0), nSize)
Next
'构建EXE图标组文件头
Dim GID As GRPICONDIR
With GID
.idReserved = 0
.idType = 1
.idCount = ID.idCount
End With
Dim GIDE As GRPICONDIRENTRY
Dim sGID As Long: sGID = Len(GID)
Dim sGIDE As Long: sGIDE = Len(GIDE)
ReDim pGID(sGID + nIcon * sGIDE - 1)
'构建图标组文件头字节数据
CopyMemory pGID(0), GID, sGID
'构建图标组各图标数据与对应的字节数据
For i = 1 To nIcon
CopyMemory GIDE, IDE(i - 1), 12
GIDE.nID = i
CopyMemory pGID(sGID + (i - 1) *sGIDE), GIDE, sGIDE
Next
'写入图标组数据
Call UpdateResource(hUpdate, RT_GROUP_ICON,1, 0, pGID(0), sGID + nIcon * sGIDE)
'将所有数据跟提交至EXE文件
EndUpdateResource hUpdate, False
CloseHandle hFile
EndSub
