C # Marshalling двойной * из C ++ DLL? - PullRequest
       26

C # Marshalling двойной * из C ++ DLL?

12 голосов
/ 22 февраля 2011

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

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 ]

11 голосов
/ 22 февраля 2011

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

  1. Код C ++ не знает, насколько велики эти массивы. Маршаллер передаст им действительный указатель, но без соответствующего параметра длины невозможно определить, насколько они велики. sizeof (dataReal) и sizeof (dataImag) скорее всего 4 или 8 на большинстве платформ (то есть sizeof (void *)). Вероятно, не то, что вы хотели.
  2. Несмотря на то, что можно возвращать указатель обратно в качестве возвращаемого значения (вы можете затем использовать его для заполнения управляемого массива), подразумеваемый владелец памяти возвращаемого значения не существует, оставляя открытой возможность утечек памяти. Был ли выделен буфер внутри 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 голосов
/ 22 февраля 2011

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

[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.

РЕДАКТИРОВАТЬ: Я думаю, вы найдете P/Invoke Interop Assistant инструмент полезным: Управляемый, родной и COM взаимодействия

1 голос
/ 22 февраля 2011

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

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

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

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

...