Golang学习笔记-golang调用c实现的dll接口细节

目的

本篇文章主要介绍golang在调用c实现的dll时,具体的一些方式。比如值传递、参数传递、指针等等的一些使用。

一、dll的代码

c实现的dll代码:
hello.h

#ifndef _HELLO_H_
#define _HELLO_H_
#include <stdio.h>

#define HELLO_EXPORTS
#ifdef HELLO_EXPORTS
#define EXPORTS_API extern "C" __declspec(dllexport)
#else
#define EXPORTS_API extern "C" __declspec(dllimport)
#endif // HELLO_EXPORTS

EXPORTS_API int add(int left, int right);
EXPORTS_API void show(char* ptr, int nLen);
EXPORTS_API char* change(char* ptr, int nLen);
EXPORTS_API void callByReference(int& nLen);
EXPORTS_API void callByPtr(int* nLen);
#endif //_HELLO_H_

hello.cpp

#include "hello.h"
int add(int left, int right)
{
	return left + right;
}
void show(char* ptr,int nLen)
{
	printf("> -------------------\n> Pass `pointer` and `int` data:\n");
	printf(">> %s, %d\n", ptr,nLen);
}
char* change(char* ptr, int nLen)
{
	if (!ptr || 0 > nLen)
		return nullptr;
	printf("> -------------------\n> Pass `pointer` and `int` data:\n");
	printf("> src strings: %s\n",ptr);
	ptr[1] = 'a';
	printf("> modify strings: %s\n", ptr);
	return ptr;
}
void callByReference(int& nLen)
{
	nLen = 100;
}
void callByPtr(int* nLen)
{
	*nLen = 1000;
}

生成一个名为c2plusdll.dll的动态库文件,位于我的路径:E:\\Code\\vs2015_project\\demo\\x64\\Release\\c2plusdll.dll

二、golang的调用代码

编写调用dll的代码:

package main

import (
	"fmt"
	"strconv"
	"syscall"
	"unsafe"
)

func main() {
	call()
}

func IntPtr(n int) uintptr {
	return uintptr(n)
}
func Int2IntPtr(n int) uintptr {
	return uintptr(unsafe.Pointer(&n))
}
func IntPtr2Ptr(n *int) uintptr {
	return uintptr(unsafe.Pointer(n))
}
func BytePtr(s []byte) uintptr {
	return uintptr(unsafe.Pointer(&s[0]))
}

func call() error {
	left := 4
	right := 5
	err := Add(left, right)
	if err != nil {
		fmt.Println("Error:", err)
		return err
	}

	str := []byte("this is a test msg!")
	err = Show(str, len(str))
	if err != nil {
		fmt.Println("Error:", err)
		return err
	}

	err = Change_bytes(str, len(str))
	if err != nil {
		fmt.Println("Error:", err)
		return err
	}

	n := 0
	err = Call_PassByValue(n)
	if err != nil {
		fmt.Println("Error:", err)
		return err
	}
	fmt.Println("> Call_PassByValue(n)的结果为 n=" + strconv.Itoa(n) + ",期待输出 100")

	n = 0
	err = Call_PassByPtr(&n)
	if err != nil {
		fmt.Println("Error:", err)
		return err
	}
	fmt.Println("> Call_PassByPtr(&n)的结果为 n=" + strconv.Itoa(n) + ",期待输出 1000")

	return nil
}

func Add(left, right int) error {
	dllPath := "E:\\Code\\vs2015_project\\demo\\x64\\Release\\c2plusdll.dll"
	handle, err := syscall.LoadLibrary(dllPath)
	if err != nil {
		fmt.Printf("Error: %s\n", err)
		return err
	}
	defer syscall.FreeLibrary(handle)

	add, err := syscall.GetProcAddress(handle, "add")
	if err != nil {
		fmt.Printf("Error: %s\n", err)
		return err
	}

	ret, _, _ := syscall.Syscall(add, 2, IntPtr(left), IntPtr(right), 0)
	if err != nil {
		fmt.Printf("Error: %s\n", err)
	}
	fmt.Println("> Add(4,5)的结果为:", ret)
	return nil
}

func Show(str []byte, l int) error {
	dllPath := "E:\\Code\\vs2015_project\\demo\\x64\\Release\\c2plusdll.dll"

	handle := syscall.NewLazyDLL(dllPath)
	show := handle.NewProc("show")

	show.Call(BytePtr(str), IntPtr(l))
	return nil
}

func Change_bytes(str []byte, l int) error {
	dllPath := "E:\\Code\\vs2015_project\\demo\\x64\\Release\\c2plusdll.dll"
	handle := syscall.NewLazyDLL(dllPath)
	change := handle.NewProc("change")
	change.Call(BytePtr(str), IntPtr(l))
	return nil
}

func Call_PassByValue(n int) error {
	dllPath := "E:\\Code\\vs2015_project\\demo\\x64\\Release\\c2plusdll.dll"
	handle := syscall.NewLazyDLL(dllPath)
	test := handle.NewProc("callByReference")
	test.Call(Int2IntPtr(n))
	return nil
}

func Call_PassByPtr(n *int) error {
	dllPath := "E:\\Code\\vs2015_project\\demo\\x64\\Release\\c2plusdll.dll"
	handle := syscall.NewLazyDLL(dllPath)
	test := handle.NewProc("callByPtr")
	test.Call(IntPtr2Ptr(n))
	return nil
}

三、结果分析

运行的结果:
Golang学习笔记-golang调用c实现的dll接口细节

从上图中可以看到:
1、当值传递时并没有修改传入的值;只有指针传递时修改了传入的值。
2、指针传递时golang侧使用的是byte切片

四、结论

1、需要修改参数的值时,必须使用指针类型

func Call_PassByPtr(n *int) error{
return nil
}

2、需要修改指针的内容时,必须使用指针类型

func Change_bytes(str []byte, l int) error {
	return nil
}

3、golang传递指针给c接口函数时,必须使用[] byte的类型,不能使用string类型

func Show(str []byte, l int) error {
	dllPath := "E:\\Code\\vs2015_project\\demo\\x64\\Release\\c2plusdll.dll"

	handle := syscall.NewLazyDLL(dllPath)
	show := handle.NewProc("show")

	show.Call(BytePtr(str), IntPtr(l))
	return nil
}

4、golang调用c接口时有三种方式:
(1)使用syscall.LoadLibrary(dllPath)函数加载dll,syscall.Syscall(...)函数调用具体的函数接口,如下:

func Add(left, right int) error {
	dllPath := "E:\\Code\\vs2015_project\\demo\\x64\\Release\\c2plusdll.dll"
	handle, err := syscall.LoadLibrary(dllPath)
	if err != nil {
		fmt.Printf("Error: %s\n", err)
		return err
	}
	defer syscall.FreeLibrary(handle)

	add, err := syscall.GetProcAddress(handle, "add")
	if err != nil {
		fmt.Printf("Error: %s\n", err)
		return err
	}
	ret, _, _ := syscall.Syscall(add, 2, IntPtr(left), IntPtr(right), 0)
	if err != nil {
		fmt.Printf("Error: %s\n", err)
	}
	fmt.Println("> Add(4,5)的结果为:", ret)
	return nil
}

(2)使用syscall.NewLazyDLL()加载dll,使用接口函数.Call(uintptr类型的参数)来调用函数

func Call_PassByPtr(n *int) error {
	dllPath := "E:\\Code\\vs2015_project\\demo\\x64\\Release\\c2plusdll.dll"
	handle := syscall.NewLazyDLL(dllPath)
	test := handle.NewProc("callByPtr")
	test.Call(IntPtr2Ptr(n))
	return nil
}

(3)使用syscall.MustLoadDLL(dllPath)加载dll,函数接口函数.Call(参数列表)调用

func Call_PassByValue(n int) error {
	dllPath := "E:\\Code\\vs2015_project\\demo\\x64\\Release\\c2plusdll.dll"
	handle := syscall.MustLoadDLL(dllPath)
	callByReference := handle.MustFindProc("callByReference")
	ret, _, err := callByReference.Call(IntPtr(n))
	if err != nil {
		fmt.Println("DllTestDef的运算结果为:", ret)
	}
	return nil
}