首页 > 解决方案 > 从指向 C-Char 数组的指针中获取字符串数据

问题描述

我从 C++ dll 获得指向 C-char 数组的指针。如何获取字符串数据?

我正在使用此代码来获取指向内存地址的指针:

去:

dll, err := windows.LoadDLL("someDll.dll")
if err != nil {
    panic(err)
}
proc := dll.MustFindProc("Function")
response, _, _ := proc.Call()

C++ 代码:

extern "C" __declspec(dllexport) char* Function() {
    char text[] = "something";
    return text;
}

我试过这个:

fmt.Println(*(*string)(unsafe.Pointer(&response)))

还有这个:

var data []byte
sh := (*reflect.SliceHeader)(unsafe.Pointer(&data))
sh.Data = response
sh.Len = 9
sh.Cap = 9
fmt.Println(data, string(data))

但没有什么对我有用。

标签: go

解决方案


您的示例 C++ 代码......不好,因为它返回一个指向已释放存储的变量的指针。使用static char text[]或类似的方法来修复它。真正的 C 或 C++ 代码将返回指向应该使用 释放free或不释放的内存的指针,您也需要处理这些情况。

尽管如此,您仍然可以通过cgoGo 内置的部分或大部分方式获得所需的内容。注意,使用 cgo 时,需要单独import "C"声明,一般以注释为前缀。(您不能将此导入放入常规导入部分。)

让我们分几个部分来介绍所有这些。

没有 DLL,C 代码返回指向staticstorage-durationchar的指针

如果我们忽略所有 DLL 方面,并假设您可以直接链接到返回 的特定 C 函数,char *那么使用它的示例 Go 代码将是:

package main

/*
extern char *Function();
*/
import "C"

import (
    "fmt"
)

func WrapFunction() string {
    return C.GoString(C.Function())
}

func main() {
    fmt.Println(WrapFunction())
}

我将一个样本Function()放在一个文件中,该文件本身cgo.go在一个目录中命名,该目录中只有一个.c文件 , function.c。这是这个特殊的非常简单的 C 代码Function()

char *Function() {
    return "hello";
}

(AC 字符串字面量具有在编译时确定char [N]的整数常量N和静态持续时间的类型,而 C 具有将数组“衰减”为指向其第一个元素的指针的有趣规则,因此我们实现了返回char *指向静态的目标-持续时间记忆。)

$ go build
$ ./cgo
hello
$ 

返回指向动态分配内存的 C 代码

假设 C(或包装的 C++)函数使用malloc(or new) 分配字符串,因此我们必须释放它。这是这样做的更新function.c

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

char *Function() {
    char *text = malloc(25); /* big enough for hello and a number */
    if (text != NULL) {
        srand(time(NULL));   /* should do this just once, really */
        sprintf(text, "hello %d", rand() % 100);
    }
    return text;
}

然后我们将重写cgo.go为:

package main

/*
#include <stdlib.h>
extern char *Function();
*/
import "C"

import (
    "fmt"
    "unsafe"
)

func WrapFunction() string {
    s := C.Function()
    // Optional -- note that C.free(nil) is a no-op,
    // and (by experimentation) C.GoString(nil) returns "".
    // So, if it's OK to use "" if `C.Function` returns NULL,
    // skip this test-and-panic.
    if s == nil {
        panic("C.Function returned NULL")
    }
    defer C.free(unsafe.Pointer(s))
    return C.GoString(s)
}

func main() {
    fmt.Println(WrapFunction())
}

编译并运行显示:

$ go build
$ ./cgo
hello 27

另见https://blog.golang.org/c-go-cgohttps://golang.org/cmd/cgo/

动态链接库

以上都假设 C 包装器位于 build 目录中。Runninggo build发现这一点并构建function.c到一个目标文件,在 C 和 Go 之间添加所有必要的运行时接口,并按Function名称直接调用该函数。

If you must determine the function name at runtime, you have a much more serious problem. The heart of the issue is that you cannot get assistance from cgo to wrap the function directly, because cgo needs to see the function's name, in that comment above the import "C" line.

See how to import a DLL function written in C using GO? for several approaches. Another one not listed there is to do the DLL lookup in a C wrapper that you write yourself. Use cgo to pass the name of the function to look up, and all the (fixed) arguments. Then you're calling a known (compile-time) function with a known (compile-time) return type and value, which simplifies the problem. The cgo runtime wrapper will insert the appropriate runtime stack-switching for your known function, so that you do not have to use the syscall wrappers.


推荐阅读