Оборачивание функции c ++ в c # с полным динамическим размером структуры - PullRequest
0 голосов
/ 21 июня 2019

Я борюсь с переносом функции C ++ на C #.У меня есть базовые знания об этом виде упаковки, но здесь я пытаюсь найти «лучшее решение».

Допустим, у меня есть только .dll, которая содержит функцию C ++.Я только знаю, что есть функция с этой подписью:

static void GetInfos(LibraryInfo& infos);

И, конечно, я знаю, что есть класс LibraryInfos

class LIBRARY_EXPORT_CLASS LibraryInfo
{
    public:
        const char* libVersion;
        const char* libName;
        const char* libDate; 
    };
};

Теперь я пытаюсь использовать эту функцию в тесте C #project:

static void Main(string[] args)
{
     // Create Pointer
     IntPtr ptr;

     // Call function
     GetInfos(out ptr);

     // Get the 100 first byte (used only to demonstrate)
     byte[] buffer = new byte[100];
     Marshal.Copy(ptr, buffer, 0, 100);

     // Display memory content
     Console.WriteLine(Encoding.ASCII.GetString(buffer));

     Console.ReadLine();
}

[DllImportAttribute("MyLibrary.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "GetInfos")]
private static extern void GetInfos(out IntPtr test);

Этот код выводит меня в качестве вывода

v.1.2.5                                                                   ?I9               
  • Во-первых: я знаю, что это действительно плохой способ сделать это, распределяя произвольную длину как байт[] здесь только для демонстрации.
  • Второе: у меня есть только версия, но если я вызываю ту же самую DLL из проекта C ++, у меня есть поле 3 с данными.
  • В-третьих: почему я использовал копию Marshal, и эта произвольная длина 100?Поскольку мне не удалось вызвать PtrToStruct, я попробовал вот что:

    [StructLayout(LayoutKind.Sequential)]
    private struct LibInformation
    {
        public IntPtr Version; // I tried, char[], string, and IntPtr
        public IntPtr Name;
        public IntPtr Date;
    }
    
    static void Main(string[] args)
    {
    // Create Pointer
    IntPtr ptr;
    
    // Call function
    GetInfos(out ptr);
    
    if (ptr != IntPtr.Zero)
    {
         LibInformation infos = (LibInformation)Marshal.PtrToStructure(ptr, typeof(LibInformation));   
    }
    
    Console.ReadLine();
    }
    

    [DllImportAttribute ("MyLibrary.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "GetInfos")] private static externvoid GetInfos (вне теста IntPtr);

Тогда я не могу получить свою версию, имя и дату.

  • Если я использую IntPtr в своей структуре, у меня не будет длины строки, поэтому я не могу реально маршал. Копировать, ни PtrToStringAuto.
  • Если я использую Char []или строка это не работает.

Я думаю, что моя проблема в том, что я не знаю размер окончательного ответа.поэтому мой лучший вариант на данный момент - создать проект C ++, вызывая эту функцию оттуда, затем обернуть эту структуру в лучшую, чтобы я мог ее маршал с другой стороны (с длиной элемента в качестве другого элемента).

Любая мысль?

[РЕДАКТИРОВАТЬ 1 На основе комментария jdweng]

[StructLayout(LayoutKind.Sequential)]
private struct LibInformation
{
    public IntPtr Version; // I tried, char[], string, and IntPtr
    public IntPtr Name;
    public IntPtr Date;
}

static void Main(string[] args)
{
    // Create Pointer
    IntPtr ptr;

    // Call function
    GetInfos(out ptr);

    var data = Marshal.PtrToStructure<LibInformation>(ptr);
    var version = Marshal.PtrToStringAnsi(data.Version);
    Console.WriteLine(version) // result : ""

    // Use Ptr directly as string instead of struct
    var version2 = Marshal.PtrToStringAnsi(ptr);
    Console.WriteLine(version2) // result : "v1.2.5" but how can i access other field ?

    Console.ReadLine();
}


[DllImportAttribute("MyLibrary.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "GetInfos")]
private static extern void GetInfos(out IntPtr test);

[РЕДАКТИРОВАТЬ 2 На основе комментария jdweng 2]

[StructLayout(LayoutKind.Sequential)]
private struct LibInformation
{
    public IntPtr Version;
    public IntPtr Name;
    public IntPtr Date;
}

static void Main(string[] args)
{
    // Create Pointer for my structure
    IntPtr ptr;

    // Create 3 pointers and allocate them
    IntPtr ptrVersion = Marshal.AllocHGlobal(100);
    IntPtr ptrName = Marshal.AllocHGlobal(100);
    IntPtr ptrDate = Marshal.AllocHGlobal(100);

    // Then declare LibInformation and assign
    LibInformation infos = new LibInformation();

    // Here is probably my missunderstanding (an my error)
    // As I need a ptr to call my function I have to get the Ptr of my struct
    IntPtr ptr = Marshal.AllocHGlobal(300);
    Marshal.StructureToPtr(infos, ptr, false);

    // Assign
    infos.Version = ptrVersion;
    infos.Name = ptrName;
    infos.Date = ptrDate;

    // Call function
    GetInfos(out ptr);

    var data = Marshal.PtrToStructure<LibInformation>(ptr);
    var version = Marshal.PtrToStringAnsi(data.Version);
    Console.WriteLine(version) // result : still ""

    Console.ReadLine();
}


[DllImportAttribute("MyLibrary.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "GetInfos")]
private static extern void GetInfos(out IntPtr test);

1 Ответ

0 голосов
/ 21 июня 2019

Я наконец нашел способ сделать это, благодаря объяснению @jdweng и ссылке @ PaulF

Единственная плохая новость в том, что я должен произвольно распределить n байтов перед вызовом своих функций

Ссылка: MSDN: маршалинг-классы-структуры-союзы

Пояснения (jdweng):

Список параметров метода находится в стеке выполнения. После возврата из метода список параметров становится недействительным, поскольку он может быть перезаписан родительским кодом. Только возвращаемое значение метода является допустимым. Таким образом, вы должны выделить все данные перед вызовом метода. Таким образом, вам необходимо выделить переменную версию, имя и дату в неуправляемой памяти. Затем объявите LibInformation и установите версию, имя и дату в выделенных местах памяти. Наконец, вызовите метод. Затем, чтобы получить три переменные, вам нужно вызвать Marshal.PtrToStruct для копирования результатов из неуправляемой памяти.

Финальный код:

[StructLayout(LayoutKind.Sequential)]
private struct LibInformation
{
    public IntPtr Version;
    public IntPtr Name;
    public IntPtr Date;
}

static void Main(string[] args)
{
    // Create 3 pointers and allocate them
    IntPtr ptrVersion = Marshal.AllocHGlobal(100);
    IntPtr ptrName = Marshal.AllocHGlobal(100);
    IntPtr ptrDate = Marshal.AllocHGlobal(100);

    // Then declare LibInformation and assign
    LibInformation infos = new LibInformation();

    // Assign
    infos.Version = ptrVersion;
    infos.Name = ptrName;
    infos.Date = ptrDate;

    // Call function
    GetInfos(out infos);

    var version = Marshal.PtrToStringAnsi(data.Version);
    var name = Marshal.PtrToStringAnsi(data.Name);
    var date = Marshal.PtrToStringAnsi(data.Date);

    Console.ReadLine();
}


[DllImportAttribute("MyLibrary.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "GetInfos")]
private static extern void GetInfos(out LibInformation test); // Changing IntPtr to LibInformation

[Редактировать 1 на основе комментария Дэвида Хеффернана]

Вот код C образца вызова:

HComplexCTXPoint::LibraryInfo versionInfo;
HComplexCTXPoint::GetInfos(versionInfo);
std::cout << versionInfo.libName << std::endl;
...