У меня есть функция C++ (WinAPI) со следующей подписью:
__declspec(dllimport) LPDWORD WINAPI MyFunction(HDET hDet, WORD wStartChan, WORD wNumChans,
LPDWORD lpdwBuffer, LPWORD lpwRetChans, LPDWORD lpdwDataMask,
LPDWORD lpdwROIMask, LPCSTR lpszAuth);
У меня есть один пример его вызова на С++, который выглядит следующим образом:
::MyFunction((HDET)pUConn->GetHDET(), (WORD)lStartChan, (WORD)lNumChans,
&(m_DataCache[lView].pdwChans[lStartChan]), &wRetChans,
&(m_DataCache[lView].dwDataMask), &(m_DataCache[lView].dwROIMask),
pUConn->GetAuthorization());
//m_DataCache[lView].pdwChans is an array of DWORDs. (Declared as DWORD * pdwChans;)
Теперь я могу маршалировать все параметры, кроме четвертого. Это указатель на массив DWORDs. Это заставляет меня задуматься int **. Однако, когда я прохожу ref int[], я возвращаюсь null. Вот моя декларация С#:
[System.Runtime.InteropServices.DllImport("MCBCIO32.dll", EntryPoint = "MIOGetData")]
public static extern IntPtr MIOGetData(int hDet, ushort sChan, ushort nChan,
ref int[] Buffer, ref short rchan, ref int datamask, ref int roimask, string auth);
Я также пробовал IntPtr и простой int[]. Int[] возвращает массив нулей. IntPtr интересно (интересно, но неправильно) - возвращает мне странные значения, ненулевые, но явно неверные. И они меняются каждый раз, когда я делаю очередной прогон, так что я думаю, что блуждаю в случайной памяти. Я распределяю память для буфера и копирую его следующим образом:
/***Snip***/
int arrsize = 100;
int[] buffer = new int[arrsize];
short rchan = -1;
IntPtr p = Marshal.AllocHGlobal(arrsize * sizeof(int));
int datamask = 0, roimask = 0;
MCBWrapper.FUNC(hDet, 0, 1, p, ref rchan, ref datamask, ref roimask, "");
Marshal.Copy(p, buffer, 0, arrsize);
Marshal.FreeHGlobal(p);
p = IntPtr.Zero;
for (int r = 0; r < arrsize; r++)
{
Console.Write($"{buffer[r]} ");
}
В этом случае буфер маршалируется как IntPtr.
Я пробовал несколько других способов маршалинга, но без особого эффекта. IntPtr кажется наиболее перспективным. Я также пытался использовать ref для IntPtr (с почти идентичным кодом), и это дает мне результаты, аналогичные одному косвенному обращению.
Буду признателен за любые мысли по этому поводу - я не привык работать с маршалингом.
It's a pointer to an array of DWORDs - нет, это указатель на DWORD, который может означать или не означать массив DWORDs. Перевод C# этого — [MarshalAs(UnmanagedType.LPArray)] int[] Buffer.
Массивы уже маршалированы как указатель на их первый элемент. Итак, Buffer должен быть int[], без ссылки.
pinvoke - ответ «нет, это не так» на вопрос «легко ли использовать c#?»
Спасибо всем. В свою защиту, int[] - это первое, что я попробовал, но nChans функционирует как длина копии, а первые 10 или около того копируемых элементов равны 0. Таким образом, это выглядело так, будто массив возвращается без изменений. Фейспалм.
Обратите внимание, что LPDWORD* в заголовке вопроса неточен, фактический код C++ просто LPDWORD
@PaulSanders Я думаю, что взаимодействие с C++ непросто ...
@Charlieface Я думаю, это неудобная и подверженная ошибкам квадратная





Это не ** указатель на указатель. Это просто указатель. Итак, вам нужно пройти в буфер. Так что это int[] не ref int[].
Размер буфера выглядит так, как будто он передается как nChan, а используемая длина буфера передается обратно в rchan. Вы указываете это, используя SizeParamIndex.
Вам также необходимо указать LPStr в последнем строковом параметре.
Использование двух других параметров ref неясно, вероятно, они должны быть либо [In] in, либо [Out] out. Также HDET непонятно, какой это размер.
[DllImport("MCBCIO32.dll")]
public static extern IntPtr MIOGetData(
int hDet,
ushort sChan,
ushort nChan,
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 4)] int[] Buffer,
[Out] out short rchan,
ref int datamask,
ref int roimask,
[MarshalAs(UnmanagedType.LPStr)] string auth);
Вы используете это так
int arrsize = 100;
int[] buffer = new int[arrsize];
int datamask = 0, roimask = 0;
var result = MCBWrapper.FUNC(hDet, 0, 1, buffer, out var rchan, ref datamask, ref roimask, "");
for (int r = 0; r < arrsize; r++)
{
Console.Write($"{buffer[r]} ");
}
Обратите внимание, что если вам нужно использовать Marshal.AllocHGlobal, вы должны обязательно освободить память в finally, чтобы предотвратить утечки.
Я всегда считал, что метод dllimport/pinvoke слишком низкого качества для использования в реальных продуктах. Сборщик мусора может перемещать вещи (поэтому С++ не должен удерживать указатели и т. д.). Я советую вам немного изучить C++/cli (не слишком сложно). введение здесь. Вы также можете прочитать о закреплении памяти при взаимодействии с C++.