c# - 将指针传递给 Go DLL Syscall 中的指针

标签 c# go dll system-calls

如果我有以下 C# DllImport它正在导入一个非 C# DLL,我想将它移植到 Go,我该怎么做?

    [DllImport("my.dll", EntryPoint = "Greet", CallingConvention = CallingConvention.Cdecl)]
    public static extern int Greet(IntPtr name, ref IntPtr greetings);
我在弄清楚如何将指针传递给 greetings 所需的指针时遇到了问题。参数(我假设因为类型是 ref IntPtr ,我对 C# 一点也不熟悉)。 dll 函数将填充我提供的指针指向的内存,我将在后续系统调用中使用该指针。这是我到目前为止所得到的
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
}
我愿意接受任何和所有建议,包括可能帮助我更好地掌握这个系统调用和不安全内容的链接和资源。谢谢!

最佳答案

首先,如果你能展示 C# Greet方法被使用。孤立的方法很难理解,尤其是当参数等效于 void ** 时。这意味着任何东西都可以进入。
TL; 博士ref IntPtr可能只是一个 **struct{}您不必分配任何结构。该库将简单地为您管理内存。您只需要给它一个指向“*MyStruct”的指针,这样它就可以将“*MyStruct”更改为实际指向内部资源。REFC# ref关键字在 docs 中有很好的解释.基本上它允许对任何类型进行引用传递。
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
相当于 C++
void Increment(int& value) { ... }

int counter;
Increment(counter);
// counter is now 11
Increment(null); // won't compile
不应为空的引用。IntPtrIntPtr通常用于表示指针并允许 native 和 CLR (C#) 程序之间的互操作。
如果 C 程序具有以下签名
void Increment(int* value);
C# 程序可以通过以下几种方式之一调用它
[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);
如果 C 程序具有以下签名
void AllocateStruct(struct MyStruct** ppStruct);
void IncrementStruct(struct MyStruct* pStruct);
然后
[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.
从上面的例子你可以看到 MyStruct更像是 token /引用比什么都重要。 ref IntPtr允许您传递对位置的引用,在图书馆代表您分配后,您将使用该引用来存储您的 token /引用。然后所有其他方法通常只使用引用 IntPtr对其进行后续操作。基本上没有类的面向对象编程。
“真实”生活示例与 ref IntPtr它有点快速和肮脏,错误处理还有很多不足之处。
它显示了 C , C#Go调用相同的版本 GetSecurityInfoLookupAccountSid Win32 库函数。
我能找到的唯一真实用例 ref IntPtrtype**/void**以便库可以为您分配内存或为您提供一个指向它已经分配的内存的指针。
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 程序,所以可能有一些明显的错误(除了缺少错误检查)
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)
}

关于c# - 将指针传递给 Go DLL Syscall 中的指针,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/69049497/

相关文章:

c# - 很棒的 C# 代码可以从中学习设计模式和最佳实践吗?

c# - 如何访问通用定义对象的属性?

c# - 从不同线程调用方法 AutoResetEvent 是否安全?

c++ - 重新定基 DLL(或提供适当的默认加载地址)值得这么麻烦吗?

dll - regsvr32 不会在注册表中创建任何条目

c# - 过滤 DataGridView Winforms

go - 服务器到服务器 OAuth2

go - go - 如何使用证书存储中的证书并在 gin 框架中运行 TLS?

c++ - 如何将 __stdcall dll 与未修饰的导出链接到 __cdecl 二进制文件中?

interface - 如何在 Go 中声明复合接口(interface)?