Reputation: 400
I am using Go's syscall package to call a DLL that is written in C++.
C++ method signature looks like this.
init(int* buffer, int argc, char* argv[], const char* fileName, const char* key, const char* prefix, const char* version)
this is the function I am using to call above method in 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
}
I get this Error When I invoking this method and don't have an Idea about this.
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
Upvotes: 2
Views: 1376
Reputation: 55453
So, basically, the mapping you've got is
int* buffer
→ buffer uintptr
int argc
→ unsafe.Pointer(&argsCount)
, where &argsCount
is a pointer to int
char* argv[]
→ unsafe.Pointer(&args)
, where args
is []string
const char* fileName
→ unsafe.Pointer(syscall.StringToUTF16Ptr(fileName))
const char* key
, const char* prefix
, const char* version
— same as above.
Now what's wrong with this.
Passing uintptr
values containing addresses of live Go objects between functions is prohibited. (More on this later.)
You pass the address of the argsCount
function argument to argc
. An address on a typical commodity platform/OS like amd64/Windows is an insanely huge value—if interpreted as a count.
My bet is that the function crashes when it makes an attempt to read that many elements from argv
—causing it to read memory your process had not mapped.
Two problems here:
Passing an address of a slice value as an argument expecting the address of the first element of that slice is wrong.
This is because a slice value (presently, in the "reference" Go implementation you're supposedly using) is a struct
with 3 fields: an address of the underlying data array, the number of elements in that array allowed to be used and the total count of such elements in that array which may be used without reallocation by the append
function when called on the slice value.
When you have args []string
and do &args
you get the address of that structure, not the address of the first element of that slice.
To do the latter, use &args[0]
.
In Go, a string (typically, and let's assume that's the case) contains characters encoded as UTF-8. This is typically not what a Windows-native C++ code expects to deal with when it says it wants a char *
.
My guess, you need to first construct a proper thing for the argv
,
something like
argv := make([]unsafe.Pointer, 0, len(args))
for i, s := range args {
argv[i] = unsafe.Pointer(syscall.StringToUTF16Ptr(s))
}
and then pass &argv[0]
to the callee.
But see below for syscall.StringToUTF16Ptr()
.
The preparation you're doing to pass string data to the rest of the arguments which have the const char*
type appears to be correct but only if the callee really means its char
is a 16-bit integer.
In other words, the source code and the toolchain used to compile that library must have made sure that char
was really wchar_t
or WCHAR
.
If yes, what you do should be OK; otherwise it's not. You should verify this.
uintptr
s across expressionsGo features garbage collection, and because of this its runtime must know all pointers to all currently live objects. As long as there exists a variable containing a pointer to a memory block allocated during the program run time, that block won't be garbage-collected.
An unsafe.Pointer
value counts as a proper reference to a memory block but an uintptr
value is not. This means when the code
p := new(someType)
u := uintptr(unsafe.Pointer(&p))
foo(u)
return
is running, the GC is free to reclaim the object allocated by p
as soon as p
was assigned its address—simply because p
is the sole reference to that object, and u
is not.
Now consider that the GC in the reference Go implementation runs concurrently with the code of your program.
This means when foo
runs, so may do the GC, and it may sweep your instance of someType
right from under the feet of foo
.
As an exception to this general rule, the Go compiler guarantees that all the uintptr(unsafe.Pointer)
type conversions occuring in the same language expression are immune to the GC. So taking our previous example, it's OK to do
foo(uintptr(unsafe.Pointer(new(someType)))
return
and
p := new(someType)
v := unsafe.Pointer(&p)
foo(uintptr(v))
return
since the type-conversion to uintptr
happens in a single expression—which is a function call.
As a consequence, you must not pass pointers to Go objects around as uintptr
s unless they contain pointers obtained from the "C side".
Upvotes: 4