Как отображать значения из структур в C# из 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";
        }

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


person TechBrkTru    schedule 11.07.2017    source источник
comment
Я понятия не имею о C #, но я приветствую вашу храбрость, предлагающую более половины вашей репутации в качестве вознаграждения, поэтому я проголосовал.   -  person einpoklum    schedule 14.07.2017
comment
Несколько вещей... Из кода, которым вы поделились, hResult нигде не объявлен (хотя игнорирование вашего кода указывает на то, что он должен быть целым числом). На первый взгляд это указывает на то, что DicErrorCode получает недопустимое целочисленное преобразование в строку, но действительно сложно определить, когда возможно, что вы неправильно используете свои структуры. Возможно ли, что вы могли бы написать буквальное представление того, что пытается сделать ваш код?   -  person Jouster500    schedule 14.07.2017


Ответы (2)


Объявления 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 для char, 8-битного типа. 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 — это макрос, но вы не упомянули, что MDA PInvokeStackImbalance жалуется на него, поэтому Stdcall, скорее всего, будет правильным. Убедитесь, что вы не выключили его.
  • [Out]out sp_BankNoteTypeList list
    Атрибут [Out] необходим здесь, чтобы убедить маршаллер pinvoke в необходимости копирования структуры обратно. Это довольно неинтуитивно, большинство программистов считают, что out достаточно. Но это деталь языка C#, о которой маршаллер не знает. Обратное копирование должно быть явно запрошено, структура не является "преобразуемой". Или, другими словами, собственный макет отличается от внутреннего управляемого макета. ByValArray делает это неизбежным.

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

person Hans Passant    schedule 16.07.2017

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

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

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

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

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

Проблемы:

  1. Calling convention mismatch (this was throwing an exception in my case): by default C(C++) uses __cdecl while the C# marshaler uses __stdcall. That doesn't work well, it corrupts the stack when pushing/popping arguments. In order to fix this you must change the default in one place only:
    • C++: BNA_API int __stdcall BanknoteType(SP_CIMNOTETYPELIST *sp_BankNoteType); - this is the method I chose
    • C#: DllImport(@"abc.dll", CallingConvention = CallingConvention.Cdecl)] — это тоже работает
  2. Некоторые типы из C# отличаются (шире), чем их аналоги из C. При выравнивании данных, если возникают такие несоответствия, все, что идет после 1st, искажается (при попытке его получить). Итак, в определении C# struct sp_notetype у вас должно быть: public uint cim_ulValues;. Полный список см. в [MSDN]: Marshaling Arguments.
  3. (может быть вариантом предыдущего) Charset ваших структур - в C используется char (ширина 8 бит) - измените его с Charset.Auto на Charset.Ansi. Проверьте [SO]: C# вызывает C DLL, передать char * в качестве неверного параметра
person CristiFati    schedule 16.07.2017