golang 从 dll 中获取 char* 作为 return 值
golang get char* as return value from dll
我在用golang调用一个dll函数,像char* fn()
,dll不是我自己写的,我不能改。这是我的代码:
package main
import (
"fmt"
"syscall"
"unsafe"
)
func main() {
dll := syscall.MustLoadDLL("my.dll")
fn := dll.MustFindProc("fn")
r, _, _ := fn.Call()
p := (*byte)(unsafe.Pointer(r))
// define a slice to fill with the p string
data := make([]byte, 0)
// loop until find '[=11=]'
for *p != 0 {
data = append(data, *p) // append 1 byte
r += unsafe.Sizeof(byte(0)) // move r to next byte
p = (*byte)(unsafe.Pointer(r)) // get the byte value
}
name := string(data) // convert to Golang string
fmt.Println(name)
}
我有一些问题:
- 有更好的方法吗?像这样的 dll 函数有数百个,我将不得不为所有函数编写循环。
- 对于像 100k+ 字节这样的超长字符串,
append()
会导致性能问题吗?
- 已解决。
unsafe.Pointer(r)
导致 linter govet 显示警告 possible misuse of unsafe.Pointer
,但代码运行正常,如何避免此警告? 解决方案: 这可以通过在govet
命令行中添加-unsafeptr=false
来解决,对于vim-ale , 添加 let g:ale_go_govet_options = '-unsafeptr=false'
。
将 uintptr 转换为 uppointer 是非法的。
你必须阅读规则:
https://golang.org/pkg/unsafe/#Pointer
但是有一个不应该产生警告的 hacky 方法:
//go:linkname gostringn runtime.gostringn
func gostringn(p uintptr, l int) string
//go:linkname findnull runtime.findnull
//go:nosplit
func findnull(s uintptr) int
// ....
name := gostringn(r, findnull(r))
函数采用指针,但我们 link 从运行时将它们作为 uintptr,因为它们具有相同的 sizeof。
理论上可能可行。却也被人看不起。
回到你的代码,正如 JimB 所说,你可以用一行来完成:
name := C.GoString((*C.char)(unsafe.Pointer(r)))
我通过跟踪go源码的os.Args
得到如下解决方案,但是我是基于go1.17的。如果你是其他版本,可以阅读源码解决。
func UintPtrToString(r uintptr) string {
p := (*uint16)(unsafe.Pointer(r))
if p == nil {
return ""
}
n, end, add := 0, unsafe.Pointer(p), unsafe.Sizeof(*p)
for *(*uint16)(end) != 0 {
end = unsafe.Add(end, add)
n++
}
return string(utf16.Decode(unsafe.Slice(p, n)))
}
我在用golang调用一个dll函数,像char* fn()
,dll不是我自己写的,我不能改。这是我的代码:
package main
import (
"fmt"
"syscall"
"unsafe"
)
func main() {
dll := syscall.MustLoadDLL("my.dll")
fn := dll.MustFindProc("fn")
r, _, _ := fn.Call()
p := (*byte)(unsafe.Pointer(r))
// define a slice to fill with the p string
data := make([]byte, 0)
// loop until find '[=11=]'
for *p != 0 {
data = append(data, *p) // append 1 byte
r += unsafe.Sizeof(byte(0)) // move r to next byte
p = (*byte)(unsafe.Pointer(r)) // get the byte value
}
name := string(data) // convert to Golang string
fmt.Println(name)
}
我有一些问题:
- 有更好的方法吗?像这样的 dll 函数有数百个,我将不得不为所有函数编写循环。
- 对于像 100k+ 字节这样的超长字符串,
append()
会导致性能问题吗? - 已解决。
unsafe.Pointer(r)
导致 linter govet 显示警告possible misuse of unsafe.Pointer
,但代码运行正常,如何避免此警告? 解决方案: 这可以通过在govet
命令行中添加-unsafeptr=false
来解决,对于vim-ale , 添加let g:ale_go_govet_options = '-unsafeptr=false'
。
将 uintptr 转换为 uppointer 是非法的。 你必须阅读规则: https://golang.org/pkg/unsafe/#Pointer
但是有一个不应该产生警告的 hacky 方法:
//go:linkname gostringn runtime.gostringn
func gostringn(p uintptr, l int) string
//go:linkname findnull runtime.findnull
//go:nosplit
func findnull(s uintptr) int
// ....
name := gostringn(r, findnull(r))
函数采用指针,但我们 link 从运行时将它们作为 uintptr,因为它们具有相同的 sizeof。
理论上可能可行。却也被人看不起。
回到你的代码,正如 JimB 所说,你可以用一行来完成:
name := C.GoString((*C.char)(unsafe.Pointer(r)))
我通过跟踪go源码的os.Args
得到如下解决方案,但是我是基于go1.17的。如果你是其他版本,可以阅读源码解决。
func UintPtrToString(r uintptr) string {
p := (*uint16)(unsafe.Pointer(r))
if p == nil {
return ""
}
n, end, add := 0, unsafe.Pointer(p), unsafe.Sizeof(*p)
for *(*uint16)(end) != 0 {
end = unsafe.Add(end, add)
n++
}
return string(utf16.Decode(unsafe.Slice(p, n)))
}