Как вызвать неуправляемую функцию c ++, которая выделяет выходной буфер для возврата данных в c #? - PullRequest
3 голосов
/ 05 февраля 2010

У меня проблемы с сортировкой выходного параметра функции c ++, возвращающей массив данных в c #.

Вот объявление C ++:

#define DLL_API __declspec(dllexport)

typedef TPARAMETER_DATA
{
    char        *parameter;
    int     size;
} PARAMETER_DATA;

int DLL_API GetParameters(PARAMETER_DATA *outputData);

Функция выделяет память для массива char, помещает туда данные и возвращает количество выделенных байтов в поле «size». Вот моя декларация c #:

[StructLayout(LayoutKind.Sequential)]
public struct PARAMETER_DATA
{        
    [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.I1, SizeConst = 50000)]        
    public byte[] data;   // tried also SizeParamIndex = 1 instead of SizeConst

    [MarshalAs(UnmanagedType.I4)]                      
    public int size;
}

[DllImport("thedll.dll", SetLastError = true, ExactSpelling = true)]
public extern static uint GetParameters(ref PARAMETER_DATA outputData); // tried also  'out' parameter

При вызове функции в c # я получаю пустую структуру (размер = 0, пустой массив). Я попытался передать параметр outputData с полем данных, инициализированным в новый байт [50000], но в любом случае данные не возвращались.

Все остальные функции в этой dll (некоторые со сложной структурой ввода) работают нормально, но это единственная функция, которая выделяет память для возврата данных. Я безуспешно пробовал много других объявлений C # (с LPArray, LPString) - всегда возвращается пустая структура данных или возникает исключение доступа к памяти. Я что-то упускаю здесь?

EDIT:

Я не могу изменить код c ++ - это внешняя библиотека.

Ответы [ 4 ]

1 голос
/ 05 февраля 2010

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

Решением может быть обработка указателя в c #. Вы также должны выяснить, несете ли вы ответственность за освобождение указателя или библиотека сделает это за вас.

[StructLayout(LayoutKind.Sequential)]
public struct PARAMETER_DATA
{        
    public IntPtr data;   // tried also SizeParamIndex = 1 instead of SizeConst

    [MarshalAs(UnmanagedType.I4)]                      
    public int size;
}

[DllImport("thedll.dll", SetLastError = true, ExactSpelling = true)]
private extern static uint GetParameters(ref PARAMETER_DATA outputData);

public static uint GetParameters(out String result)
{
    PARAMETER_DATA outputData = new PARAMETER_DATA();
    result= Marshal.PtrToStringAnsi(outputData.data, outputData.size );
    Marshal.FreeHGlobal(outputData.data); // not sure about this
}
0 голосов
/ 15 февраля 2014

    For you reference, I use LayoutKind.Explicit and FieldOffset, and ignore ArraySubType in my project.

    [StructLayout(LayoutKind.Explicit, CharSet = CharSet.Ansi, Size=50004)]
    public struct PARAMETER_DATA
    {
        [FieldOffset(0)]
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 50000)]
        public byte[] data;

        [FieldOffset(50000)]
        [MarshalAs(UnmanagedType.I4)]
        public int size;
    }

0 голосов
/ 05 февраля 2010

Это одна из тех вещей, которые вы пробовали? LPArray и SizeParamIndex = 1

[StructLayout (LayoutKind.Sequential)]
public struct PARAMETER_DATA
{
  [MarshalAs (UnmanagedType.LPArray, ArraySubType = UnmanagedType.I1, SizeParamIndex = 1)]
  public byte [] data; 

  [MarshalAs (UnmanagedType.I4)]
  public int size;
}
0 голосов
/ 05 февраля 2010

Ваши объявления маршалинга для структуры C # не соответствуют вашей структуре C ++, они соответствуют этой структуре c ++

typedef TPARAMETER_DATA
{
    char    parameter[50000];
    int     size;
} PARAMETER_DATA;

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

Я уверен, что есть и другие способы сделать это, но один из способов, который я получил, - это использовать SAFEARRAY. SAFEARRAY является частью стандартного COM API, созданного изначально для взаимодействия с VB (я думаю). http://msdn.microsoft.com/en-us/library/ms221145.aspx

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

Итак, ваша новая структура параметров такова (я не думаю, что размер нужен, SAFEARRAY уже знает)

typedef TPARAMETER_DATA
{
    SAFEARRAY * parameter;
    int         size; // I think this is redundant.
}

В вашем коде C ++ вы выделяете массив, используя

SAFEARRAY * psa = SafeArrayCreateVector(VT_UI1, 0, 50000);
if ( ! psa)
    return E_OUTOFMEMORY;

HRESULT hr = SafeArrayLock(psa);
if (FAILED(hr))
{
    SafeArrayDestroy(psa);
    return hr;
}

CopyMemory(psa->pvData, mydataptr, 50000);
SafeArrayUnlock(psa);

PARAMETER_DATA pda = {psa, 50000};

Тогда объявление C # для структуры будет

[StructLayout(LayoutKind.Sequential)]
public struct PARAMETER_DATA
{        
    [MarshalAs(UnmanagedType.SafeArray)]        
    public byte[] data; // I used System.Array here, but I think byte[] is OK

    [MarshalAs(UnmanagedType.I4)]                      
    public int size;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...