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

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

Файлът .h работи

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

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

Мога да извикам функции в dll, който използва маршалирани константни типове като прости типове низове, int и т.н. Но не мога да разбера как просто да маршалирам указател към C# обект, който е състоянието.

За да предам препратката към обекта към функцията 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 получавам грешка:

Структурата не трябва да бъде стойностен клас.

Търсих много тук и намерих различни неща за Marshall и void*, но нищо не ми помогна да накарам това да работи

Благодаря.


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
В този случай е вярно, наистина не трябваше да предавам това на данните, тъй като ще бъде налично в обратното извикване на моя обект, но исках да осигуря правилното извикване на функцията, когато потребителят иска да предостави контекст. Предполагам, че управлението на данните може да се управлява от C# обекта, за да се поддържа състояние/контекст. Така че предполагам, че това решава проблема ми, но предполагам, че първоначалният въпрос как да или е възможно да се премине обектът остава без отговор - person Andre; 12.04.2012
comment
Според мен наистина не искате да предавате препратка към C# обект. Не забравяйте, че управляваните обекти могат да се преместват от 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