Маршал va_list в C # делегат

Я пытаюсь сделать эту работу из C #:

C заголовок:

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

bool Init(uint32 version, LogFunc *log)
</code>

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

<code>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));
    }
}
</code>

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

Loaded plugin %s for version %d.

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

Я пытался с помощьюobject[] args и получил это исключение: An invalid VARIANT was detected during a conversion from an unmanaged VARIANT to a managed object. Passing invalid VARIANTs to the CLR can cause unexpected exceptions, corruption or data loss.

РЕДАКТИРОВАТЬ: Я мог бы изменить подпись делегата на это:

<code>internal delegate void LogFunc(string format, IntPtr args);
</code>

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

Ответы на вопрос(4)

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

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

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

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

http://www.dotnetinterop.com/faq/?q=Vararg

http://bartdesmet.net/blogs/bart/archive/2006/09/28/4473.aspx

Другой подход - передать 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 как есть семейству функций vprintf * в Windows (x86, x64) и Linux x86, но в Linux x64 вам потребуется скопировать структуру, чтобы это работало.

Смотрите мое демо для получения дополнительных объяснений.

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

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

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

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}")

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

ВАШ ОТВЕТ НА ВОПРОС