C# P / Invoke: обратный вызов делегата Varargs

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


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

это определение работает, но я получаю строки типа "Format %s probed with size=%d and score=%d". Я попытался добавить ключевое слово __arglist, но оно не разрешено для делегатов.

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

редактировать: для тех, кто еще не понял: я ИМПОРТ функция varargs но экспорт его в качестве обратного вызова, который затем называется моим родным кодом. Я могу указать только один за раз -> возможна только одна перегрузка и __arglist не работает.

6 ответов


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

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

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

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

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


вот способ справиться с этим. Это может быть или не быть применимо к вашему случаю, в зависимости от того, предназначены ли ваши аргументы обратного вызова для использования с 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();

    ...
}

Я столкнулся с этим вопросом перед публикацией Маршал va_list в C# делегат. Я думаю, что это похоже, и я нашел реализацию, которая работает для меня. HTH.


на самом деле это возможно в 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
    {
    }
}

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

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


для этого вам понадобится поддержка маршаллера P/invoke. Маршаллер такой поддержки не оказывает. Поэтому это невозможно.