Golang调用Dll案例

Golang调用Dll案例

前言

在家办公已经两个多星期了,目前最大的困难就是网络很差。独自一个人用golang开发调用dll的驱动程序。本来就是半桶水的我,还在为等待打开一个页面而磨平了耐心。本想依葫芦画瓢把这个驱动做了。可网上找到的案例都是一些简单的调用dll。对于各种传参、获取返回值和一些常见错误的文章太少(可能因为网络不好一些优质文章还没有点开就被关掉了)。今天ITDragon就做一个简单的葫,以广播驱动作为案例。

1.The specified module could not be found.

2.%1 is not a valid Win32 application.

3.The operation completed successfully.

4.error: unknown type name ‘HWND‘、‘DWORD‘.

5.获取dll返回的结构体

6.dll传参unsigned char* argName, struct _PlayParam* pParam

调用LCAudioThrDll 案例

ITDragon龙 先画一个葫芦。这个dll是在做广播驱动时用到,列举了其中几个有代表性的方法介绍。

package main

/*
#include <stdlib.h>
typedef struct _PlayParam
{
    long hWnd;                      //主窗口的句柄,如果不为0,则线程有事件会向该窗口发送消息
    int Priority;                   //优先级
    int MultiGroup;                 //多播组号
    int CastMode;                   //传输模式,单播,多播,广播
    long IP;                        //ip,如果是广播和多播,此参数是源网卡的IP,如果此地址为0,则由系统决定使用哪个网卡,如果是单播,这是个目标设备的ip地址。
    int   Volume;                   //播放音量取值0~100
    int   Tone;                     //音调
    int   Treble;                   //高音频率
    int   Bass;                     //低音频率
    int   Treble_En;                //高音增益
    int   Bass_En;                  //低音增益
    unsigned short   SourceType;    //输入源,0为文件,1为声卡
    unsigned short   OptionByte;    //选项字,默认为0;bit0=1 禁止重采样,bit1=1,启动监听,bit2=1,禁用解码功能(仅播放符合要求的音频文件)
    int DeviceID;                   //音频输入ID号 1~N
    int MaxBitrate;                 //允许最大的比特率组合,如果源文件高于此比特率,将被重压缩至此比特率。
    unsigned int Option[15];        //选项
    int nChannels;                  //采样的通道 1~2 CodecType
    int nSamplesPerSec;             //采样频率 8K,11.025K,22.05K,44.1K
    int AudioBufferLength;          //Audio数据的长度
    unsigned char* AudioBuf;        //Audio数据的指针
    unsigned int PrivateData[128];  //私有信息,lc_init初始化后,用户不能修改里面的内容。
}PlayParam;
*/
import "C"
import (
    "fmt"
    "os"
    "strconv"
    "strings"
    "syscall"
    "unicode"
    "unsafe"
)

/*
struct _PlayParam* __stdcall lc_play_getmem (void);
int __stdcall lc_init(unsigned char* pFileName, struct _PlayParam* pParam);
int __stdcall lc_play(struct _PlayParam* pParam);
int __stdcall lc_set_volume(struct _PlayParam* pParam, char volume);
int __stdcall lc_addip (struct _PlayParam* pParam,DWORD ip);
*/

var (
    lcAudioSdk, _               = syscall.LoadDLL("LCAudioThrDll.dll")
    lcAudioSdkPlayGetMemFunc, _ = lcAudioSdk.FindProc("lc_play_getmem")
    lcAudioSdkInitFunc, _       = lcAudioSdk.FindProc("lc_init")
    lcAudioSdkPlayFunc, _       = lcAudioSdk.FindProc("lc_play")
    lcAudioSdkSetVolumeFunc, _  = lcAudioSdk.FindProc("lc_set_volume")
    lcAudioSdkAddIPFunc, _      = lcAudioSdk.FindProc("lc_addip")
)

func main() {
    filePath := `D:\upload\attachment\20200217115847582_-581698856.mp3`
    if IsIllegalFile(filePath) {
        return
    }

    audioSource := C.CString(filePath)
    defer C.free(unsafe.Pointer(audioSource))
    var playParam *C.PlayParam
    /**
    step1   申请PlayParam内存
            1. 无参
            2. 获取并转换dll 返回结构体指针
    */
    playParamMem, _, _ := lcAudioSdkPlayGetMemFunc.Call()
    playParam = (*C.PlayParam)(unsafe.Pointer(playParamMem))
    playParam.Volume = 80
    playParam.SourceType = 0
    playParam.CastMode = 0
    playParam.IP = C.long(ipAddrToInt("127.0.0.1"))

    /**
    step2   初始化客户端
            1. 入参是unsigned char* 和 struct _PlayParam*
            2. 获取并转换dll 返回int类型
    */
    initResult, _, _ := lcAudioSdkInitFunc.Call(uintptr(unsafe.Pointer(audioSource)), uintptr(unsafe.Pointer(playParam)))
    fmt.Println("lcaudio init result : ", int32(initResult))

    /**
    step3   播放音频
            1. 入参是struct _PlayParam*
            2. 获取并转换dll 返回int类型
    */
    playResult, _, _ := lcAudioSdkPlayFunc.Call(uintptr(unsafe.Pointer(playParam)))
    fmt.Println("lcaudio play result : ", int32(playResult))

    /**
    step4   调整音量
            1. 入参是struct _PlayParam* 和 char (疑惑)
            2. 获取并转换dll 返回int类型
    */
    volumeResult, _, _ := lcAudioSdkSetVolumeFunc.Call(uintptr(unsafe.Pointer(playParam)), uintptr(90))
    fmt.Println("lcaudio set volume result : ", int32(volumeResult))

    /**
    step5   单播模式添加IP设备
            1. 入参是struct _PlayParam* 和 DWORD
            2. 获取并转换dll 返回int类型
    */
    addIpResult, _, _ := lcAudioSdkAddIPFunc.Call(uintptr(unsafe.Pointer(playParam)), uintptr(C.long(ipAddrToInt("192.168.0.5"))))
    fmt.Println("lcaudio add ip result : ", int32(addIpResult))

}

// ip地址转16进制
func ipAddrToInt(ipAddr string) int {
    bits := strings.Split(ipAddr, ".")
    b0, _ := strconv.Atoi(bits[0])
    b1, _ := strconv.Atoi(bits[1])
    b2, _ := strconv.Atoi(bits[2])
    b3, _ := strconv.Atoi(bits[3])
    var sum int
    sum += int(b0) << 24
    sum += int(b1) << 16
    sum += int(b2) << 8
    sum += int(b3)

    return sum
}

// 文件校验
func IsIllegalFile(filePath string) bool {
    if IsChineseChar(filePath) {
        return true
    }
    if !PathExists(filePath) {
        return true
    }

    return false
}

func IsChineseChar(str string) bool {
    for _, r := range str {
        if unicode.Is(unicode.Scripts["Han"], r) {
            return true
        }
    }
    return false
}

func PathExists(path string) bool {
    _, err := os.Stat(path)
    if err == nil {
        return true
    }
    if os.IsNotExist(err) {
        return false
    }
    return false
}

填坑

1. The specified module could not be found.

在执行syscall.LoadDLL时报错。如果报这个错,可以考虑从两个方面找问题:

第一:有可能是dll路径不对。

第二:有可能是当前dll所需要的其他dll丢失。

第一种情况很好解决,换一个全英文路径试一试。第二种情况需要借助DependenciesGui工具查找dll的依赖项。不推荐用depends22这个工具。将缺失的dll下载并放在当前dll同一层目录即可(或者放在系统目录),只要黄色感叹号消失即可。

Golang调用Dll案例

2. %1 is not a valid Win32 application.

一般是在64位下执行32位的dll会出现这种情况,配置编译环境即可。GOARCH=386;CGO_ENABLED=1

Golang调用Dll案例

3. The operation completed successfully.

在执行.Call()方法会返回三个参数。其中第三个参数就是error。并且这个error始终不为nil,打印的错误信息是操作已完成?????????。ITDragon龙用着龟速的网络在git上看到了这个issue,但是已经被关闭了,说的是已经修复了.......可我用的go version 是1.13.1 。没有深究这个问题,毕竟工作要紧。最后是直接忽略第三个参数,通过dll方法的返回值来判断是否成功。

Golang调用Dll案例

4. error: unknown type name ‘HWND‘、‘DWORD‘.

在定义结构体时,遇到了两个无法识别的类型。查阅了一些文档和dll使用说明文档。我ITDragon龙用long类型代替了,结果是没问题。

5. 获取dll返回的结构体

如果返回值是结构体指针,采用:(*C.自定义结构体)(unsafe.Pointer(返回值))

如果返回值是结构体,采用:(*C.自定义结构体)(unsafe.Pointer(&返回值))

详情可以参考调用LCAudioThrDll 案例

我的疑惑

ITDragon龙可能连半桶水都不是,从接触dll开发到现在只有几天。因为项目进度的问题,很多细节都没有时间去了解,只要能跑起来就行。想到后续还会有其他dll需要调用,先整理一个葫芦,后续再画。

1. 为什么dll需要的参数是unsigned char*,定义C.CString 可以正常执行

2. 为什么dll需要的参数是char,定义golang的int类型可以正常执行

3. 为什么dll需要的参数是DWORD,定义golang的int类型可以正常执行

下面是我了解的类型转换,先挖再填!

Golang调用Dll案例