私たちが毎日コンピューターを使用するとき、よく使用されるプログラムを開く必要があることがよくあります。これらのプログラムはインターフェイス上に特定のアイコンを表示するため、すぐに識別して見つけることができます。ただし、場合によっては、個人の好みやテーマとの一貫性を高めるなど、これらのプログラム アイコンを変更したい場合があります。
この記事では、golang といくつかのシステム ライブラリを使用してプログラムのアイコンを変更する方法に焦点を当てます。デモ環境として Windows を使用します。
まず、実行する必要がある基本的な手順の概要を説明します。
次に、これらの手順を 1 つずつ完了する方法について説明します。
ステップ 1: リソース ファイルを開いてアイコン resource を見つけます
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) を反復処理し、アイコン リソースのインデックスを検索します。通常、インデックス付けはアイコン リソースのサイズと形式に依存します。リソース ファイル内で変更するアイコン リソースのインデックスは、独自の裁量で決定できます。
ステップ 2: 新しいアイコン リソースをリソース ファイルに追加する
新しいアイコン リソースをプログラム リソース ファイルに追加するには、まずそれを ICO ファイル形式で保存する必要があります。 golang の画像ライブラリを使用して 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 }
ステップ 3: プログラムの .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 中国語 Web サイトの他の関連記事を参照してください。