Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Passing string to Syscall(uintptr)

Tags:

go

dll

I need to pass string as param to C DLL in golang, i want to get something like this:

  proc, e = syscall.GetProcAddress(h, "JLINKARM_ExecCommand") //One of the functions

    vals := []string{"device = STM32F429ZI"}
    start := uintptr(unsafe.Pointer(&vals[0]))

    asd, _, _ = syscall.Syscall6(uintptr(proc), 3, start, 0, 0, 0, 0, 0) 

but it doesn't work. Syscall take as param uintptr. I can't use C package because it won't build on windows.

In C it works like:

strcpy(acIn, "device = STM32F407IE");

JLINKARM_ExecCommand(acIn, &acOut[0], sizeof(acOut));

So it is any chance that C DLL can get string param and use it properly?

like image 664
Frnk Wdl Avatar asked Sep 05 '25 03:09

Frnk Wdl


2 Answers

To pass a string to syscall you need to pass a pointer to the first character of the string. The first question is what string encoding your DLL function expects. Go strings are encoded as UTF-8 unicode so if your C function expects something else you have to convert them first. Here are some common cases:

1) ASCII string

Say your C function expects a zero-terminated ASCII string, then you can do the following:

s := "some string"
b := append([]byte(s), 0)
syscall.Syscall(uintptr(proc), 1, uintptr(unsafe.Pointer(&b[0])), 0, 0)

First convert your string to a byte array and add the zero that C expects at the end. Then pass the pointer to the first byte character.

To be safe you should also make sure that you actually pass in only valid ASCII strings and no invalid characters. Usually only characters in the range [0..127] are valid for general ASCII. The rest depends on the current codepage.

2) UTF-16 on Windows

If you call into a Windows DLL you want to usually use their UTF-16 version of that function, e.g. SendMessageW, and thus need to convert your string to UTF-16. Luckily there is a wrapper function for that under Windows, so you can just do the following:

s := "some string"
s16, err := syscall.UTF16PtrFromString(s)
if err == nil {
    syscall.Syscall(uintptr(proc), 1, uintptr(unsafe.Pointer(s16)), 0, 0)
}

This will convert to UTF-16 and append the expected zero at the end for you.

like image 69
gonutz Avatar answered Sep 07 '25 21:09

gonutz


A good way to handle this, is to use the mkwinsyscall tool. You can create a Go file like this:

package main
//go:generate mkwinsyscall -output zmsi.go msi.go
//sys MsiInstallProduct(path string, command string) (e error) = msi.MsiInstallProductW

Then run go generate, and you get a result file like this (some parts removed for brevity):

func MsiInstallProduct(path string, command string) (e error) {
    var _p0 *uint16
    _p0, e = syscall.UTF16PtrFromString(path)
    if e != nil {
        return
    }
    var _p1 *uint16
    _p1, e = syscall.UTF16PtrFromString(command)
    if e != nil {
        return
    }
    return _MsiInstallProduct(_p0, _p1)
}

func _MsiInstallProduct(path *uint16, command *uint16) (e error) {
    r0, _, _ := syscall.Syscall(procMsiInstallProductW.Addr(), 2, uintptr(unsafe.Pointer(path)), uintptr(unsafe.Pointer(command)), 0)
    if r0 != 0 {
        e = syscall.Errno(r0)
    }
    return
}

As you can see, it automatically takes care of the string conversion, as well as the Syscall code, and even the error handling.

like image 27
Zombo Avatar answered Sep 07 '25 21:09

Zombo