Подтвердить что ты не робот

С# Marshalling double * из С++ DLL?

У меня есть С++ DLL с экспортированной функцией:

extern "C" __declspec(dllexport) double* fft(double* dataReal, double* dataImag)
{
  [...]
}

Функция вычисляет БПФ двух двойных массивов (вещественных и мнимых), возвращает один двойной массив с вещественными мнимыми компонентами, чередующимися: {Re, Im, Re, Im,...}

Я не уверен, как вызвать эту функцию в С#. Что я делаю:

[DllImport("fft.dll")]
static extern double[] fft(double[] dataReal, double[] dataImag);

и когда я проверяю его так:

double[] foo = fft(new double[] { 1, 2, 3, 4 }, new double[] { 0, 0, 0, 0 });

Я получаю исключение MarshalDirectiveException:

Невозможно вывести "возвращаемое значение": неверная комбинация управляемых/неуправляемых типов.

Я предполагаю, что это потому, что С++ double* не совсем то же самое, что С# double[], но я не уверен, как его исправить. Любые идеи?

Изменить: Я изменил подписи, поэтому теперь передаю дополнительную информацию:

extern "C" __declspec(dllexport) void fft(double* dataReal, double* dataImag, int length, double* output);

Мы всегда знаем, что длина output будет 2x length

и

[DllImport("fft.dll")]
static extern void fft(double[] dataReal, double[] dataImag, int length, out double[] output);

протестирован следующим образом:

double[] foo = new double[8];
fft(new double[] { 1, 2, 3, 4 }, new double[] { 0, 0, 0, 0 }, 4, out foo);

Теперь я получаю исключение AccessViolationException, а не исключение MarshalDirectiveException.

4b9b3361

Ответ 1

В вашем примере есть несколько проблем:

  • Код С++ не знает, насколько велики эти массивы. Маршаллер передаст им действительный указатель, но без соответствующего параметра длины, нет способа рассказать, насколько они велики. sizeof (dataReal) и sizeof (dataImag), вероятно, 4 или 8 на большинстве платформ (т.е. sizeof (void *)). Наверное, не то, что вы намеревались.
  • В то время как возможно вернуть маркеру указатель назад в качестве возвращаемого значения (вы могли бы использовать его для заполнения управляемого массива), не существует подразумеваемого владельца памяти возвращаемого значения, оставляя открытым возможность утечки памяти. Был ли буфер, выделенный внутри fft новым? Если это так, тогда вам понадобится другой экспорт, который управляемый код может вызвать для освобождения памяти или вместо него использовать LocalAlloc (а затем Marshal.FreeHGlobal на управляемой стороне). В лучшем случае это проблема.

Вместо этого мое предложение было бы определить fft таким образом:


extern "C" __declspec(dllexport) void __stdcall fft(double const* dataReal, int dataRealLength, double const* dataImag, int dataImagLength, double* result, int resultLength)
{
  // Check that dataRealLength == dataImagLength
  // Check that resultLength is twice dataRealLength
}

Соответствующая подпись P/Invoke будет следующей:


[DllImport("fft.dll")]
static extern void fft(double[] dataReal, int dataRealLength, double[] dataImag, int dataImagLength, double[] result, int resultLength);

И затем пример вызова:


double[] dataReal = new double[] { 1.0, 2.0, 3.0, 4.0 };
double[] dataImag = new double[] { 5.0, 6.0, 7.0, 8.0 };
double[] result = new double[8];
fft(dataReal, dataReal.Length, dataImag, dataImag.Length, result, result.Length);

Изменить: обновление, основанное на том, что описано fft.

Ответ 2

Нет необходимости менять подпись. Вы можете использовать следующее:

[DllImport( "fft.dll", EntryPoint = "fft" )]
public static extern IntPtr fft( double[] dataReal, double[] dataImag );

Затем вам нужно будет скопировать байты из возвращенного IntPtr. Поскольку вы знаете, что размер вывода равен удвоенному входу, вы делаете это следующим образом:

double[] result = new double[ doubleSize ];
Marshal.Copy( pointer, result, 0, doubleSize );

result будет содержать байты, возвращаемые функцией fft.

EDIT: Я считаю, что вы найдете инструмент P/Invoke Interop Assistant полезным: Managed, Native и COM Interop

Ответ 3

Это не хороший прототип для взаимодействия. Вы можете решить два способа:

  • Измените прототип функции С++ и верните все дубликаты через параметры (надеюсь, у вас есть источник С++ func)
  • Используйте IntPtr, как описано Rest Wing

Ссылка на возвращаемую память будет работать правильно только в том случае, если память была выделена функцией CoTaskMemAlloc. В противном случае вы получите исключение во время выполнения во время освобождения памяти (CLR всегда пытается освободить память возврата с помощью CoTaskMemFree).

Я думаю, что лучшим был бы первый способ, описанный в ответе Питера Хуэне.