Go的标准包syscall提供了调用DLL所需的API。本例用到的API如下(仅列出重要部分):
// LoadDLL 和 MustLoadDLL 根据DLL名字 name 来加载DLL,返回值为 syscall.DLL 结构体指针。
// LoadDLL 返回 error 表示错误。MustLoadDLL 错误时调用 panic
func LoadDLL(name string) (*DLL, error)
func MustLoadDLL(name string) *DLL
// syscall.DLL 结构体表示 DLL 抽象
type DLL struct {
// ...
}
// FindProc 和 MustFindProc 根据函数名字 name 加载 DLL 中的函数
// 返回值为 syscall.Proc 结构体指针。
// FindProc 返回 eror 表示错误。MustFindProc 错误时调用 panic
func (d *DLL) FindProc(name string) (proc *Proc, err error)
func (d *DLL) MustFindProc(name string) *Proc
// syscall.Proc 结构体表示 DLL 中的函数
type Proc struct {
// ...
}
// Call 方法调用 syscall.Proc 表示的函数,参数为 uintptr 切片,返回值为 r1, r2 和 error
// C 函数只能有一个返回值,因此r2用不到。
// error表示错误,该错误永远不为nil,它是由GetLastError()构成的
func (p *Proc) Call(a ...uintptr) (r1, r2 uintptr, lastErr error)
// 将字符串转换为byte指针
func StringBytePtr(s string) *byte
下面是一个完整的例子:
首先实现一个DLL,定义了两个函数,一个从Go接收参数,一个返回参数给Go:
// test.h
#ifndef TEST_H
#define TEST_H
#ifdef TEST_DLL_EXPORT
#define TEST_API __declspec(dllexport)
#else
#define TEST_API __declspec(dllimport)
#endif
TEST_API void greet(char *name);
TEST_API char *name();
#endif
// test.c
#define TEST_DLL_EXPORT
#include "test.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void greet(char *name)
{
printf("Hello, %s!\n", name);
}
char *name()
{
char buf[] = "Gopher";
char *n = malloc(strlen(buf) + 1);
strcpy(n, buf);
n[strlen(buf)] = '\0';
return n;
}
下面是Go调用DLL的代码:
package main
import (
"fmt"
"syscall"
"unsafe"
)
func main() {
dll := syscall.MustLoadDLL("test.dll")
procGreet := dll.MustFindProc("greet")
procGreet.Call(uintptr(unsafe.Pointer(syscall.StringBytePtr("Cynhard"))))
procName := dll.MustFindProc("name")
r, _, _ := procName.Call()
// 获取C返回的指针。
// 注意C返回的r为char*,对应的Go类型为*byte
p := (*byte)(unsafe.Pointer(r))
// 定义一个[]byte切片,用来存储C返回的字符串
data := make([]byte, 0)
// 遍历C返回的char指针,直到 '\0' 为止
for *p != 0 {
data = append(data, *p) // 将得到的byte追加到末尾
r += unsafe.Sizeof(byte(0)) // 移动指针,指向下一个char
p = (*byte)(unsafe.Pointer(r)) // 获取指针的值,此时指针已经指向下一个char
}
name := string(data) // 将data转换为字符串
fmt.Printf("Hello, %s!\n", name)
}
注意从C中返回的指针是由malloc动态分配的,Go中不会对此指针进行引用计数,不会被垃圾回收,因此会造成内存泄漏。解决方法是在DLL中提供释放资源的接口,在Go中调用此接口。
注:本例中用到的低级编程知识请见:Go低级编程