C# P/Invoke:Varargs 委托回调

发布于 2024-11-23 16:35:06 字数 571 浏览 2 评论 0原文

我只是想进行一些托管/非托管互操作。为了获得扩展的错误信息,我决定注册 dll 提供的日志回调:


[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public unsafe delegate void LogCallback(void* arg1,int level,byte* fmt);

此定义有效,但我得到类似“Format %sprobed with size=%d and Score=%d”的字符串。 我尝试添加 __arglist 关键字,但委托不允许这样做。

好吧,这对我来说并不是那么戏剧性,但我只是好奇是否可以在 C# 中获取 varargs 参数。 我知道我可以使用 C++ 进行互操作。 那么:有没有一种方法可以纯粹用 C# 来做到这一点,并付出合理的努力?

编辑:对于那些仍然不明白的人:我不导入一个可变参数函数而是将其导出为回调,然后调用它我的本机代码。我一次只能指定一个 ->只有一种重载可能,并且 __arglist 不起作用。

I was just trying to do some managed/unmanaged interop. To get extended error information I decided to register a log callback offered by the dll:


[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public unsafe delegate void LogCallback(void* arg1,int level,byte* fmt);

This definition works, but i get strings like "Format %s probed with size=%d and score=%d".
I tryed to add the __arglist keyword, but it is not allowed for delegates.

Well, it is not so dramatic for me, but I am just curious wether one could get the varargs parameters in C#.
I know that I could use c++ for interop.
So: Is there a way to do this purely in C#, with reasonable efford?

EDIT: For those who still did not get it: I am NOT IMPORTING a varargs function BUT EXPORTING it as a callback, which is then called my native code. I can specify only one at a time -> only one overload possible and __arglist does NOT work.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

场罚期间 2024-11-30 16:35:06

这是处理它的方法。它可能适用也可能不适用于您的情况,具体取决于您的回调参数是否要与 printf 系列函数一起使用。

首先,从 msvcrt 导入 vsprintf_vscprintf

[DllImport("msvcrt.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
public static extern int vsprintf(
    StringBuilder buffer,
    string format,
    IntPtr args);

[DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int _vscprintf(
    string format,
       IntPtr ptr);

接下来,使用 IntPtr args 指针声明您的委托:

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public unsafe delegate void LogCallback(
    void* arg1,
    int level, 
    [In][MarshalAs(UnmanagedType.LPStr)] string fmt,
    IntPtr args);

现在,当您的委托是通过本机代码调用的,只需使用 vsprintf 正确格式化消息:

private void LogCallback(void* data, int level, string fmt, IntPtr args)
{
    var sb = new StringBuilder(_vscprintf(fmt, args) + 1);
    vsprintf(sb, fmt, args);

    //here formattedMessage has the value your are looking for
    var formattedMessage = sb.ToString();

    ...
}

Here is the way to deal with it. It may or may not be applicable to your case, depending on whether your callback arguments are meant to be used with printf family of functions.

First, import vsprintf and _vscprintf from msvcrt:

[DllImport("msvcrt.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
public static extern int vsprintf(
    StringBuilder buffer,
    string format,
    IntPtr args);

[DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int _vscprintf(
    string format,
       IntPtr ptr);

Next, declare your delegate with IntPtr args pointer:

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public unsafe delegate void LogCallback(
    void* arg1,
    int level, 
    [In][MarshalAs(UnmanagedType.LPStr)] string fmt,
    IntPtr args);

Now when your delegate is invoked via native code, simply use vsprintf to format the message correctly:

private void LogCallback(void* data, int level, string fmt, IntPtr args)
{
    var sb = new StringBuilder(_vscprintf(fmt, args) + 1);
    vsprintf(sb, fmt, args);

    //here formattedMessage has the value your are looking for
    var formattedMessage = sb.ToString();

    ...
}
乱世争霸 2024-11-30 16:35:06

不,没有办法做到这一点。这是不可能的原因是因为变量参数列表在 C 中的工作方式。

在 C 中,变量参数只是作为额外参数推送到堆栈(在我们的例子中是非托管堆栈)。 C 不会在任何地方记录堆栈上的参数数量,被调用函数采用其最后一个形式参数(可变参数之前的最后一个参数)获取其位置并开始从堆栈中弹出参数。

它知道从堆栈中弹出多少变量的方式完全基于约定 - 其他一些参数指示堆栈上有多少变量参数。对于 printf 来说,它通过解析格式字符串并在每次看到格式代码时从堆栈中弹出来实现这一点。看来你的回调是相似的。

为了让 CLR 处理这个问题,它必须能够知道正确的约定来确定需要拾取多少个参数。您无法编写自己的处理程序,因为它需要访问您无权访问的非托管堆栈。所以你无法从 C# 中做到这一点。

有关这方面的更多信息,您需要阅读 C 调用约定。

No there is no possible way to do it. The reason it is impossible is because of the way variable argument lists work in C.

In C variable arguments are just pushed as extra parameters on to the stack (the unmanaged stack in our case). C does not anywhere record the number of parameters on the stack, the called function takes its last formal parameter (the last argument before the varargs) gets its location and starts popping arguments off the stack.

The way that it knows how many variables to pop off the stack is completely convention based - some other parameter indicates how many variable arguments are sitting on the stack. For printf it does that by parsing the format string and popping off the stack every time it sees a format code. It seems like your callback is similar.

For the CLR to handle that, it would have to be able to know the correct convention to determine how many arguments it needed to pickup. You can't write your own handler, because it would require access to the unmanaged stack which you don't have access to. So there is no way you can do this from C#.

For more information on this you need to read up on C calling conventions.

撕心裂肺的伤痛 2024-11-30 16:35:06

我不同意@shf301,这是可能的。

对于 PInvoke,您可以使用 __arglist,如下所示:

[DllImport("msvcrt", CallingConvention = CallingConvention.Cdecl, EntryPoint = "printf")]
public static extern int PrintFormat([MarshalAs(UnmanagedType.LPStr)] string format, __arglist);

调用: PrintFormat("Hello %d", __arglist(2019));

对于委托和回调:

  1. 定义以下结构:

    公共不安全结构VariableArgumentBuffer
    {
        公共 const int BufferLength = 64; // 如果需要你可以增加它
    
        公共固定字节缓冲区[BufferLength];
    
        [MethodImpl(MethodImplOptions.AggressiveInlined)]
        公共静态 VariableArgumentBuffer Create(params object[] args)
        {
            VariableArgumentBuffer buffer = new VariableArgumentBuffer();
            写入(参考缓冲区,参数);
            返回缓冲区;
        }
    
        公共静态无效写入(参考VariableArgumentBuffer缓冲区,参数对象[]参数)
        {
            如果(参数==空)
            返回;
    
            固定(字节* ptr = buffer.Buffer)
            {
                整数偏移量=0;
    
                for (int i = 0; i < args.Length; i++)
                {
                    var arg = args[i];
    
                    if (offset + Marshal.SizeOf(arg) > BufferLength)
                        抛出新的 ArgumentOutOfRangeException();
    
                    开关(参数)
                    {
                    大小写字节值:
                         *(ptr + offset++) = 值;
                         休息;
    
                    案例空值:
                         *(短*)(ptr + 偏移量) = 值;
                         偏移+= sizeof(短);
                         休息;
    
                    案例整数值:
                        *(int*)(ptr + 偏移量) = 值;
                        偏移量 += sizeof(int);
                        休息;
    
                    案例长值:
                        *(长*)(ptr + 偏移量)=值;
                        偏移量 += sizeof(long);
                        休息;
    
                    案例 IntPtr 值:
                        *(IntPtr*)(ptr + 偏移量) = 值;
                        offset += IntPtr.Size;
                        休息;
    
                    default: // TODO: 添加更多类型
                        抛出新的NotImplementedException();
                  }
              }
           }
        }
     }
    
  2. 定义您的委托

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    公共委托 int PrintFormatDelegate([MarshalAs(UnmanagedType.LPStr)] 字符串格式,VariableArgumentBuffer arglist);
    
  3. 用于调用

    callback("Hello %d %s", VariableArgumentBuffer.Create(2019, Marshal.StringToHGlobalAnsi("圣诞快乐")));
    
  4. 用于实现

    public static int MyPrintFormat(字符串格式,VariableArgumentBuffer arglist)
    {
        var Stream = new UnmanagedMemoryStream(arglist.Buffer, VariableArgumentBuffer.BufferLength);
        var binary = new BinaryReader(stream);
    
        ....
    }
    
    • 您必须解析format才能知道推入堆栈的内容,然后使用binary读取参数。例如,如果您知道已推送 int32,则可以使用 binary.ReadInt32() 读取它。如果您不明白这部分内容,请在评论中告诉我,以便我为您提供更多信息。

I disagree with @shf301, It's possible.

You can use __arglist in case of PInvoke, like this:

[DllImport("msvcrt", CallingConvention = CallingConvention.Cdecl, EntryPoint = "printf")]
public static extern int PrintFormat([MarshalAs(UnmanagedType.LPStr)] string format, __arglist);

Calling: PrintFormat("Hello %d", __arglist(2019));

In the case of delegates and callbacks:

  1. Define the following struct:

    public unsafe struct VariableArgumentBuffer
    {
        public const int BufferLength = 64; // you can increase it if needed
    
        public fixed byte Buffer[BufferLength];
    
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static VariableArgumentBuffer Create(params object[] args)
        {
            VariableArgumentBuffer buffer = new VariableArgumentBuffer();
            Write(ref buffer, args);
            return buffer;
        }
    
        public static void Write(ref VariableArgumentBuffer buffer, params object[] args)
        {
            if (args == null)
            return;
    
            fixed (byte* ptr = buffer.Buffer)
            {
                int offset = 0;
    
                for (int i = 0; i < args.Length; i++)
                {
                    var arg = args[i];
    
                    if (offset + Marshal.SizeOf(arg) > BufferLength)
                        throw new ArgumentOutOfRangeException();
    
                    switch (arg)
                    {
                    case byte value:
                         *(ptr + offset++) = value;
                         break;
    
                    case short value:
                         *(short*)(ptr + offset) = value;
                         offset += sizeof(short);
                         break;
    
                    case int value:
                        *(int*)(ptr + offset) = value;
                        offset += sizeof(int);
                        break;
    
                    case long value:
                        *(long*)(ptr + offset) = value;
                        offset += sizeof(long);
                        break;
    
                    case IntPtr value:
                        *(IntPtr*)(ptr + offset) = value;
                        offset += IntPtr.Size;
                        break;
    
                    default: // TODO: Add more types
                        throw new NotImplementedException();
                  }
              }
           }
        }
     }
    
  2. Define your delegate

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    public delegate int PrintFormatDelegate([MarshalAs(UnmanagedType.LPStr)] string format, VariableArgumentBuffer arglist);
    
  3. For calling

    callback("Hello %d %s", VariableArgumentBuffer.Create(2019, Marshal.StringToHGlobalAnsi("Merry christmas")));
    
  4. For implementing

    public static int MyPrintFormat(string format, VariableArgumentBuffer arglist)
    {
        var stream = new UnmanagedMemoryStream(arglist.Buffer, VariableArgumentBuffer.BufferLength);
        var binary = new BinaryReader(stream);
    
        ....
    }
    
    • You have to parse format to know what is pushed into the stack, and then read arguments using binary. For example, if you know an int32 is pushed, you can read it using binary.ReadInt32(). If you don't understand this part, please tell me in comments so I can provide you more info.
唐婉 2024-11-30 16:35:06

实际上在CIL中是可以的:

.class public auto ansi sealed MSIL.TestDelegate
       extends [mscorlib]System.MulticastDelegate
{
    .method public hidebysig specialname rtspecialname 
            instance void  .ctor(object 'object',
                                 native int 'method') runtime managed
    {
    }
    .method public hidebysig newslot virtual 
            instance vararg void  Invoke() runtime managed
    {
    }
}

Actually it is possible in CIL:

.class public auto ansi sealed MSIL.TestDelegate
       extends [mscorlib]System.MulticastDelegate
{
    .method public hidebysig specialname rtspecialname 
            instance void  .ctor(object 'object',
                                 native int 'method') runtime managed
    {
    }
    .method public hidebysig newslot virtual 
            instance vararg void  Invoke() runtime managed
    {
    }
}
沧笙踏歌 2024-11-30 16:35:06

以下文章介绍了略有不同的方案,可能会有所帮助:

如何在中 P/Invoke VarArgs(变量参数) C#

The following article covers a slightly different scenario and may be helpful:

How to P/Invoke VarArgs (variable arguments) in C#

薄凉少年不暖心 2024-11-30 16:35:06

您需要 P/invoke 编组器的支持才能实现这一点。编组器不提供此类支持。因此这是不可能完成的。

You'd need support from the P/invoke marshaller for this to be possible. The marshaller does not provide such support. Thus it cannot be done.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文