Golang修改已编译程序的方法探讨
Golang修改已编译程序的方法探讨 我构思了一款使用Go和Raylib开发的游戏,其核心特色是高度支持模组修改。除了模组部分,其他大部分内容我都已经规划好了。我希望游戏本身是编译好的,模组也是编译好的。理想情况下,我构建好游戏后,游戏能够自动检测特定文件夹中的二进制文件。
模组主要会是新游戏对象的映射(即结构体的映射,结构体包含数据成员和需要定义的函数成员)。那么,我该如何实现这一点,以便在添加模组(或未来任何人添加模组)时,无需重新编译我的项目呢?更具体地说,我想实现类似《我的世界》模组那样的机制。你有一个存放jar文件(在我的案例中是二进制文件)的文件夹,《我的世界》的模组加载器会检测并加载它们。
更多关于Golang修改已编译程序的方法探讨的实战教程也可以访问 https://www.itying.com/category-94-b0.html
我曾考虑过使用 JavaScript 或 Go 解释器作为备选方案,但前提是我的原始想法无法实现(或实现起来极其困难)。
更多关于Golang修改已编译程序的方法探讨的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
通过研究Yaegi,它看起来确实非常出色。我认为我会使用Yaegi。使用二进制文件会让我和模组制作者都感到更加困难。
嗨 @bosko2,
我立刻想到的有:
- 使用 Lua 或 JavaScript 这类嵌入式脚本语言。有一些现成的包可以使用(可以在 GitHub 上找找)。
- 使用像 Yaegi 这样的 Go 解释器。
要实现Go编译后程序的动态模组加载,核心是使用Go的插件系统(plugin package)。以下是具体实现方案:
1. 定义模组接口(主程序)
// mod_interface.go
package main
import "github.com/gen2brain/raylib-go/raylib"
type GameObject interface {
Update()
Draw()
GetPosition() rl.Vector2
SetPosition(rl.Vector2)
}
type ModInfo struct {
Name string
Version string
Author string
}
type ModInitializer interface {
Initialize() (ModInfo, []GameObject)
}
2. 主程序加载模组
// main.go
package main
import (
"fmt"
"plugin"
"path/filepath"
"os"
)
var activeMods []ModInfo
var gameObjects []GameObject
func loadMods(modDir string) {
files, err := os.ReadDir(modDir)
if err != nil {
rl.TraceLog(rl.LogWarning, "无法读取模组目录")
return
}
for _, file := range files {
if filepath.Ext(file.Name()) == ".so" {
modPath := filepath.Join(modDir, file.Name())
p, err := plugin.Open(modPath)
if err != nil {
rl.TraceLog(rl.LogError, fmt.Sprintf("加载模组失败: %v", err))
continue
}
initSym, err := p.Lookup("InitMod")
if err != nil {
rl.TraceLog(rl.LogError, "模组缺少InitMod函数")
continue
}
initFunc, ok := initSym.(func() (ModInfo, []GameObject))
if !ok {
rl.TraceLog(rl.LogError, "InitMod签名不匹配")
continue
}
modInfo, objects := initFunc()
activeMods = append(activeMods, modInfo)
gameObjects = append(gameObjects, objects...)
rl.TraceLog(rl.LogInfo, fmt.Sprintf("加载模组: %s v%s", modInfo.Name, modInfo.Version))
}
}
}
func main() {
rl.InitWindow(800, 600, "游戏模组示例")
defer rl.CloseWindow()
// 加载模组
loadMods("./mods")
for !rl.WindowShouldClose() {
// 更新所有游戏对象
for _, obj := range gameObjects {
obj.Update()
}
rl.BeginDrawing()
rl.ClearBackground(rl.RayWhite)
// 绘制所有游戏对象
for _, obj := range gameObjects {
obj.Draw()
}
rl.EndDrawing()
}
}
3. 模组实现示例
// mod_example.go
package main
import "github.com/gen2brain/raylib-go/raylib"
// 导出函数必须大写开头
func InitMod() (ModInfo, []GameObject) {
info := ModInfo{
Name: "怪物模组",
Version: "1.0.0",
Author: "模组作者",
}
// 创建自定义游戏对象
monster := &Monster{
position: rl.NewVector2(100, 100),
texture: rl.LoadTexture("monster.png"),
}
return info, []GameObject{monster}
}
// 自定义怪物类型
type Monster struct {
position rl.Vector2
texture rl.Texture2D
}
func (m *Monster) Update() {
// 怪物逻辑
m.position.X += 1
}
func (m *Monster) Draw() {
rl.DrawTexture(m.texture, int32(m.position.X), int32(m.position.Y), rl.White)
}
func (m *Monster) GetPosition() rl.Vector2 {
return m.position
}
func (m *Monster) SetPosition(pos rl.Vector2) {
m.position = pos
}
4. 编译模组
# 编译模组为共享库
go build -buildmode=plugin -o mods/monster_mod.so mod_example.go
# 编译主程序
go build -o game main.go mod_interface.go
5. 目录结构
game/
├── game # 主程序
├── mods/
│ ├── monster_mod.so # 编译好的模组
│ └── weapon_mod.so # 另一个模组
├── main.go
├── mod_interface.go
└── assets/ # 资源文件
6. 高级特性:热重载支持
// hot_reload.go
package main
import (
"time"
"os"
"io"
"crypto/md5"
)
type ModFile struct {
Path string
ModTime time.Time
Hash [16]byte
}
func watchMods(modDir string, reloadChan chan<- string) {
modFiles := make(map[string]ModFile)
for {
files, _ := os.ReadDir(modDir)
for _, file := range files {
if filepath.Ext(file.Name()) == ".so" {
path := filepath.Join(modDir, file.Name())
info, _ := os.Stat(path)
// 计算文件哈希
f, _ := os.Open(path)
data, _ := io.ReadAll(f)
f.Close()
hash := md5.Sum(data)
// 检查文件是否变化
if old, exists := modFiles[path]; exists {
if old.ModTime != info.ModTime() || old.Hash != hash {
reloadChan <- path
}
}
modFiles[path] = ModFile{
Path: path,
ModTime: info.ModTime(),
Hash: hash,
}
}
}
time.Sleep(2 * time.Second)
}
}
注意事项
- 平台限制:Go插件仅支持Linux、macOS和FreeBSD,不支持Windows
- 版本兼容:主程序和模组必须使用相同Go版本编译
- 依赖管理:模组和主程序需要共享相同的接口定义
- 内存安全:插件卸载可能导致内存泄漏,建议保持插件常驻
此方案通过Go的plugin包实现了编译后程序的动态模组加载,模组可以独立编译为.so文件,主程序运行时自动检测并加载。

