Search code examples
c#arraysstructpinvokemarshalling

C# Marshaling, ubalanced stack and getting PInvoke signature correct


I'm trying to call a C DLL in my C# project using marshaling and have some functions working but I have trouble with others. Like the one below.

My first problem is getting the structs correct and next problem is to return PROFILE_INFO as an array with the list of program files or maybe it won't return a list and proNum is an index.

function in C

extern "C" __declspec(dllexport) int WINAPI GetProgramFileList (unsigned long proNum, PROFILE_INFO *proFile);

typedef struct{
    PROINFO         proInfo;
    __int64             proSize;
    PROGRAM_DATE    createDate;
    PROGRAM_DATE    writeDate;
}PROFILE_INFO;

typedef struct{
    char wno[33];
    char dummy[7];
    char comment[49];
    char dummy2[7];
    char type;
    char dummy3[7];
}PROINFO;

typedef struct{
    short   year;
    char    month;
    char    day;
    char    hour;
    char    min;
    char    dummy[2];
}PROGRAM_DATE;

My function

[DllImport(@".\IFDLL.dll", EntryPoint = "GetProgramFileList", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Winapi, SetLastError = true)]
public static extern int GetProgramFileListTest(ulong proNum, ref PROFILE_INFO pro);

[StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)]
public struct PROFILE_INFO
{
    [MarshalAs(UnmanagedType.Struct)]
    public PROINFO proInfo;            // WNo/name/type
    public long proSize;               // Program size
    [MarshalAs(UnmanagedType.Struct)]
    public PROGRAM_DATE createDate;    // Program creating date
    [MarshalAs(UnmanagedType.Struct)]
    public PROGRAM_DATE writeDate;     // Program updating date
}

[StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)]
public struct PROINFO
{
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 33)] public string wno;         // WNo.
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 7)] private string dummy;       // dummy
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 49)] public string comment;     // program name
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 7)] private string dummy2;      // dummy
    public char type;                                                               // program type
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 7)] private string dummy3;      // dummy
}

[StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)]
public struct PROGRAM_DATE
{
    public short year;                 // Date (Year) 4-digit
    public char month;                 // Date (Month)
    public char day;                   // Date (Day)
    public char hour;                  // Date (Time)
    public char min;                   // Date (Minutes)
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 2)] private char dummy; // Dummy
}

The PROGRAM_DATE createDate in C# struct PROFILE_INFO will throw:

Cannot marshal field 'createDate' of type 'CClient.Models.PROFILE_INFO': The type definition of this field has layout information but has an invalid managed/unmanaged type combination or is unmarshalable.

Changing PROGRAM_DATE fields to string makes it accept it but the function returns an argument(-60) error instead. Though not sure if I'm closer to success.

Other attempts, including attempts to get PROFILE_INFO to return as an array (ref PROFILE_INFO[]), landed in:

A call to PInvoke function has unbalanced the stack. This is likely because the managed PInvoke signature does not match the unmanaged target signature.

I have got these descriptions following the C dll:

Explanation

Obtain the program list in the standard area

Argument

proNum [in]

Specify the number of the data to be obtained.

proFile [out]

Store the program information in the standard area, in PROFILE_INFO type.

Before executing this function, make sure to ensure the data area for the number of the data to be obtained.

Return value

If it succeeded, “0” is returned. If there is an error, a value other than “0” is returned.

Other functions I have working is GetProgramDirInfo, SendProgram, ReceiveProgram, SearchProgram, etc. but they don't return any arrays, so I assume marshaling arrays is my problem here. Also I'm trying to avoid using unsafe pointers and I'm unsure if I need to do the copying myself.

Any help is appreciated.


Solution

  • Few points when working with p/invoke:

    • don't add what's obvious for .NET (structs are structs, ...)
    • don't add pack if you're unsure, by default .NET should behave like C/C++ on that matter
    • don't add Ansi information if there's no string in the definition (only when there are strings or TStr, etc.). It doesn't cause problems but is useless
    • in general, don't add attributes if you don't know what they are used for
    • int and long in C/C++ are (in general) 32-bit. long is not 64-bit in C/C++

    Here is a code that should be better:

      [DllImport(@".\IFDLL.dll", EntryPoint = "GetProgramFileList")]
      public static extern int GetProgramFileListTest(uint proNum, ref PROFILE_INFO pro);
    
      [StructLayout(LayoutKind.Sequential)]
      public struct PROFILE_INFO
      {
          public PROINFO proInfo;            // WNo/name/type
          public long proSize;               // Program size
          public PROGRAM_DATE createDate;    // Program creating date
          public PROGRAM_DATE writeDate;     // Program updating date
      }
    
      [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
      public struct PROINFO
      {
          [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 33)] public string wno;         // WNo.
          [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 7)] private string dummy;       // dummy
          [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 49)] public string comment;     // program name
          [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 7)] private string dummy2;      // dummy
          public char type;                                                               // program type
          [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 7)] private string dummy3;      // dummy
      }
    
      [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
      public struct PROGRAM_DATE
      {
          public short year;                 // Date (Year) 4-digit
          public char month;                 // Date (Month)
          public char day;                   // Date (Day)
          public char hour;                  // Date (Time)
          public char min;                   // Date (Minutes)
          [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 2)] private string dummy; // Dummy
      }