указатели — Как отобразить значения из структур в C # из переполнения стека

файл abc.h

typedef struct sp_BankNoteTypeList
{
int cim_usNumOfNoteTypes;
struct sp_notetype
{
USHORT cim_usNoteID;
CHAR   cim_cCurrencyID[3];
ULONG  cim_ulValues;
bool   cim_bConfigured;
}SP_CIMNOTETYPE[12];
}SP_CIMNOTETYPELIST,*SP_LPCIMNOTETYPELIST;BNA_API int BanknoteType(SP_CIMNOTETYPELIST *sp_BankNoteType);

abc.cpp (файл DLL)

int BanknoteType(SP_CIMNOTETYPELIST *sp_BankNoteType)
{
LPWFSCIMNOTETYPE    fw_notetypedata;
LPWFSCIMNOTETYPELIST    lpNoteTypeList;   //output param
hResult = WFSGetInfo(hService, WFS_INF_CIM_BANKNOTE_TYPES, (LPVOID)NULL, 400000, &res);
lpNoteTypeList=(LPWFSCIMNOTETYPELIST)res->lpBuffer;
if(hResult!=0)
{
return (int)hResult;
}
sp_BankNoteType->cim_usNumOfNoteTypes = lpNoteTypeList->usNumOfNoteTypes;
for(int i=0;i<lpNoteTypeList->usNumOfNoteTypes;i++)
{
sp_BankNoteType->SP_CIMNOTETYPE[i].cim_usNoteID = lpNoteTypeList->lppNoteTypes[i]->usNoteID;
sp_BankNoteType->SP_CIMNOTETYPE[i].cim_ulValues = lpNoteTypeList->lppNoteTypes[i]->ulValues;
sp_BankNoteType->SP_CIMNOTETYPE[i].cim_bConfigured = lpNoteTypeList->lppNoteTypes[i]->bConfigured;
sp_BankNoteType->SP_CIMNOTETYPE[i].cim_cCurrencyID[0] = lpNoteTypeList->lppNoteTypes[i]->cCurrencyID[0];
sp_BankNoteType->SP_CIMNOTETYPE[i].cim_cCurrencyID[1] = lpNoteTypeList->lppNoteTypes[i]->cCurrencyID[1];
sp_BankNoteType->SP_CIMNOTETYPE[i].cim_cCurrencyID[2] = lpNoteTypeList->lppNoteTypes[i]->cCurrencyID[2];

}
return (int)hResult;
}

Состав :-

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct sp_notetype
{
public ushort cim_usNoteID;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
public char[] cim_cCurrencyID;
public ulong cim_ulValues;
public bool cim_bConfigured;
};

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct sp_BankNoteTypeList
{
public int cim_usNumOfNoteTypes;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 12)]
public sp_notetype[] SP_CIMNOTETYPE;
};
public sp_notetype[] SP_CIMNOTETYPE;
public sp_BankNoteTypeList SP_CIMNOTETYPELIST;

Вызов функции: —

[DllImport(@"abc.dll")]
public static extern int BanknoteType(out sp_BankNoteTypeList SP_CIMNOTETYPELIST);public string BNA_BankNoteType(out int[] NoteID,out string[]CurrencyID,out string[] Values,out bool[] Configured)
{
NoteID = new int[12];
CurrencyID = new string[12];
Values = new string[12];
Configured = new bool[12];
try
{
trace.WriteToTrace(" Entered in BNA_BankNoteType ", 1);
hResult = BanknoteType(out SP_CIMNOTETYPELIST);
for (int i = 0; i < 12; i++)
{
NoteID[i] = (SP_CIMNOTETYPELIST.SP_CIMNOTETYPE[i].cim_usNoteID);
CurrencyID[i] = (SP_CIMNOTETYPELIST.SP_CIMNOTETYPE[i].cim_cCurrencyID[0]).ToString() + (SP_CIMNOTETYPELIST.SP_CIMNOTETYPE[i].cim_cCurrencyID[1]).ToString() + (SP_CIMNOTETYPELIST.SP_CIMNOTETYPE[i].cim_cCurrencyID[2]).ToString();
Values[i] = (SP_CIMNOTETYPELIST.SP_CIMNOTETYPE[i].cim_ulValues).ToString();
Configured[i] = (SP_CIMNOTETYPELIST.SP_CIMNOTETYPE[i].cim_bConfigured);

}

return DicErrorCode(hResult.ToString());
}
catch (Exception ex)
{

return "FATAL_ERROR";
}

когда я пытаюсь вызвать их в C #, я получаю значение мусора в C #.
Пока я отлаживаю их, я считаю правильным хранить значения.
Любая помощь относительно того, как значения должны быть переданы, была бы очень полезна.
Заранее спасибо.

8

Решение

Декларации C # находятся на правильном пути, просто детали не так точны. Хорошая отправная точка эта почта, показывает, как написать некоторый тестовый код, чтобы убедиться, что объявления структуры хорошо соответствуют. Делаем так на этом конкретном:

C++: auto len = sizeof(SP_CIMNOTETYPELIST);                    // 196 bytes
C# : var len = Marshal.SizeOf(typeof(sp_BankNoteTypeList));    // 296 bytes

Не близко, вы должны получить точное совпадение, чтобы надеяться на правильное распределение. Объявление C # для внутренней структуры должно выглядеть так:

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct sp_notetype {
public ushort cim_usNoteID;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
public char[] cim_cCurrencyID;
public uint cim_ulValues;
private byte _cim_bConfigured;
public bool cim_bConfigured {
get { return _cim_bConfigured != 0; }
}
};

[DllImport(@"abc.dll", CallingConvention = CallingConvention.Stdcall)]
public static extern int BanknoteType([Out]out sp_BankNoteTypeList list);

Объявление sp_BankNoteTypeList в порядке. Перезапустите тест, и теперь вы должны получить также 196 байт в C #. Аннотирование изменений:

  • CharSet = CharSet.Ansi
    Это было необходимо, чтобы заставить члена CHAR правильно провести маршал. Это Windows typedef для char8-битный тип. CharSet.Auto был бы правильным выбором, если бы нативная структура использовала WCHAR.
  • public uint cim_ulValues;
    Windows использует Модель данных LLP64, это держит ULONG на 32 бита в 32-битных и 64-битных программах. Что делает uint правильный эквивалент C # вместо ulong.
  • private byte _cim_bConfigured;
    bool это очень сложный тип с плохой стандартизацией. Это 1 байт в C ++, 4 байта в C, 2 байта в COM-взаимодействии, 1 байт в управляемом коде. Маршалинг по умолчанию предполагает BOOL как соответствующий нативный тип, как это делается в winapi. Объявление его закрытым как байт с получателем общедоступных свойств — это один из способов сделать это, и я предпочел здесь один, применение атрибута [MarshalAs (UnmanagedType.U1)] к полю было бы другим.
  • CallingConvention = CallingConvention.Stdcall
    Очень важно четко указать на это, другой распространенный выбор здесь — CallingConvention.Cdecl. Я не могу сказать из собственного фрагмента, какой из них правильный, BNA_API — это макрос, но вы не упомянули, что PInvokeStackImbalance MDA жалуется на это, так что Stdcall, скорее всего, будет правильным. Убедитесь, что вы не выключили его.
  • [Out]out sp_BankNoteTypeList list
    Атрибут [Out] необходим здесь, чтобы убедить маршаллера pinvoke, что копирование структуры обратно необходимо. Это довольно не интуитивно понятно, большинство программистов считают, что out достаточно. Но это деталь языка C #, о которой маршаллер не знает. Копирование обратно должно быть запрошено явным образом, структура не является «blittable». Или, другими словами, собственный макет не совпадает с внутренним управляемым макетом. ByValArray делает это неизбежным.

Весьма прачечная, надеюсь, я их всех получил. Получение одинакового размера структуры — 95% битвы.

7

Другие решения

Я воссоздал C ++ часть, так как я получил ошибки, связанные с WFSGetInfo и связанных с ним структур, я заполняю поля некоторыми случайными данными (примечание о char[3] поле: я заполняю его 3 случайными заглавными буквами). С вдохновением от MSDN и многие другие вопросы ТАК, Я идентифицировал список проблем в коде; После устранения этих проблем я смог получить право данные из C #. Как примечание, я использую VStudio2015.

Пара наземных нот:

  • BNA_API для меня это __declspec(dllexport)
  • Объявление функции находится внутри

    #if defined(__cplusplus)
    extern "C" {
    #endif
    
    // Function declaration comes here
    
    #if defined(__cplusplus)
    }
    #endif
    

    блок, чтобы избежать Искажение имени в C ++. Для более подробной информации, проверьте [MSDN]: украшенные имена

Проблемы:

  1. Несоответствие соглашения о вызовах (в моем случае это было исключение): по умолчанию С(C ++) использует __cdecl в то время как C # маршалер использует __stdcall, Это не работает хорошо, это повреждает стек, когда От себяИНГ /попаргументы пинга. Чтобы это исправить, вы должны изменить значение по умолчанию в только одно место:
    • C ++: BNA_API int __stdcall BanknoteType(SP_CIMNOTETYPELIST *sp_BankNoteType); — это метод, который я выбрал
    • C #: DllImport(@"abc.dll", CallingConvention = CallingConvention.Cdecl)] — это тоже работает
  2. Некоторые из типов из C # отличаются (шире), чем их корреспонденты из С. Когда данные выровнены, если такие несоответствия происходят, все, что идет после 1улица один запутался (при попытке вытащить его). Итак, в struct sp_notetype«s C # определение, вы должны иметь: public uint cim_ulValues;, Проверьте [MSDN]: маршалинг аргументов для полного списка
  3. (может быть вариант предыдущего) Charset ваших структур — в С, char (Шириной 8 бит) — измените его с Charset.Auto в Charset.Ansi, Проверьте [SO]: C # вызывает C DLL, передайте char * как параметр, неверный
3