Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pass pointer to a pointer in Go DLL Syscall

If I have the following C# DllImport which is importing a non-C# DLL and I want to port it over to Go, how would I go about doing that?

    [DllImport("my.dll", EntryPoint = "Greet", CallingConvention = CallingConvention.Cdecl)]
    public static extern int Greet(IntPtr name, ref IntPtr greetings);

I've run into problems figuring out how to pass a pointer to a pointer which is needed for the greetings parameter (I assume since the type is ref IntPtr, I'm not that familiar at all with C#). The dll function will populate the memory pointed to by the pointer that I provide which I'll use in subsequent syscalls. Here's what I've got so far,

package main

import (
    "fmt"
    "syscall"
    "unsafe"
)

var (
    MyDll = syscall.MustLoadDLL("my.dll")
    greet = MyDll.MustFindProc("Greet")
)

func Greet(name string) error {
    nameP, err := syscall.UTF16PtrFromString(name)
    if err != nil {
        return err
    }
    // I need to provide a pointer to a pointer for greetings. How can I allocate some memory here
    // then pass a pointer to its pointer? I tried this: create a handle with a zero-value, then
    // take a pointer to it, then pass a pointer to the pointer as the second parameter to Call but
    // the pointer becomes nil after the Call.
    handle := syscall.Handle(0)
    handleP := &handle
    r1, _, _ := greet.Call(uintptr(unsafe.Pointer(nameP)), uintptr(unsafe.Pointer(&handleP)))
    if r1 == 0 {
        return fmt.Errorf("there was an error")
    }
    return nil
}

I'm open to any and all suggestions including links and resources that might help me get a better grasp on this syscall and unsafe stuff. Thanks!

like image 844
Clark McCauley Avatar asked Nov 07 '22 00:11

Clark McCauley


1 Answers

Firstly, it would help if you could show how the C# Greet method is used. A method in isolation its quite hard to understand especially when the parameter is the equivalent of void ** which means anything can go in.

TL;DR ref IntPtr is probably just a **struct{} where you don't have to allocate any struct. The library will simply manage the memory for you. You just need to give it a pointer to "*MyStruct" so that it can change you "*MyStruct" to actually point to the internal resource.

REF

The C# ref keyword is quite well explained in the docs. Basically it allows a pass by reference for any type. The following declaration in C#

void Increment(ref value) { ... }

int counter = 10;
Increment(ref counter);
// counter is now 11
Increment(counter); // won't compile must be passed with 'ref'
Increment(null); // won't compile

is equivalent to C++

void Increment(int& value) { ... }

int counter;
Increment(counter);
// counter is now 11
Increment(null); // won't compile

A reference which should not be null.

IntPtr

IntPtr is usually used to represent pointers and allows for interop between native and CLR (C#) programs.

If a C program has the following signature

void Increment(int* value);

a C# program could call it in one of a few ways

[DllImport("example.dll")]
static unsafe extern void Increment(int* value); // this way allows null to be passed in

unsafe {
    int counter = 10;
    Increment(&10);
}

,

[DllImport("example.dll")]
static extern void Increment(ref int value);

int counter = 10;
Increment(ref counter);

If a C program has the following signature

void AllocateStruct(struct MyStruct** ppStruct);
void IncrementStruct(struct MyStruct* pStruct);

then

[DllImport("example.dll")]
static extern void AllocateStruct(ref IntPtr ppStruct);
// static unsafe extern void AllocateStruct(MyStruct** ppStruct)

[DllImport("example.dll")]
static extern void IncrementStruct(IntPtr pStruct);
// static unsafe extern void IncrementStruct(MyStruct* pStruct);

IntPtr pMyStruct;
AllocateStruct(ref pMyStruct);
IncrementStruct(pMyStruct);
// Free My Struct

// If you need to get inside the struct then
// MyStruct myStruct = Marshal.StructureToPtr<MyStruct>(pMyStruct)
// Often you don't (need to) or (should not) manipulate the struct directly so keeping it as IntPtr is perfectly acceptable.

From the example above you can see MyStruct is more of a token/reference than anything else. ref IntPtr allows you to pass a reference to the location which you will use to store your token/reference after the library allocates it on your behalf. Then all the other methods will usually just use the reference IntPtr to perform subsequent manipulations on it. Basically Object Orientated Programming without classes.

"Real" Life Example with ref IntPtr

It is a bit quick and dirty and error handling leave a lot to be desired. It shows the C, C# and Go versions which call the same GetSecurityInfo and LookupAccountSid Win32 library functions.

The only real use case I can find for ref IntPtr is type**/void** so that the library can allocate the memory for you or give you a pointer to memory it has already allocated.

C

#include <stdio.h>
#include <windows.h>
#include <tchar.h>
#include "accctrl.h"
#include "aclapi.h"
#pragma comment(lib, "advapi32.lib")

int _tmain(int argc, _TCHAR* argv[])
{
    // --- get the executing program's file path and handle -----

    LPTSTR executablePath = argv[0];
    _tprintf(TEXT("Opening File %s\n"), executablePath);

    HANDLE hFile = CreateFile(
        executablePath,
        GENERIC_READ,
        FILE_SHARE_READ,
        NULL,
        OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL,
        NULL);

    if (hFile == INVALID_HANDLE_VALUE) return EXIT_FAILURE;

    // -------------------------------------------------

    // --------- Get the owner SID of the file ---------

    PSID pSidOwner = NULL;
    PSECURITY_DESCRIPTOR pSD = NULL;

    DWORD dwRtnCode = GetSecurityInfo(
        hFile,
        SE_FILE_OBJECT,
        OWNER_SECURITY_INFORMATION,
        &pSidOwner,
        NULL,
        NULL,
        NULL,
        &pSD);

    if (dwRtnCode != ERROR_SUCCESS) return EXIT_FAILURE;

    // -------------------------------------------------

    // ------- 

    TCHAR AcctName[MAX_PATH];
    DWORD dwAcctName = MAX_PATH;

    TCHAR DomainName[MAX_PATH];
    DWORD dwDomainName = MAX_PATH;

    SID_NAME_USE eUse = SidTypeUnknown;

    BOOL bRtnBool = LookupAccountSid(
        NULL,           // local computer
        pSidOwner,
        &AcctName,
        &dwAcctName,
        DomainName,
        &dwDomainName,
        &eUse);

    if (bRtnBool == FALSE) return EXIT_FAILURE;

    _tprintf(TEXT("Account Owner = %s\n"), AcctName);
    _tprintf(TEXT("Account Owner's Domain = %s\n"), DomainName);

    return 0;
}

C#

using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;

public class Example
{
    [DllImport("advapi32.dll", SetLastError = true, CallingConvention = CallingConvention.Winapi)]
    static extern uint GetSecurityInfo(
        IntPtr handle,
        uint ObjectType,
        uint SecurityInfo,
        ref IntPtr ppsidOwner, // <-- HERE
        IntPtr ppsidGroup, // bit hacky (in safe C# you must "pass a reference" in C you can pass a pointer to a pointer or null)
        IntPtr ppDacl,
        IntPtr ppSacl,
        ref IntPtr ppSecurityDescriptor // <-- HERE
    );

    [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    static extern bool LookupAccountSid(
        string lpSystemName,
        IntPtr Sid,
        StringBuilder lpName,
        ref uint cchName,
        StringBuilder ReferencedDomainName,
        ref uint cchReferencedDomainName,
        out uint peUse);

    const uint ERROR_SUCCESS = 0;
    const uint OWNER_SECURITY_INFORMATION = 0x00000001;
    const uint SE_FILE_OBJECT = 1;

    public static void Main()
    {

        // get the executing program's file path and handle
        string executablePath = Environment.GetCommandLineArgs().GetValue(0).ToString();
        IntPtr hFile = File.Open(executablePath, FileMode.Open, FileAccess.Read, FileShare.Read)
            .SafeFileHandle.DangerousGetHandle();

        IntPtr pSidOwner = IntPtr.Zero; // some internal struct you shouldn't allocate or modify (acts like a token)
        IntPtr pSD = IntPtr.Zero; // some internal struct you shouldn't allocate or modify (acts like a token)

        // vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv

        uint dwRtnCode = GetSecurityInfo(
            hFile,
            SE_FILE_OBJECT,
            OWNER_SECURITY_INFORMATION,
            ref pSidOwner, // <-- HERE
            IntPtr.Zero,
            IntPtr.Zero,
            IntPtr.Zero,
            ref pSD // <-- HERE
        );

        // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

        if (dwRtnCode != ERROR_SUCCESS) throw new InvalidOperationException("GetSecurityInfo Failed");

        StringBuilder name = new StringBuilder(50);
        uint cchName = (uint)name.Capacity;
        StringBuilder domainName = new StringBuilder(50);
        uint cchDomainName = (uint)domainName.Capacity;
        uint sidUse;

        LookupAccountSid(
            null,
            pSidOwner,
            name,
            ref cchName,
            domainName,
            ref cchDomainName,
            out sidUse);

        Console.WriteLine("Account Owner = {0}", name);
        Console.WriteLine("Account Owner's Domain = {0}", domainName);

        // PLEASE FREE pSD once done 
    }


}

Go

my second Go program I have ever written so there are probably some blaring mistakes (other than the lack of error checking)

package main

import (
    "fmt"
    "syscall"
    "unsafe"
    "os"
)

var (
    advapi32, _ = syscall.LoadLibrary("advapi32.dll")
    kernel32, _ = syscall.LoadLibrary("kernel32.dll")
    createFileW, _ = syscall.GetProcAddress(kernel32, "CreateFileW")
    getSecurityInfo, _ = syscall.GetProcAddress(advapi32, "GetSecurityInfo")
    lookupAccountSidW, _ = syscall.GetProcAddress(advapi32, "LookupAccountSidW")
)

type SE_OBJECT_TYPE uint32
const (SE_FILE_OBJECT = 1)

type SECURITY_INFORMATION uint32
const (OWNER_SECURITY_INFORMATION = 0x00000001)

const (
    GENERIC_READ    = 0x80000000
    FILE_SHARE_READ = 0x00000001
    OPEN_EXISTING   = 0x00000003
    FILE_ATTRIBUTE_NORMAL   = 0x00000080
)

type Handle uintptr

func CreateFile(
    name string,
    access uint32,
    mode uint32,
    sa *uint, // *SecurityAttributes,
    createmode uint32,
    attrs uint32,
    templatefile *uint,
) (handle Handle, err error) {

    utf16name, _ := syscall.UTF16PtrFromString(name)

    r0, _, _ := syscall.Syscall9(
        uintptr(createFileW), 7, 
        uintptr(unsafe.Pointer(utf16name)), 
        uintptr(access), 
        uintptr(mode), 
        uintptr(unsafe.Pointer(sa)), 
        uintptr(createmode), 
        uintptr(attrs), 
        uintptr(unsafe.Pointer(templatefile)),
        0, 0)
    handle = Handle(r0)
    return
}

func GetSecurityInfo(
    handle Handle, 
    objectType SE_OBJECT_TYPE, 
    securityInformation SECURITY_INFORMATION, 
    owner **struct{}, 
    group **struct{}, 
    dacl **struct{}, 
    sacl **struct{}, 
    sd **struct{}, //**SECURITY_DESCRIPTOR,
) (ret error) {
    r0, _, _ := syscall.Syscall9(
        uintptr(getSecurityInfo), 8, 
        uintptr(handle), 
        uintptr(objectType), 
        uintptr(securityInformation), 
        uintptr(unsafe.Pointer(owner)), 
        uintptr(unsafe.Pointer(group)), 
        uintptr(unsafe.Pointer(dacl)), 
        uintptr(unsafe.Pointer(sacl)), 
        uintptr(unsafe.Pointer(sd)), 
        0)
    if r0 != 0 {
        ret = syscall.Errno(r0)
    }
    return
}

func LookupAccountSid(
    systemName *uint16,
    sid *struct{}, // *SID,
    name *uint16,
    nameLen *uint32, 
    refdDomainName *uint16,
    refdDomainNameLen *uint32,
    use *uint32,
) (err error) {
    r, _, e := syscall.Syscall9(
        uintptr(lookupAccountSidW), 7, 
        uintptr(unsafe.Pointer(systemName)), 
        uintptr(unsafe.Pointer(sid)),
        uintptr(unsafe.Pointer(name)),
        uintptr(unsafe.Pointer(nameLen)),
        uintptr(unsafe.Pointer(refdDomainName)),
        uintptr(unsafe.Pointer(refdDomainNameLen)),
        uintptr(unsafe.Pointer(use)),
        0, 0)
    if r == 0 {
        err = e
    }
    return
}

func main() {
    defer syscall.FreeLibrary(advapi32)
    defer syscall.FreeLibrary(kernel32)

    // get the executing program's file path and handle
    var hFile, _ = CreateFile(os.Args[0], GENERIC_READ, FILE_SHARE_READ, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nil);

    // defer LocalFree(Handle(unsafe.Pointer(pSD))) // PLEASE FREE pSD once done 

    // vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv

    var pSD *struct{} //*SECURITY_DESCRIPTOR
    var pSidOwner *struct{}
    GetSecurityInfo(hFile, SE_FILE_OBJECT, OWNER_SECURITY_INFORMATION, &pSidOwner, nil, nil, nil, &pSD)

    // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

    nameLen := uint32(50)
    name := make([]uint16, nameLen)

    domainLen := uint32(50)
    domainName := make([]uint16, domainLen)

    var sidUse uint32

    LookupAccountSid(nil, pSidOwner, &name[0], &nameLen, &domainName[0], &domainLen, &sidUse)

    var n = syscall.UTF16ToString(name)
    var dn = syscall.UTF16ToString(domainName)

    fmt.Printf("Account Owner = %s\n", n)
    fmt.Printf("Account Owner's Domain = %s\n", dn)
}
like image 94
Kwiksilver Avatar answered Nov 11 '22 05:11

Kwiksilver