我正在使用Go的syscall软件包来调用用C ++编写的DLL。
C ++方法签名看起来像这样。
init(int* buffer, int argc, char* argv[], const char* fileName, const char* key, const char* prefix, const char* version)
这是我用来在Go中调用上述方法的函数。
func init(
buffer uintptr,
argsCount int,
args []string,
fileName string,
key string,
prefix string,
version string
) uintptr {
// libHandle is handle to the loaded DLL
methodAddress := getProcAddress(libHandle, "init")
status, _, err := syscall.Syscall9(
methodAddress,
7,
buffer,
uintptr(unsafe.Pointer(&argsCount)),
uintptr(unsafe.Pointer(&args)),
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(fileName))),
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(key))),
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(prefix))),
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(version))),
0,
0)
fmt.Println(err.Error())
return status
}
当我调用此方法并且对此一无所知时,出现此错误。
Exception 0xc0000005 0x0 0x0 0x7fffe30bdb33
PC=0x7fffe30bdb33
syscall.Syscall9(0x7fffe32db600, 0x7, 0x97db50, 0xc00007ff10,
0xc00007ff70, 0xc000054180, 0xc0000541a0, 0xc0000541c0, 0xc0000541e0,
0x0, ...)
c:/go/src/runtime/syscall_windows.go:210 +0xf3
main.main()
E:/Path/test.go:157 +0x2be
rax 0x81fbf0
rbx 0x1
rcx 0x7ff804ad1310
rdi 0x7ff10
rsi 0xc00007ff70
rbp 0x0
rsp 0x81fbc0
r8 0x0
r9 0x7ff804ad0000
r10 0xc00007ff00
r11 0x81fbf0
r12 0x7ff10
r13 0xc00007ff70
r14 0xc000054180
r15 0x97db50
rip 0x7fffe30bdb33
rflags 0x10257
cs 0x33
fs 0x53
gs 0x2b
答案 0 :(得分:0)
所以,基本上,您所拥有的映射是
int* buffer
→buffer uintptr
int argc
→unsafe.Pointer(&argsCount)
,其中&argsCount
是指向int
char* argv[]
→unsafe.Pointer(&args)
,其中args
是[]string
const char* fileName
→unsafe.Pointer(syscall.StringToUTF16Ptr(fileName))
const char* key
,const char* prefix
,const char* version
-与上面相同。
现在这是怎么回事。
禁止在函数之间传递包含实时Go对象地址的uintptr
值。 (稍后会详细介绍。)
您将argsCount
函数参数的地址传递给argc
。如果将其解释为计数,那么在典型的商品平台/操作系统(例如amd64 / Windows)上的地址是一个巨大的价值。
我敢打赌,该函数在尝试从argv
中读取许多元素时会崩溃—导致该函数读取您的进程未映射的内存。
这里有两个问题:
将分片值的地址作为参数传递而期望该分片的第一个元素的地址是错误的。
这是因为slice值(当前,在您正在使用的“引用” Go实现中)是3个参数的结构:基础数据数组的地址,该数组中元素的数量为在切片值上调用时,append
函数可以使用而无需重新分配的数组中此类元素的总数。
拥有args []string
并执行&args
时,您将获得该结构的地址,而不是该切片的第一个元素的地址。
要执行后者,请使用&args[0]
。
在Go中,一个字符串(通常,假设是这种情况)包含编码为UTF-8的字符。通常,这不是Windows本地C ++代码期望使用char *
时要处理的内容。
我的猜测是,您需要首先为argv
构造一个合适的东西,
像
argv := make([]unsafe.Pointer, 0, len(args))
for i, s := range args {
argv[i] = unsafe.Pointer(syscall.StringToUTF16Ptr(s))
}
然后将&argv[0]
传递给被叫方。
但是请参见下面的syscall.StringToUTF16Ptr()
。
为将字符串数据传递给其余类型为const char*
的参数而进行的准备工作似乎是正确的,但前提是被叫方确实表示其char
是16位整数。
换句话说,用于编译该库的源代码和工具链必须确保char
确实是wchar_t
or WCHAR
。
如果是,那么您应该做的事;否则不是。您应该对此进行验证。
uintptr
的说明Go具有垃圾回收功能,因此,运行时必须知道指向所有当前活动对象的所有指针。只要存在一个包含指向程序运行时分配的内存块的指针的变量,该内存块就不会被垃圾回收。
unsafe.Pointer
值被视为对存储块的正确引用,而uintptr
值不是。 这意味着代码< / p>
p := new(someType)
u := uintptr(unsafe.Pointer(&p))
foo(u)
return
正在运行,一旦分配了p
的地址,GC便可以随意回收p
分配的对象,这是因为p
是对该对象的唯一引用,并且u
不是。
现在考虑参考Go实现中的GC与程序代码同时运行。
这意味着foo
运行时,GC也会运行,它可能会从someType
的脚下扫掠foo
的实例。
作为该通用规则的例外,Go编译器保证在同一语言表达式中发生的所有uintptr(unsafe.Pointer)
类型转换均不受GC的影响。因此,以我们之前的示例为例,
foo(uintptr(unsafe.Pointer(new(someType)))
return
和
p := new(someType)
v := unsafe.Pointer(&p)
foo(uintptr(v))
return
因为向uintptr
的类型转换发生在单个表达式中,这是一个函数调用。
因此,不得将指针传递给Go对象,因为uintptr
的 除非它们包含从“ C端”获得的指针。