C # P / Invoke: обратный вызов делегата Varargs - PullRequest
9 голосов
/ 14 июля 2011

Я просто пытался сделать какое-то управляемое / неуправляемое взаимодействие.Чтобы получить расширенную информацию об ошибках, я решил зарегистрировать обратный вызов журнала, предлагаемый dll:


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

Это определение работает, но я получаю строки типа "Формат% s проверен с размером =% d и счет =% d».Я попытался добавить ключевое слово __arglist, но это не разрешено для делегатов.

Что ж, это не так уж драматично для меня, но мне просто любопытно, можно ли получить параметры varargs в C #.Я знаю, что я мог бы использовать c ++ для взаимодействия.Итак: есть ли способ сделать это чисто в C #, с разумной скоростью?

РЕДАКТИРОВАТЬ : Для тех, кто все еще не получил его: я НЕ ИМПОРТИРУЮ функция varargs НО ЭКСПОРТИРОВАТЬ ее как обратный вызов , который затем называется моим собственным кодом.Я могу указать только одну за раз -> возможна только одна перегрузка, и __arglist НЕ работает.

Ответы [ 6 ]

4 голосов
/ 15 июля 2011

Нет, нет никакого способа сделать это.Причина, по которой это невозможно, заключается в том, что списки аргументов переменных работают в C.

В C аргументы переменных просто помещаются в стек как дополнительные параметры (неуправляемый стек в нашем случае).C нигде не записывает количество параметров в стеке, вызываемая функция берет свой последний формальный параметр (последний аргумент перед varargs), получает свое местоположение и начинает выталкивать аргументы из стека.

Способ, которым он знает, сколько переменных извлекается из стека, полностью основан на соглашениях - некоторый другой параметр указывает, сколько аргументов переменных находится в стеке.Для printf это делается путем анализа строки формата и выталкивания из стека каждый раз, когда он видит код формата.Похоже, ваш обратный вызов похож.

Чтобы CLR справился с этим, он должен быть в состоянии знать правильное соглашение, чтобы определить, сколько аргументов необходимо собрать.Вы не можете написать свой собственный обработчик, потому что это потребует доступа к неуправляемому стеку, к которому у вас нет доступа.Таким образом, вы никак не можете сделать это из C #.

Для получения дополнительной информации об этом вам нужно ознакомиться с соглашениями о вызовах C.

2 голосов
/ 04 июня 2016

Вот способ справиться с этим. Это может или не может быть применимо к вашему случаю, в зависимости от того, предназначены ли ваши аргументы обратного вызова для использования с printf семейством функций.

Сначала импортируйте vsprintf и _vscprintf из 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);

Далее объявите свой делегат с указателем 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();

    ...
}
1 голос
/ 11 ноября 2013

На самом деле это возможно в 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
    {
    }
}
0 голосов
/ 27 декабря 2018

Я не согласен с @ shf301, возможно.

Вы можете использовать __arglist в случае PInvoke, например:

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

Вызов: PrintFormat("Hello %d", __arglist(2019));

В случае делегатов и обратных вызовов:

  1. Определите следующую структуру:

    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. Определите своего делегата

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    public delegate int PrintFormatDelegate([MarshalAs(UnmanagedType.LPStr)] string format, VariableArgumentBuffer arglist);
    
  3. Для звонков

    callback("Hello %d %s", VariableArgumentBuffer.Create(2019, Marshal.StringToHGlobalAnsi("Merry christmas")));
    
  4. Для реализации

    public static int MyPrintFormat(string format, VariableArgumentBuffer arglist)
    {
        var stream = new UnmanagedMemoryStream(arglist.Buffer, VariableArgumentBuffer.BufferLength);
        var binary = new BinaryReader(stream);
    
        ....
    }
    
    • Вам нужно проанализировать format, чтобы узнать, что помещается в стек, а затем прочитать аргументы, используя binary. Например, если вы знаете, что int32 выдвинут, вы можете прочитать его, используя binary.ReadInt32(). Если вы не понимаете эту часть, пожалуйста, сообщите мне в комментариях, чтобы я мог предоставить вам больше информации.
0 голосов
/ 15 июля 2011

Вам понадобится поддержка маршаллера P / invoke, чтобы это было возможно. Маршаллер не оказывает такой поддержки. Таким образом, это не может быть сделано.

0 голосов
/ 14 июля 2011

Следующая статья описывает немного другой сценарий и может быть полезна:

Как P / вызвать VarArgs (переменные аргументы) в C #

...