2012-04-28 4 views
4

Я пытаюсь сделать эту работу с C#:Маршал va_list в C# делегат

C заголовок:

typedef void (LogFunc) (const char *format, va_list args); 

bool Init(uint32 version, LogFunc *log) 

C# реализация:

static class NativeMethods 
{ 
    [DllImport("My.dll", SetLastError = true)] 
    internal static extern bool Init(uint version, LogFunc log); 

    [UnmanagedFunctionPointer(CallingConvention.Cdecl, SetLastError = true)] 
    internal delegate void LogFunc(string format, string[] args); 
} 

class Program 
{ 
    public static void Main(string[] args) 
    { 
     NativeMethods.Init(5, LogMessage); 
     Console.ReadLine(); 
    } 

    private static void LogMessage(string format, string[] args) 
    { 
     Console.WriteLine("Format: {0}, args: {1}", format, DisplayArgs(args)); 
    } 
} 

Что здесь происходит, что вызов до NativeMethods.Init переадресовывает LogMessage и передает данные из неуправляемого кода в качестве параметров. Это работает в большинстве случаев, когда аргументы являются строками. Тем не менее, есть звонок, по которому есть формат:

Загруженный плагин% s для версии% d.

и args содержит только строку (имя плагина). Они не содержат значения версии, что имеет смысл, так как я использовал string[] в объявлении делегата. Вопрос в том, как мне написать делегат, чтобы получить как строку, так и int?

Я попытался с помощью object[] args и получил это исключение: недопустимый ВАРИАНТ был обнаружен во время перехода от неуправляемого VARIANT к управляемому объекту. Передача недействительных VARIANT в среду CLR может вызвать неожиданные исключения, повреждение или потерю данных.

EDIT: Я мог бы изменить делегат подпись к этому:

internal delegate void LogFunc(string format, IntPtr args); 

я мог разобрать формат и выяснить, сколько аргументов ожидать и какого типа. Например. для Загруженный плагин% s для версии% d. Я ожидал бы строку и int. Есть ли способ получить эти 2 из этого IntPtr?

+0

Маршалинг аргументов является лишь частью проблемы, вы можете правильно форматировать строку, вызывая vsprintf(). Вам нужно будет написать небольшой адаптер на языке C++/CLI. –

ответ

1

Я понимаю, есть также "__arglist" ключевое слово доступны в C#:

+3

это, но это не работает с делегатами. Я попытался использовать его следующим образом: 'internal delegate void LogFunc (строковый формат, __arglist);' – Suiden

3

Только в случае, если это поможет кому-то, вот решение для сортировочной аргументы. Делегат объявлен как:

[UnmanagedFunctionPointer(CallingConvention.Cdecl, SetLastError = true)] // Cdecl is a must 
internal delegate void LogFunc(string format, IntPtr argsAddress); 

argsAddress является неуправляемым адрес памяти, где начинается массив (я думаю). Размер format дает размер массива. Зная это, я могу создать управляемый массив и заполнить его. Псевдокод:

size <- get size from format 
if size = 0 then return 

array <- new IntPtr[size] 
Marshal.Copy(argsAddress, array, 0, size); 
args <- new string[size] 

for i = 0 to size-1 do 
    placeholder <- get the i-th placeholder from format // e.g. "%s" 
    switch (placeholder) 
     case "%s": args[i] <- Marshal.PtrToStringAnsi(array[i]) 
     case "%d": args[i] <- array[i].ToString() // i can't explain why the array contains the value, but it does 
     default: throw exception("todo: handle {placeholder}") 

Честно говоря, я не уверен, как это работает. Похоже, что это дает правильные данные. Я не утверждаю, что это правильно.

+0

Большое вам спасибо, я пытался сделать то же самое, используя тот же неуправляемый API (я догадался, из чего выглядят ваши сообщения как). – mbarthelemy

0

. NET может (в некоторой степени) маршал между va_list и ArgIterator. Вы можете попробовать это:

[UnmanagedFunctionPointer(CallingConvention.Cdecl, SetLastError = true)] 
internal delegate void LogFunc(string format, ArgIterator args); 

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

0

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

См https://github.com/jeremyVignelles/va-list-interop-demo

Основная идея:

Вы объявить обратный вызов делегат:

[UnmanagedFunctionPointer(CallingConvention.Cdecl)] 
internal delegate void LogFunc(string format, IntPtr args); 

Вы передаете свой обратный вызов, как вы делали:

NativeMethods.Init(5, LogMessage); 

В обратном вызове , вы обрабатываете конкретные случаи различных платформ. Вам нужно понять, как это работает на каждой платформе. Из моего тестирования и понимания вы можете передать IntPtr as-is в семейство функций vprintf * в Windows (x86, x64) и Linux x86, но в Linux x64 вам нужно будет скопировать структуру для работы.

См. Мою демонстрацию для получения дополнительных пояснений.

Смежные вопросы