Golang模块与资源相对路径的使用指南

Golang模块与资源相对路径的使用指南 你好 🙂

我的一个模块遇到了问题。基本上,它找不到我包含在模块中的 *.dll 文件。问题是,如果我在我的另一个模块中尝试使用这个模块,就会收到错误。

我的文件结构如下:

- yjuqsoft
-- lua53
--- go.mod
--- lua.go
--- lua53.dll
-- test
--- go.mod
--- main.go

以下是文件内容:

yjuqsoft/lua53/go.mod

module yjuqsoft/lua53

go 1.14

require golang.org/x/sys v0.0.0-20200331124033-c3d80250170d

yjuqsoft/lua53/lua.go

package lua53

import (
	"fmt"

	"golang.org/x/sys/windows"
)

var dll *windows.DLL

func init() {
	tmp, err := windows.LoadDLL("lua53.dll") // 有问题的行
	dll = tmp
	fmt.Println(err)
}

// DllRelease unloads DLL from memory
func DllRelease() {
	dll.Release()
}

yjuqsoft/test/go.mod

module yjuqsoft/test

go 1.14

require yjuqsoft/lua53 v0.0.0

replace yjuqsoft/lua53 => ../lua53

yjuqsoft/test/main.go

package main

import _ "yjuqsoft/lua53"

func main() {
	// lua53.DllRelease()
}

如果我运行这个,会得到以下输出:Failed to load lua53.dll: The specified module could not be found.

好吧,…… 找不到模块。如果我使用绝对路径,一切正常。但我不想这样做,因为我的模块 yjuqsoft/lua53 应该是独立的。那么,我该如何解决这个问题呢?

顺便说一下,所有文件都存储在 U 盘上。GOPATH 没有指向这个 U 盘。如果这有什么影响的话。


更多关于Golang模块与资源相对路径的使用指南的实战教程也可以访问 https://www.itying.com/category-94-b0.html

3 回复

@Yjuq 你是怎么运行这个的?我看到 DLL 文件是和你的 Go 源代码放在一起的,但如果你运行的是类似 go install 这样的命令,二进制文件很可能被放到了其他地方。安装时你可能需要手动移动/复制那个文件。

// 代码示例:这里可以放置相关的Go代码

更多关于Golang模块与资源相对路径的使用指南的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


我只是进入测试目录并使用 go build 并运行它。我认为问题在于工作目录根本就是错误的。我刚刚找到了一个对我有效的解决方案。基本上就是摆脱相对路径,并根据 lua.go 文件的存储位置生成一个绝对路径。

package lua53

import (
	"golang.org/x/sys/windows"
	"path/filepath"
	"runtime"
)

var dll *windows.DLL

func init() {
	_, path, _, _ := runtime.Caller(0)
	path = filepath.Join(filepath.Dir(path), "lua53.dll")
	dll = windows.MustLoadDLL(path)
}

// DllRelease unloads DLL from memory
func DllRelease() {
	dll.Release()
}

在Go模块中处理DLL等资源文件时,需要明确资源文件的嵌入和加载方式。windows.LoadDLL()默认在系统路径和当前工作目录中查找DLL,但在模块依赖场景下,这通常不是资源文件的实际位置。

以下是几种解决方案:

方案1:使用//go:embed嵌入资源(Go 1.16+)

// lua.go
package lua53

import (
    _ "embed"
    "fmt"
    "io/ioutil"
    "os"
    "path/filepath"
    
    "golang.org/x/sys/windows"
)

//go:embed lua53.dll
var dllBytes []byte

var dll *windows.DLL

func init() {
    // 创建临时文件
    tmpFile, err := ioutil.TempFile("", "lua53_*.dll")
    if err != nil {
        panic(err)
    }
    defer tmpFile.Close()
    
    // 写入DLL数据
    if _, err := tmpFile.Write(dllBytes); err != nil {
        panic(err)
    }
    tmpFile.Close()
    
    // 从临时文件加载DLL
    dll, err = windows.LoadDLL(tmpFile.Name())
    if err != nil {
        panic(err)
    }
    
    // 清理临时文件(可选,DLL加载后可以删除)
    os.Remove(tmpFile.Name())
    
    fmt.Println("DLL loaded successfully")
}

方案2:使用embed.FS(Go 1.16+)

// lua.go
package lua53

import (
    "embed"
    "fmt"
    "io/ioutil"
    "os"
    "path/filepath"
    
    "golang.org/x/sys/windows"
)

//go:embed lua53.dll
var dllFS embed.FS

var dll *windows.DLL

func init() {
    // 从嵌入的文件系统读取
    dllData, err := dllFS.ReadFile("lua53.dll")
    if err != nil {
        panic(err)
    }
    
    // 创建临时文件
    tmpFile, err := ioutil.TempFile("", "lua53_*.dll")
    if err != nil {
        panic(err)
    }
    
    // 写入并加载
    if _, err := tmpFile.Write(dllData); err != nil {
        tmpFile.Close()
        panic(err)
    }
    tmpFile.Close()
    
    dll, err = windows.LoadDLL(tmpFile.Name())
    if err != nil {
        panic(err)
    }
    
    // 可选:删除临时文件
    os.Remove(tmpFile.Name())
}

方案3:使用相对路径查找(适用于旧版本Go)

// lua.go
package lua53

import (
    "fmt"
    "os"
    "path"
    "path/filepath"
    "runtime"
    
    "golang.org/x/sys/windows"
)

var dll *windows.DLL

func init() {
    // 获取当前源文件所在目录
    _, filename, _, _ := runtime.Caller(0)
    dir := filepath.Dir(filename)
    
    // 构建DLL完整路径
    dllPath := filepath.Join(dir, "lua53.dll")
    
    // 检查文件是否存在
    if _, err := os.Stat(dllPath); os.IsNotExist(err) {
        // 尝试从模块根目录查找
        dir = findModuleRoot(dir)
        dllPath = filepath.Join(dir, "lua53.dll")
    }
    
    dll, err = windows.LoadDLL(dllPath)
    fmt.Println("Loading DLL from:", dllPath)
    fmt.Println(err)
}

func findModuleRoot(startDir string) string {
    dir := startDir
    for {
        if _, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil {
            return dir
        }
        parent := filepath.Dir(dir)
        if parent == dir {
            break
        }
        dir = parent
    }
    return startDir
}

方案4:使用资源文件系统包

// 使用第三方包如 github.com/mjibson/esc 或 packr
// 首先安装:go get github.com/mjibson/esc

//go:generate esc -o lua53_resources.go -pkg lua53 -private lua53.dll

// lua.go
package lua53

import (
    "fmt"
    "io/ioutil"
    "os"
    
    "golang.org/x/sys/windows"
)

var dll *windows.DLL

func init() {
    // 从生成的文件系统读取
    dllData := _escFSMustByte(false, "/lua53.dll")
    
    tmpFile, err := ioutil.TempFile("", "lua53_*.dll")
    if err != nil {
        panic(err)
    }
    
    if _, err := tmpFile.Write(dllData); err != nil {
        tmpFile.Close()
        panic(err)
    }
    tmpFile.Close()
    
    dll, err = windows.LoadDLL(tmpFile.Name())
    if err != nil {
        panic(err)
    }
    
    os.Remove(tmpFile.Name())
}

推荐方案

对于新项目,建议使用方案1或方案2的//go:embed特性,这是Go 1.16+的标准做法。如果必须支持旧版本Go,方案3提供了基于相对路径的查找方法。

关键点是:资源文件必须被正确嵌入到模块中,并在运行时提取到可访问的位置(如临时文件)进行加载。直接使用windows.LoadDLL("lua53.dll")无法找到模块内的资源文件,因为加载路径取决于调用模块的工作目录。

回到顶部