C# маршаллинг double* из библиотеки DLL C++?

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

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

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

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

[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 исключение:

не удается маршалировать "возвращаемое значение": недопустимая комбинация управляемого/неуправляемого типа.

Я предполагаю, что это потому, что C++ double* не совсем то же самое, что C# 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.

3 ответов


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

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

вместо этого, мое предложение было бы определить БПФ таким образом:


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);

Edit: обновление на основе того, что описывается fft.


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

[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 будет содержать байты, возвращенные


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

  1. изменить прототип функции C++ и вернуть все удвоения через параметры (надеюсь, у вас есть источник c++ func)
  2. используйте IntPtr, как описано в REST Wing

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

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