C# P/Вызов Поврежденной памяти

#c# #c #pinvoke

Вопрос:

У меня странная проблема с C# P/Invoke, когда структура повреждена при переходе с C# на C , если только она не передается по ссылке, но не происходит со всеми другими структурами в одном вызове и других вызовах.

Первый вызов работает на 100% без проблем, даже с массивом. Вторые вызовы работают только тогда, когда я передаю Struct8 как ссылку, если я этого не сделаю, то память будет повреждена к тому времени, когда она попадет в код C , что не имеет смысла, так как почему все остальные структуры будут работать без необходимости передачи по ссылке?

Мои структуры 1:1 (строка в символ с маршалами и т.д.) Код C по большей части использует массивы в стиле C

Я делаю следующие звонки

 var struct1 = new Struct1() { Name = @"TEST" };
Create(
    struct1,
    ref struct2,
    new Struct3() { Channel = string.IsNullOrEmpty(request.Channel) ? "TESTCHANNEL" : request.Channel },
    Array.Empty<Struct4>(),
    new Struct5() { Something = 0 },
    out var struct6,
    out var struct7,
    out var struct2Detail
);

var struct8 = new Struct8() { Number = struct2.Number, TrySomething = 1 };
Initialize(struct1, struct8, out struct6, out struct2Detail);
 

Работает только тогда, когда я называю это так:

 Initialize(struct1, ref struct8, out struct6, out struct2Detail);
 

Псевдокод для сортировки структур на C#

 public static void Create(
    Struct1 struct1
    ,ref Struct2 struct2
    ,Struct3 struct3
    ,Struct4[] struct4
    ,Struct5 struct5
    ,out Struct6 struct6
    ,out Struct7 struct7
    ,out Struct2Detail struct2Detail
)
{
    int numProcFields = struct4.Length;
    Net5_Create(
        struct1
        ,ref struct2
        ,struct3
        ,ref numProcFields
        ,struct4
        ,struct5
        ,out struct6
        ,out struct7
        ,out struct2Detail
    );
}

[DllImport(LIBNAME, CallingConvention = CallingConvention.Cdecl)]
public static extern void Net5_Create(
    Struct1 struct1
    ,[In, Out] ref Struct2 struct2
    ,Struct3 struct3
    ,[In, Out] ref int numProcFields
    ,Struct4[] struct4
    ,Struct5 struct5
    ,out Struct6 struct6
    ,out Struct7 struct7
    ,out Struct2Detail struct2Detail
);

public static void Initialize(
    Struct1 struct1
    ,Struct8 struct8
    ,out Struct6 struct6
    ,out Struct2Detail struct2Detail
)
{
    Net5_Initialize(
        struct1
        ,struct8
        ,out struct6
        ,out struct2Detail
    );
}

[DllImport(LIBNAME, CallingConvention = CallingConvention.Cdecl)]
public static extern void Net5_Initialize(
    Struct1 struct1
    ,Struct8 struct8
    ,out Struct6 struct6
    ,out Struct2Detail struct2Detail
);

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct Struct1
{
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 33)]
    public string Name;
}

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct Struct2
{
    public int Number;
    public int Reference;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 65)]
    public string ReferenceValue;
    public int ReferenceYear;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 16)]
    public string DateCreated;
}

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct Struct4
{
    public int ObjectType;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 65)]
    public string Name;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 201)]
    public string Value;
}

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct Struct6
{
    public int Number1;
    public int Number2;
    public int Number3;
    public int Number4;
    public int Number5;
    public int Number6;
    public int Number7;
    public int Number8;
}

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct Struct7
{
    public int Number1;
    public int Number2;
    public int Number3;
    public int Number4;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 65)]
    public string String1;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 257)]
    public string String2;
}

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct Struct8
{
    public int Number;
    public int TrySomething;
}
 

Код на стороне C

 #ifdef __cplusplus
extern "C" {
#endif
#ifdef _WIN32
#    define MODULE_API __declspec(dllexport)
#else
#  define MODULE_API
#endif

MODULE_API void Net5_Initialize(
    Struct1* struct1
    ,Struct8* struct8
    ,Struct6* struct6
    ,Struct2Detail* struct2Detail
);

void Net5_Initialize(
        Struct1* struct1
        ,Struct8* struct8
        ,Struct6* struct6
        ,Struct2Detail* struct2Detail
    )
{
    dotnetInstance->Initialize(
         struct1
        , struct8
        , struct6
        , struct2Detail
    );
}
#ifdef __cplusplus
}
#endif
#endif

// typedef __int32 int32;
struct Struct1
{
    char Name[33];
    Struct1()
    {
        Name[0] = 0;
    }
    void Function1() { ... }
}

struct Struct2
{
    int32 Number;
    int32 Reference;
    char ReferenceValue[65];
    int32 ReferenceYear;
    char DateCreated[16];
    Struct2()
    {
        Number = 0;
        Reference = 0;
        ReferenceValue[0] = 0;
        ReferenceYear = 0;
        DateCreated[0] = 0;
    }
    void Function1() { ... }
}

struct Struct4
{
    int32 ObjectType;
    char Name[65];
    char Value[201];
    Struct4()
    {
        ObjectType = 0;
        Name[0] = 0;
        Value[0] = 0;
    }
    void Function1() { ... }
}

struct Struct6
{
    int32 Number1;
    int32 Number2;
    int32 Number3;
    int32 Number4;
    int32 Number5;
    int32 Number6;
    int32 Number7;
    int32 Number8;
    Struct6()
    {
        Number1 = 0;
        Number2 = 0;
        Number3 = 0;
        Number4 = 0;
        Number5 = 0;
        Number6 = 0;
        Number7 = 0;
        Number8 = 0;
    }
    void Function1() { ... }
}

struct Struct7
{
    int32 Number1;
    int32 Number2;
    int32 Number3;
    int32 Number4;
    char String1[65]
    char String2[257];
    Struct7()
    {
        Number1 = 0;
        Number2 = 0;
        Number3 = 0;
        Number4 = 0;
        String1[0] = 0;
        String2[0] = 0;
    }
    void Function1() { ... }
}
struct Struct8
{
  int32 Number;
  int32 TrySomething;
  Struct8()
  {
    Number = 0;
    TrySomething = 0;
  }
    void Function1() { ... }
}
void Class::Create(Struct1* struct1, Struct2* struct2, Struct3* struct3, int32* numProcFields, Struct4* struct4, Struct5* struct5, Struct6* struct6, Struct7* struct7, Struct2Detail* struct2Detail)
{
    ...
}
void Class::Initialize(Struct1* struct1, Struct8* struct8, Struct6* struct6, Struct2Detail* struct2Detail)
{
    lprintf("Initialize - Started (%d)", struct8->Number);
    ...
}
 

Комментарии:

1. Не заглядывая слишком глубоко… Struct1 передается как указатель на стороне C , но передается по значению на стороне C#.

2. дополнение к комментарию canton7: размер Struct1 совпадает с размером указателя (так как он содержит только указатель на строку), поэтому нет проблем с отправкой Struct1 по val, а не по ссылке (но все равно это неправильно)

3. Если я изменю все структуры на по ссылке, то у меня возникнут другие проблемы с повреждением кучи при возврате функций

4. использование out также неправильно … его скорее следует использовать, когда родная сторона ожидает указатель на указатель, пожалуйста, покажите Initialize на родной стороне, как Class::Initialize не то, что здесь используется (это член класса, поэтому очевидно, что он thiscall не использует cdecl )

5. Конечно. Я отредактировал вопрос. Это просто прокси, так как для класса существует глобальный экземпляр. Добавлен код заголовка и код включения как одно целое, но они находятся в разных файлах

Ответ №1:

Оказывается, проблема была не в инициализации, а в том, что она топала по памяти входного массива Create, если это тоже byref

Работа Над Созданием

 [DllImport(LIBNAME, CallingConvention = CallingConvention.Cdecl)]
public static extern void Net5_Create(
    ref Struct1 struct1
    ,ref Struct2 struct2
    ,ref Struct3 struct3
    ,ref int numProcFields
    ,[MarshalAs(UnmanagedType.LPArray)] Struct4[] struct4
    ,ref Struct5 struct5
    ,ref Struct6 struct6
    ,ref Struct7 struct7
    ,ref Struct2Detail struct2Detail
);
 

Комментарии:

1. Я почти уверен, что если вы добавите ref в массив и не будете использовать атрибут, это тоже должно сработать…

2. @selvin нет, этого бы не было