最近学习golang,调用了dll文件,打包后没有dll文件,没能单文件部署就想办法实现单文件部署

微信群里有人建议`go-bindata`实现打包静态资源

1. golang调用dll的实现

基础类`Dll`实现dll文件的加载及方法通用的调用

package dll

import (
	"errors"
	"fmt"
	"syscall"
	"unsafe"

	"github.com/hbh112233abc/seal/internal/pkg"
)

type Dll struct {
	DllPath string
	dll     *syscall.DLL
}

func NewDLL(dllPath string) (*Dll, error) {
	if !pkg.IsFile(dllPath) {
		panic(fmt.Errorf("dllPath not found: %v", dllPath))
	}

	d := &Dll{
		DllPath: dllPath,
	}
	d.init()
	return d, nil
}

func (d *Dll) init() {
	handle, err := syscall.LoadDLL(d.DllPath)
	if err != nil {
		panic(fmt.Errorf("dll load error: %v", err))
	}
	d.dll = handle
}

func (d *Dll) Call(procName string, params ...unsafe.Pointer) error {
	proc, err := d.dll.FindProc(procName)
	if err != nil {
		return err
	}
	data := make([]uintptr, len(params))
	for _, p := range params {
		data = append(data, uintptr(p))
	}
	ret, _, err := proc.Call(data...)
	if int(ret) != 0 {
		return errors.New(DllCallErr.Error(int(ret)))
	}
	return nil
}

`edccom.go`实现具体的dll调用方法封装

package dll

/*
#cgo windows CFLAGS: -DX86=1
#cgo !windows LDFLAGS: -lm
#include <stdio.h>
#include <stdlib.h>
*/
import "C"

import (
	"bytes"
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"unsafe"

	"github.com/hbh112233abc/seal/internal/pkg"
)

type SealParser struct {
	PageCount int
	SealInfo  []SealInfo
}

type SealInfo struct {
	HandlerName string
	IdCard      string
	SealCount   int8
	SealName    string
	SealTime    string
	Type        string
	NPage       int8    `json:"nPage"`
	PosLeft     float32 `json:"posLeft"`
	PosTop      float32 `json:"posTop"`
}

type EDCCom struct {
	*Dll
}

func NewEDCCom(dllPath string) (*EDCCom, error) {
	if !strings.HasSuffix(dllPath, "EDCCom.dll") {
		panic(fmt.Errorf("dllPath must `EDCCom.dll`: %v", dllPath))
	}
	d, err := NewDLL(dllPath)
	if err != nil {
		return nil, fmt.Errorf("DLL load failed: %v", err)
	}
	return &EDCCom{d}, nil
}

func (ec *EDCCom) GetSealInfo(file string) (SealParser, error) {
	exist := pkg.IsFile(file)
	if !exist {
		return SealParser{}, fmt.Errorf("file not found: %s", file)
	}
	ext := filepath.Ext(file)
	ext = strings.ToLower(ext)

	var procName string
	switch ext {
	case ".edc":
		procName = "Com_GetEdcSealInfo"
	case ".pdf":
		procName = "Com_GetPdfSealInfo"
	default:
		return SealParser{}, fmt.Errorf("unsupported ext: %s", ext)
	}

	return ec.sealInfo(procName, file)
}

//获取印章信息
func (ec *EDCCom) sealInfo(procName string, file string) (SealParser, error) {
	proc, err := ec.dll.FindProc(procName)
	if err != nil {
		fmt.Printf("proc %s not found,err:%v \n", procName, err)
		return SealParser{}, err
	}

	var sealParser SealParser
	//转换成GBK编码
	gbkDecode, _ := pkg.Utf8ToGbk([]byte(file))
	file = string(gbkDecode)

	input := C.CString(file)
	defer C.free(unsafe.Pointer(input))

	var result C.char
	var size int
	ret, _, err := proc.Call(
		uintptr(unsafe.Pointer(input)),
		uintptr(unsafe.Pointer(&result)),
		uintptr(unsafe.Pointer(&size)),
	)
	if ret != 0 {
		fmt.Println("Com_GetEdcSealInfo failed: ", DllCallErr.Error(int(ret)), err)
		return sealParser, err
	}

	resultString := C.GoStringN(&result, C.int(4096))
	// fmt.Println(resultString)
	resultDecode, err := pkg.GbkToUtf8([]byte(resultString))
	if err != nil {
		fmt.Println("GBK to utf8 failed: ", err)
		return sealParser, err
	}
	// fmt.Println(string(resultDecode))

	index := bytes.IndexByte(resultDecode, 0)
	resultDecodeTrim := resultDecode[:index]
	err = json.Unmarshal(resultDecodeTrim, &sealParser)
	if err != nil {
		fmt.Println("json.Unmarshal failed: ", err)
		return sealParser, err
	}

	return sealParser, nil
}

最终调用方法

func GetSealInfo() error {
	pwd := pkg.CurrentAbPath()
	dllPath := filepath.Join(pwd, "EDCCom.dll")
	fmt.Println("dll:", dllPath)
	dll, err := dll.NewEDCCom(dllPath)
	if err != nil {
		return err
	}

	edcFile := filepath.Join(pwd, "tests", "files", "signed.edc")
	res, err := dll.GetSealInfo(edcFile)
	if err != nil {
		return err
	}
	fmt.Printf("%s seal info: \n %#v \n", edcFile, res)

	return nil
}

2. 打包dll到exe的实现

我调用的dll如下:

 安装`go-bindata`

go install -a -v github.com/go-bindata/go-bindata/...@latest

在`main.go`添加

//go:generate go-bindata -fs -o=EDCCom.go -prefix=./ -nocompress -nomemcopy ./docs/dll

命令行执行

go generate

此时main.go目录下将生成`EDCCom.go`

main.go中增加`init`方法,释放dll文件

func init() {
	pwd := pkg.CurrentAbPath()
	dllFiles, err := AssetDir("docs/dll")
	if err != nil {
		log.Fatal(err)
		return
	}
	for _, dllFile := range dllFiles {
		// fmt.Println(dllFile)
		localDll := filepath.Join(pwd, dllFile)
		if !pkg.IsFile(localDll) {
			bytes, err := Asset(fmt.Sprintf("docs/dll/%s", dllFile))
			if err != nil {
				log.Fatal(err)
				return
			}
			ioutil.WriteFile(localDll, bytes, 0644)
		}
	}
}

此时运行`go run .`就会先检查目录下有没有dll文件,如果没有则先生成dll文件

打包后的exe文件也变大了,单文件部署就没问题了.