Как вызвать функцию в C++ Dll из C#, которая имеет обратный вызов void* и объектный параметр

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

Файл .h задерживает

extern int SetErrorHandler(void (*handler) (int, const char*, void*),
                              void *data_ptr);

Обработчик — это функция обратного вызова, которая вызывается при возникновении ошибки, а data_ptr — это любой объект (состояние), который возвращается вам, в случае моего приложения это будет просто этот (текущий объект).

Я могу вызывать функции в dll, которые используют упорядоченные постоянные типы, такие как строки простых типов, целые числа и т. д. Но я не могу понять, как просто маршалировать указатель на объект С#, который является состоянием.

Чтобы передать ссылку на объект в функцию C из того, что я нашел, выполнив поиск здесь, и в противном случае кажется, что мне нужен тип структуры, чтобы иметь возможность маршалировать в функцию, поэтому я создал структуру для хранения моего объекта:

[StructLayout(LayoutKind.Sequential)]
struct MyObjectState
{
   public object state;
}

РЕДАКТИРОВАТЬ: я попытался поместить атрибут: [MarshalAs(UnmanagedType.Struct, SizeConst = 4)] в свойство public object state, но это приводит к той же ошибке, поэтому я удалил его, похоже, он все равно не сработает.

Структура содержит одно свойство объекта для хранения любого объекта для функции обратного вызова.

Я объявил делегата в C# следующим образом:

delegate void ErrorHandler(int errorCode, IntPtr name, IntPtr data);

Затем я объявил функцию импорта в C# следующим образом:

[DllImport("somedll.dll", CallingConvention = CallingConvention.Cdecl)]
static extern int SetErrorHandler handler, IntPtr data);

Затем я создал функцию обратного вызова в своем коде C#:

void MyErrorHandler(int errorCode, IntPtr name, IntPtr data)
{
    var strName = Marshal.PtrToStringAnsi(name);
    var state = new MyObjectState();
    Marshal.PtrToStructure(data, state);
    Console.WriteLine(strName);
}

Я могу вызвать библиотечную функцию следующим образом:

var state = new MyObjectState()
{
    state = this
};
IntPtr pStruct = Marshal.AllocHGlobal(Marshal.SizeOf(state));
Marshal.StructureToPtr(state, pStruct, true);
int ret = SetErrorHandler(MyErrorHandler, pStruct);

Вызов работает, и функция обратного вызова вызывается, но я не могу получить доступ к данным в функции обратного вызова, и когда я пытаюсь Marshal.PtrToStructure, я получаю сообщение об ошибке:

Структура не должна быть классом значений.

Я много искал здесь и нашел разные вещи о Маршалле и пустоте *, но ничто не помогло мне заставить это работать.

Спасибо.


person Andre    schedule 12.04.2012    source источник


Ответы (2)


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

Таким образом, вы можете просто передать IntPtr.Zero делегату. Внутри вашего делегата обработчика ошибок вы просто игнорируете значение data_ptr, так как this будет доступно.

Если вы не следуете этому описанию, вот короткая программа, чтобы проиллюстрировать, что я имею в виду. Обратите внимание, что MyErrorHandler — это метод экземпляра, который действует как обработчик ошибок и может изменять данные экземпляра.

class Wrapper
{
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    delegate void ErrorHandler(int errorCode, string name, IntPtr data);

    [DllImport("somedll.dll", CallingConvention = CallingConvention.Cdecl)]
    static extern int SetErrorHandler(ErrorHandler handler, IntPtr data);

    void MyErrorHandler(int errorCode, string name, IntPtr data)
    {
        lastError = errorCode;
        lastErrorName = name;
    }

    public Wrapper()
    {
        SetErrorHandler(MyErrorHandler, IntPtr.Zero);
    }            

    public int lastError { get; set; }
    public string lastErrorName { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        Wrapper wrapper = new Wrapper();
    }
}
person David Heffernan    schedule 12.04.2012
comment
В этом случае это правда, действительно, мне действительно не нужно было передавать это данным, поскольку они будут доступны в обратном вызове моего объекта, но я хотел обеспечить правильный вызов функции, когда пользователь хочет предоставить контекст. Я предполагаю, что управление данными может обрабатываться объектом С# для поддержания состояния/контекста. Итак, я думаю, это решает мою проблему, но я думаю, что первоначальный вопрос о том, как или можно ли передать объект, остается без ответа. - person Andre; 12.04.2012
comment
На мой взгляд, вы действительно не хотите передавать ссылку на объект С#. Помните, что управляемые объекты можно перемещать с помощью GC. Вы собираетесь принудительно закреплять такие объекты? И что нативный код может сделать с такой штукой? Он совершенно непрозрачен. Вы можете использовать ObjectIDGenerator, чтобы получить уникальный идентификатор для каждого объекта, но лично я думаю, что ваш вопрос лучше всего решить, обойдя его! - person David Heffernan; 12.04.2012
comment
То, что вы говорите, имеет смысл, и я думаю, что ответ: не делайте этого / избегайте этого, это может быть не невозможно, но вы не должны, спасибо. - person Andre; 12.04.2012

Возможно, есть способ сделать это, но я давно сдался. Решение, которое я придумал, немного хакерское, но оно очень эффективное и работает со всем, что я ему предложил:

C# -> Управляемый C++ -> Собственные вызовы

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

Честно говоря, хотя я и надеюсь, что кто-то даст неуклонный ответ, я сам довольно долго боролся с этим.

person Chris Eberle    schedule 12.04.2012
comment
Спасибо, Крис, это полезно, но на данный момент у меня нет инструментов или знаний для написания библиотеки Managed C++, возможно, я смогу понять это когда-нибудь в будущем. Я не думаю, что вы можете предоставить ссылку на общий документ с документацией, как его использовать? Наконец, если все это не работает для меня, решение состоит в том, чтобы убедиться, что любые данные, которые мне нужны в обратном вызове, должны быть добавлены в структуру и упорядочены как простые значения? Спасибо - person Andre; 12.04.2012
comment
PS: у меня также есть дополнительная проблема с моим обратным вызовом, он работает нормально, но даже если я удалю весь код в теле функции обратного вызова, в конце функции я получаю нарушение прав доступа (попытка чтения или записи защищенной памяти) - person Andre; 12.04.2012
comment
На самом деле я только что понял: мне не хватало атрибута [UnmanagedFunctionPointer(CallingConvention.Cdecl)] для моего делегата: stackoverflow.com/questions/4906931/ - person Andre; 12.04.2012