在我们日常使用电脑时,我们常常需要打开一些常用的程序。这些程序会在我们的界面上展示出一个特定的图标,以便我们快速地识别和找到它们。但是在某些情况下,我们可能会希望更改这些程序图标,例如让它们更符合自己的个人喜好或主题。
在本篇文章中,我们将着重介绍如何使用golang和一些系统库来更改程序的图标。我们将使用Windows作为我们的演示环境。
首先,让我们概述一下我们需要进行的基本步骤:
接下来,我们将逐一讨论如何完成这些步骤。
第一步:打开资源文件并找到图标资源
立即学习“go语言免费学习笔记(深入)”;
在golang中,我们可以使用系统库“syscall”中的函数来打开和读取文件。为此,我们需要定义一些必要的变量:
package main
import (
"os"
"syscall"
"unsafe"
)
var (
kernel32DLL = syscall.MustLoadDLL("kernel32.dll")
BeginUpdateResourceProc = kernel32DLL.MustFindProc("BeginUpdateResourceW")
UpdateResourceProc = kernel32DLL.MustFindProc("UpdateResourceW")
EndUpdateResourceProc = kernel32DLL.MustFindProc("EndUpdateResourceW")
)我们这里使用了Windows API中的几个函数,分别是“BeginUpdateResourceW”、“UpdateResourceW”和“EndUpdateResourceW”。这些函数可以帮助我们操作程序资源文件中的资源。
接下来,我们需要打开要更改的程序的资源文件(可以是.exe或.dll文件),并找到其图标资源。这里我们使用了一个名为“findIconIndex”的函数来遍历程序资源文件,找到其图标资源所在的索引号。
func findIconIndex(exePath string) (int, error) {
exeFile, err := os.OpenFile(exePath, os.O_RDWR, 0666)
defer exeFile.Close()
if err != nil {
return 0, err
}
exeStat, err := exeFile.Stat()
if err != nil {
return 0, err
}
exeSize := exeStat.Size()
// DOS header
dosHeader := new(image.DosHeader)
err = binary.Read(exeFile, binary.LittleEndian, dosHeader)
if err != nil {
return 0, err
}
exeFile.Seek(int64(dosHeader.Lfanew), 0)
// File header and optional header
fileHeader := new(image.FileHeader)
err = binary.Read(exeFile, binary.LittleEndian, fileHeader)
if err != nil {
return 0, err
}
extHeader := make([]byte, fileHeader.SizeOfOptionalHeader-2)
exeFile.Read(extHeader)
// Section headers
sections := make([]image.SectionHeader, fileHeader.NumberOfSections)
err = binary.Read(exeFile, binary.LittleEndian, sections)
if err != nil {
return 0, err
}
// Find icon resource
for _, section := range sections {
if section.Name == ".rsrc" {
exeFile.Seek(int64(section.Offset), 0)
resourceHeader := new(resourceHeader)
err = binary.Read(exeFile, binary.LittleEndian, resourceHeader)
if err != nil {
return 0, err
}
stack := []resourceDirectoryEntry{resourceDirectoryEntry{uint32(resourceHeader.RootID), int64(resourceHeader.OffsetToDirectory)}}
for len(stack) > 0 {
currentEntry := stack[len(stack)-1]
stack = stack[:len(stack)-1]
exeFile.Seek(currentEntry.offset, 0)
directoryHeader := new(resourceDirectoryHeader)
err = binary.Read(exeFile, binary.LittleEndian, directoryHeader)
if err != nil {
return 0, err
}
entries := make([]resourceDirectoryEntry, directoryHeader.NumNamedEntries+directoryHeader.NumIDEntries)
for i := range entries {
err = binary.Read(exeFile, binary.LittleEndian, &entries[i])
if err != nil {
return 0, err
}
if entries[i].nameIsString {
nameBytes := make([]byte, entries[i].nameOffset&0x7FFFFFFF)
exeFile.Read(nameBytes)
entries[i].name = syscall.UTF16ToString(nameBytes)
}
}
for _, entry := range entries {
if entry.ID&^0xFFFF == rtIcon {
return int(entry.ID & 0xFFFF), nil
} else if entry.name == "ICON" {
stack = append(stack, resourceDirectoryEntry{entry.ID, int64(entry.offset)})
} else if entry.name == "#0" && entry.ID&^0xFFFF == rtGroupIcon {
groupIconDirHeader := new(resourceGroupIconDirectoryHeader)
exeFile.Seek(int64(entry.offset), 0)
err = binary.Read(exeFile, binary.LittleEndian, groupIconDirHeader)
if err != nil {
return 0, err
}
var largestIcon resourceGroupIconDirectoryEntry
for i := 0; i < int(groupIconDirHeader.Count); i++ {
groupIconDirEntry := new(resourceGroupIconDirectoryEntry)
err = binary.Read(exeFile, binary.LittleEndian, groupIconDirEntry)
if err != nil {
return 0, err
}
if groupIconDirEntry.Width > largestIcon.Width || groupIconDirEntry.Height > largestIcon.Height {
largestIcon = *groupIconDirEntry
}
}
return int(largestIcon.ID), nil
} else if entry.name == "ICONGROUP" {
stack = append(stack, resourceDirectoryEntry{entry.ID, int64(entry.offset)})
} else if entry.name == "MAINICON" {
stack = append(stack, resourceDirectoryEntry{entry.ID, int64(entry.offset)})
} else {
stack = append(stack, resourceDirectoryEntry{entry.ID, int64(entry.offset)})
}
}
}
return 0, fmt.Errorf("Icon not found")
}
}
return 0, fmt.Errorf("Resource not found")
}此函数遍历程序资源文件中的每个节(.rsrc)并找到图标资源的索引。通常情况下,索引取决于图标资源的大小和格式。我们可以自行决定要更改的图标资源在资源文件中的索引。
第二步:将新的图标资源添加到资源文件中
要将新的图标资源添加到程序资源文件中,我们需要先将其保存为ICO文件格式。我们可以使用golang中的image库来创建ICO文件。
package main
import (
"image"
"image/draw"
"image/png"
"os"
)
func writeIcoFile(icon image.Image, filename string) error {
file, err := os.Create(filename)
if err != nil {
return err
}
defer file.Close()
// Create icon file header
iconSize := icon.Bounds().Size()
fileHeader := new(resourceIconFileHeader)
fileHeader.Reserved = 0
fileHeader.Type = 1
fileHeader.Count = 1
// Create icon directory entry
dirEntry := new(resourceIconDirectoryEntry)
dirEntry.Width = uint8(iconSize.X)
dirEntry.Height = uint8(iconSize.Y)
dirEntry.Colors = 0
dirEntry.Reserved = 0
dirEntry.Plane = 1
dirEntry.BitCount = 32
dirEntry.SizeInBytes = uint32(40 + 4*iconSize.X*iconSize.Y)
dirEntry.Offset = 22
// Create bitmap info header and color mask for bitmap graphics
colorMask := [12]byte{0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0xFF, 0x00, 0x00}
infoHeader := new(bitmapInfoHeader)
infoHeader.Size = 40
infoHeader.Width = int32(iconSize.X)
infoHeader.Height = int32(2 * iconSize.Y)
infoHeader.Planes = 1
infoHeader.BitCount = 32
infoHeader.Compression = 0
infoHeader.SizeImage = uint32(4 * iconSize.X * iconSize.Y)
infoHeader.XPelsPerMeter = 0
infoHeader.YPelsPerMeter = 0
infoHeader.ClrUsed = 0
infoHeader.ClrImportant = 0
// Write icon file header, directory entry, bitmap info header and color mask
binary.Write(file, binary.LittleEndian, fileHeader)
binary.Write(file, binary.LittleEndian, dirEntry)
binary.Write(file, binary.LittleEndian, infoHeader)
binary.Write(file, binary.LittleEndian, colorMask)
// Write bitmap graphics
rgba := image.NewRGBA(image.Rect(0, 0, iconSize.X, 2*iconSize.Y))
draw.Draw(rgba, rgba.Bounds(), image.Black, image.ZP, draw.Src)
draw.Draw(rgba, image.Rect(0, 0, iconSize.X, iconSize.Y), icon, image.ZP, draw.Over)
draw.Draw(rgba, image.Rect(0, iconSize.Y, iconSize.X, 2*iconSize.Y), image.Transparent, image.ZP, draw.Src)
err = png.Encode(file, rgba)
if err != nil {
return err
}
return nil
}这个函数创建了一个ICO文件头,并将图标资源附加到其中。ICO文件头包含有关ICO文件中的图标资源的必要信息。
接下来,我们将它们写入资源文件中。我们需要使用Windows API中的“BeginUpdateResource”、“UpdateResource”和“EndUpdateResource”函数来执行此操作。
func updateIcon(exePath, icoPath string, iconIndex int) error {
exeFile, err := os.OpenFile(exePath, os.O_RDWR, 0666)
defer exeFile.Close()
if err != nil {
return err
}
icoFile, err := os.Open(icoPath)
defer icoFile.Close()
if err != nil {
return err
}
// Read ICO file and prepare icon directory entry
icoData, err := ioutil.ReadAll(icoFile)
if err != nil {
return err
}
dirEntry := new(resourceIconDirectoryEntry)
dirEntry.Width = 0
dirEntry.Height = 0
dirEntry.Colors = 0
dirEntry.Reserved = 0
dirEntry.Plane = 1
dirEntry.BitCount = 0
dirEntry.SizeInBytes = uint32(len(icoData))
dirEntry.Offset = 22
// Find update handle
exeHandle, err := syscall.CreateFile(syscall.StringToUTF16Ptr(exePath), syscall.GENERIC_READ|syscall.GENERIC_WRITE, 0, nil, syscall.OPEN_EXISTING, syscall.FILE_ATTRIBUTE_NORMAL, 0)
if err != nil {
return err
}
defer syscall.CloseHandle(exeHandle)
updateHandle, _, err := BeginUpdateResourceProc.Call(uintptr(exeHandle), 0)
defer syscall.CloseHandle(syscall.Handle(updateHandle))
if updateHandle == 0 {
return fmt.Errorf("BeginUpdateResourceW failed")
}
// Write resource to update handle
success, _, err := UpdateResourceProc.Call(uintptr(updateHandle), uintptr(rtIcon), uintptr(iconIndex), 0, uintptr(unsafe.Pointer(&icoData[0])), uintptr(len(icoData)))
if success == 0 {
return fmt.Errorf("UpdateResourceW failed")
}
// Write updated icon directory entry
success, _, err = UpdateResourceProc.Call(uintptr(updateHandle), uintptr(rtGroupIcon), uintptr(MAKEINTRESOURCE(iconIndex)), 0, uintptr(unsafe.Pointer(dirEntry)), uintptr(unsafe.Sizeof(*dirEntry)))
if success == 0 {
return fmt.Errorf("UpdateResourceW failed")
}
// Commit update handle
success, _, err = EndUpdateResourceProc.Call(updateHandle, 0)
if success == 0 {
return fmt.Errorf("EndUpdateResourceW failed")
}
return nil
}第三步:更改程序的.manifest文件
我们需要更改程序的.manifest文件,以便它可以访问新的图标资源。为此,我们需要在.manifest文件中添加以下内容:
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> <assemblyIdentity type="win32" name="MyApplication" version="1.0.0.0" processorArchitecture="x86" /> <icon type="group">MyIconResourceIndex</icon> </assembly>
这将为程序指定一个图标资源索引号,以便它可以使用新的图标资源。我们可以使用golang中的os库来更改.manifest文件。
func updateManifest(manifestPath string, iconIndex int) error {
manifestData, err := ioutil.ReadFile(manifestPath)
if err != nil {
return err
}
updatedManifest := strings.Replace(string(manifestData), "</assembly>", " <icon type="group">"+strconv.Itoa(iconIndex)+"</icon>
</assembly>", 1)
err = ioutil.WriteFile(manifestPath, []byte(updatedManifest), 0666)
if err != nil {
return err
}
return nil
}现在,我们已经知道了如何使用golang和系统库来更改程序的图标。将这些步骤组合在一起,我们构建了一个完整的功能。下面是一个示例代码:
package main
import (
"encoding/binary"
"encoding/hex"
"fmt"
"image"
"image/png"
"io/ioutil"
"os"
"strconv"
"strings"
"syscall"
"unsafe"
)
const (
rtIcon = 14
rtGroupIcon = rtIcon + 11
LOAD_LIBRARY_AS_IMAGE_RESOURCE = 0x00000020
)
// Resource header
type resourceHeader struct {
RootID uint16
RootType uint16
RootName [16]uint16
RootDataSize uint32
RootDataVer uint32
RootDate uint32
RootRMLow uint16
RootRMHigh uint16
RootLangID uint16
RootDataVerOS uint32
}
// Resource directory header
type resourceDirectoryHeader struct {
Characteristics uint32
TimeDateStamp uint32
VersionMajor uint16
VersionMinor uint16
NumNamedEntries uint16
NumIDEntries uint16
}
// Resource directory entry
type resourceDirectoryEntry struct {
nameIsString bool
ID uint32
offset uint32
nameOffset uint32
name string
}
// Resource icon file header
type resourceIconFileHeader struct {
Reserved uint16
Type uint16
Count uint16
}
// Resource icon directory entry
type resourceIconDirectoryEntry struct {
Width uint8
Height uint8
Colors uint8
Reserved uint8
Plane uint16
BitCount uint16
SizeInBytes uint32
Offset uint32
}
// Resource group icon directory header
type resourceGroupIconDirectoryHeader struct {
Width uint16
Height uint16
ColorCount uint16
Reserved uint16
Planes uint16
BitCount uint16
Count uint32
}
// Resource group icon directory entry
type resourceGroupIconDirectoryEntry struct {
Width uint8
Height uint8
ColorCount uint8
Reserved uint8
Planes uint16
BitCount uint16
BytesInRes uint32
ID uint16
}
// Bitmap header
type bitmapInfoHeader struct {
Size uint32
Width int32
Height int32
Planes uint16
BitCount uint16
Compression uint32
SizeImage uint32
XPelsPerMeter int32
YPelsPerMeter int32
ClrUsed uint32
ClrImportant uint32
}
var (
kernel32DLL = syscall.MustLoadDLL("kernel32.dll")
user32DLL = syscall.MustLoadDLL("user32.dll")
autoDetectEncodingProc = kernel32DLL.MustFindProc("AutoDetectEncoding")
BeginUpdateResourceProc = kernel32DLL.MustFindProc("BeginUpdateResourceW")
LoadImageProc = user32DLL.MustFindProc("LoadImageW")
ResourceNotFound = fmt.Errorf("Resource not found")
NoIconFound = fmt.Errorf("Icon not found")
)
func main() {
exePath := "path/to/program.exe"
icoPath := "path/to/newicon.png"
manifestPath := "path/to/program.exe.manifest"
iconIndex, err := findIconIndex(exePath)以上就是golang程序图标修改的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号