2015-08-07 2 views
3

Извините, если есть дубликат - я изо всех сил пытался найти ответ (я нашел несколько вопросов о функциях C++, которые используют обратные вызовы функций, и некоторые ответы, которые используют классы как обратные вызовы при вызове с C/C++, но ..C#/C++ Класс обратного вызова (не функция) Interop - howto?

  • Я в C#.
  • я называю C функция ++
  • Я не могу изменить сигнатуру функции C++.
  • Кроме того, я использую динамический метод р/вызова, а то статическая привязка (мой пример ниже);

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

Следуя некоторым идеям в Интернете, я попытался создать класс с той же подписью, а затем прикрепить этот класс и передать его., Но я получаю ошибку C#: «Object is not-Blittable» (который он не делает В нем нет переменных!).

Заголовочный файл:

снова извинения, если есть какие-либо ошибки в моем примере, я пытался раздеть все не соответствующий код и взрываются макросы, но я надеюсь, что вы понимаете суть того, что происходит

struct someData_t 
    { 
    int length; /**< JSON data length */ 
    char* pData; /*< JSON data */ 
    }; 

    namespace FOO { 
    class ICallback 
    { 
    public: virtual ~ICallback() {} 

     virtual void Callback(const someData_t &response) = 0; 
    }; 
    } 

    extern "C" __declspec(dllexport) void process(const someData_t *inData, FOO::ICallback *listener); 

Мой C# файл:

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Threading.Tasks; 
using System.Runtime.InteropServices; 

namespace Scratchpad { 
    class Program { 
    static void Main(string[] args) { 
     Console.Out.WriteLine("I'm in Managed C#..."); 

     IntPtr user32 = NativeMethods.LoadLibrary(@"somelongpath\my_c.dll"); 

     IntPtr pAddressOfFunctionToCall = NativeMethods.GetProcAddress(user32, "process"); 

     process proc = (process)Marshal.GetDelegateForFunctionPointer(pAddressOfFunctionToCall, typeof(process)); 

     String someJson = "{ \"type\":\"someTestJson\"}"; 
     byte[] rawdata = Encoding.UTF8.GetBytes(someJson); 

     someData myData = new someData(); 
     int dataLength = rawdata.Length * Marshal.SizeOf(typeof(byte)); // I realise byte is size 1 but.. 

     myData.length = rawdata.Length; 

     myData.pData = Marshal.AllocHGlobal(dataLength); 
     Marshal.Copy(rawdata, 0, myData.pData, dataLength); 

     Console.Out.WriteLine("Size of mydata: " + Marshal.SizeOf(myData)); 
     IntPtr unmanagedADdr = Marshal.AllocHGlobal(Marshal.SizeOf(myData)); 

     Marshal.StructureToPtr(myData, unmanagedADdr, true); 

     // ################################################################ 
     // FIXME: This area still working 
     Callbacker myCallback = new Callbacker(); 

     GCHandle gch = GCHandle.Alloc(myCallback, GCHandleType.Pinned); 

     IntPtr mycallbackPtr = gch.AddrOfPinnedObject(); 

     // FIXME: close of working area. 
     // ################################################################ 
     // CALL THE FUNCTION! 
     proc(unmanagedADdr, mycallbackPtr); 


     myData = (someData) Marshal.PtrToStructure(unmanagedADdr, typeof(someData)); 

     Marshal.FreeHGlobal(unmanagedADdr); 
     Marshal.FreeHGlobal(myData.pData); 
     gch.Free(); 
     unmanagedADdr = IntPtr.Zero; 

     bool result = NativeMethods.FreeLibrary(user32); 

     Console.Out.WriteLine("Fini!)"); 
    } 

    private delegate void process(IntPtr data, IntPtr callback); 

    [StructLayout(LayoutKind.Sequential)] 
    private struct someData { 
     public int length; 
     public IntPtr pData; 
    } 

    private class Callbacker { 
     public void Callback(someData response) { 
     Console.WriteLine("callback Worked!!!"); 
     } 
    } 

    } 

    static class NativeMethods { 
    [DllImport("kernel32.dll")] 
    public static extern IntPtr LoadLibrary(string dllToLoad); 

    [DllImport("kernel32.dll")] 
    public static extern IntPtr GetProcAddress(IntPtr hModule, string procedureName); 

    [DllImport("kernel32.dll")] 
    public static extern bool FreeLibrary(IntPtr hModule); 
    } 
} 

Любые предложения приветствуются

+0

Вы не можете передать указатель на управляемый класс здесь, потому что ваш управляемый класс Callbacker не реализует интерфейс FOO :: ICallback – AccessViolation

ответ

2

Вы можете создать некоторую неуправляемую оболочку для вашего управляемого Callbacker класса, который реализует интерфейс ICallback. Что-то вроде этого:

typedef void (*PointerToManagedFunctionToInvoke)(const someData_t&); 

class UnmanagedDelegate : public FOO::ICallback { 
    private: 
     PointerToManagedFunctionToInvoke managedCallback; 
    public: 
     UnmanagedDelegate(PointerToManagedFunctionToInvoke inManagedCallback) 
     : managedCallback(inManagedCallback) {} 

     virtual void Callback(const someData_t &response) 
     { 
      managedCallback(response); 
     } 
}; 

// Export this to managed part 
UnmanagedDelegate* CreateUnmanagedDelegate(PointerToManagedFunctionToInvoke inManagedCallback) 
{ 
    return new UnmanagedDelegate(inManagedCallback); 
} 

Тогда на C# части Вы можете создать делегат маршала в качестве PointerToManagedFunctionToInvoke, передать его CreateUnmanagedDelegate получить неуправляемую реализацию ICallback и использовать, чтобы перейти к вашему process

Обратите внимание что managedCallback должен оставаться выделенным на стороне C#, тогда как объект класса UnmanagedDelegate жив. И вы должны удалить объект UnmanagedDelegate, когда он больше не используется.

ИЛИ Вы можете использовать тонкий C++/CLI для реализации этой оболочки.

+0

Большое спасибо! Мне потребовалось некоторое время, чтобы проработать и реализовать его - то, что я сделал, было сделано третьей библиотекой, которую я связал прямо с проектом C# без прохождения pInvoke. Когда я скомпилировал ваш код, хотя он возражал против нехватки точки с запятой в конце класса (я не лучший программист на C++, но это правда!?) Я подозреваю, что теперь у меня есть эта оболочка слой в CI может переопределить, как мои структуры должны включаться из общего файла .h ... но он работает. Спасибо огромное! – Jmons

+1

Да, должна быть точка с запятой после объявления класса - мой плохой - не скомпилировал код для проверки синтаксических ошибок. Добро пожаловать, НО - пожалуйста, ОЧЕНЬ ОСТОРОЖНО с управлением памятью - одна простая ошибка может привести к утечкам памяти и ошибкам. Это не обычный C# сборщик мусора. – AccessViolation

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