Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How should I declare this C struct for interop?

I have to use a legacy C routine in the application I am developing. The code in here works, but I have to convert almost all the fields to char arrays in order to use it. There is a better way to do it? I have tried some version using strings, all to no avail.

This is the code found in the original header file...

typedef struct PXUCAMR
{
   char xumrversaocomc01;
   char xumrretcomc02[2];
   char xumrretusuc02[2]; 
   char xumrcodfalhac05[5];
   char xumrfiller1c01; 
   char xumrtipoambclic01; 
   char xumrambientec01; 
   char xumrconvertec01; 
   char xumroperacaoc01; 
   char xumropcaoexec01; 
   xumrcom_t *xumrhandleconnb31;
   char xumrreshconnc04[4];
   long xumrtamdadosb31; 
   char xumrtransacaosrvc08[8];
   char xumrtransrvdb2c04[4];
   char xumrpgmservidorc08[8]; 
   char xumrversaopgmsrvc02[2];
   char xumrconectardbc01;
   char xumrusuariosrvc08[8];
   char xumrsenhasrvc08[8]; 
   char xumridcriptc08[8];
   char xumrpgmclientec08[8];
   char xumrversaopgmclientec02[2]; 
   char xumridclientec20[20];  
   char xumrtipoidclientec01;
   char xumrusuarioclientec08[8];
   char xumrprodutophac16[16]; 
   char xumridservidorc30[30]; 
   char xumrdadosc10000[10000];
}
pxucamr_t;

... and this is the declaration I am using in my C# app...

[StructLayout(LayoutKind.Sequential)]
internal struct PXUCAMR
{
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)]
    public char[] xumrversaocomc01;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
    public char[] xumrretcomc02;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
    public char[] xumrretusuc02;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)]
    public char[] xumrcodfalhac05;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)]
    public char[] xumrfiller1c01;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)]
    public char[] xumrtipoambclic01;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)]
    public char[] xumrambientec01;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)]
    public char[] xumrconvertec01;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)]
    public char[] xumroperacaoc01;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)] // 16
    public char[] xumropcaoexec01;
    [MarshalAs(UnmanagedType.I4)]
    public int xumrhandleconnb31;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
    public char[] xumrreshconnc04;
    [MarshalAs(UnmanagedType.I4)]
    public int xumrtamdadosb31;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] // 36
    public char[] xumrtransacaosrvc08;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
    public char[] xumrtransrvdb2c04;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
    public char[] xumrpgmservidorc08;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
    public char[] xumrversaopgmsrvc02;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)]
    public char[] xumrconectardbc01;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] // 67
    public char[] xumrusuariosrvc08;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
    public char[] xumrsenhasrvc08;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
    public char[] xumridcriptc08;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
    public char[] xumrpgmclientec08;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] // 93
    public char[] xumrversaopgmclientec02;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)]
    public char[] xumridclientec20;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)] // 114
    public char[] xumrtipoidclientec01;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
    public char[] xumrusuarioclientec08;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] // 138
    public char[] xumrprodutophac16;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 30)] // 168
    public char[] xumridservidorc30;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 10000)]
    public char[] xumrdadosc10000;
}

Is there a better way of doing it?

EDIT:

Based in Justin Rudd's answer, I have tested this version of the struct:

[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)]
internal struct PXUCAMRV3
{
    public char xumrversaocomc01;

    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 2)]
    public string xumrretcomc02;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 2)]
    public string xumrretusuc02;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 5)]
    public string xumrcodfalhac05;

    public char xumrfiller1c01;
    public char xumrtipoambclic01;
    public char xumrambientec01;
    public char xumrconvertec01;
    public char xumroperacaoc01;
    public char xumropcaoexec01; // 16

    [MarshalAs(UnmanagedType.I4)]
    public int xumrhandleconnb31;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 4)]
    public string xumrreshconnc04;
    [MarshalAs(UnmanagedType.I4)]
    public int xumrtamdadosb31;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 8)] // 36
    public string xumrtransacaosrvc08;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 4)]
    public string xumrtransrvdb2c04;

    /* ... same pattern to remaining fields ... */
}

I have tried it in just some fields with success, but I changed all of it, problems with the returning values appear. For example, I send this...

pxucamrv3.xumrpgmservidorc08 = "PHAPREXW";
pxucamrv3.xumrversaopgmsrvc02 = "01";
pxucamrv3.xumrpgmclientec08 = "PHAOCLXN";
pxucamrv3.xumrversaopgmclientec02 = "02";
pxucamrv3.xumridservidorc30 = "N006";
pxucamrv3.xumrcodfalhac05 = "00000";
pxucamrv3.xumrretcomc02 = "00";
pxucamrv3.xumrretusuc02 = "00";

... and get this ...

pxucamrv3.xumrpgmservidorc08 == "PHAPREX"
pxucamrv3.xumrversaopgmsrvc02 == "0"
pxucamrv3.xumrpgmclientec08 == "PHAOCLX"
pxucamrv3.xumrversaopgmclientec02 == "0"
pxucamrv3.xumridservidorc30 == "N006"
pxucamrv3.xumrcodfalhac05 == "01 "
pxucamrv3.xumrretcomc02 == "W"
pxucamrv3.xumrretusuc02 == "0"

... as we can see, there is a problem with the marshalling/unmarshalling of strings. The char fields are looking alright. It's not a mapping problem, as the beggining of the string fields are ok. But it seems to be truncating the end of the strings. And my test call should not return an error (using the previous struct, it works), so the C routine didn't receive the data as it should too (should return only zeros in xumrretcomc02; the returned "W" means there is an error, but I there are lots of error codes starting in "W").

I will keep digging into it.

Again, sorry for my poor english. :)

like image 602
Ricardo Nolde Avatar asked Nov 09 '09 18:11

Ricardo Nolde


1 Answers

I ended up using a byte array as the representation of the struct, and using a builder to set the values. The performance gain is not big, but it is there. And there is memory advantages too, as I can pin it in memory and send it directly to the unmanaged routine. It actually helps me with another problem: this struct has two versions, with diferent xumrdadosc10000 sizes... the original one is 10000 bytes, but the new one is 200000 bytes, but the name is the same for compatibility purposes. So, I can create the right size in the constructor, and off I go.

/// <summary>Builds PXUCAMR/PXUCAMRV3 as a byte array.</summary>
internal class PxucamrBuilder
{
    internal byte[] Pxucamr;

    /// <summary>Get/set field xumrversaocomc01, offset 0, size 1 byte.</summary>
    internal string StructureVersion
    {
        get { return Encoding.ASCII.GetString(this.Pxucamr, 0, 1); }
        set { Encoding.ASCII.GetBytes(value, 0, 1, this.Pxucamr, 0); }
    }

    /// <summary>Get/set field xumrambientec01, offset 12, size 1 byte.</summary>
    internal string Context
    {
        get { return Encoding.ASCII.GetString(this.Pxucamr, 12, 1); }
        set { Encoding.ASCII.GetBytes(value, 0, 1, this.Pxucamr, 12); }
    }

    /// <summary>Get/set field xumrcodfalhac05, offset 5, size 5 byte.</summary>
    internal string ErrorCode
    {
        get { return Encoding.ASCII.GetString(this.Pxucamr, 5, 5); }
        set { Encoding.ASCII.GetBytes(value, 0, 5, this.Pxucamr, 5); }
    }

    /// <summary>Get/set field xumridservidorc30, offset 130, size 30 bytes.</summary>
    internal string ServerId
    {
        get { return Encoding.ASCII.GetString(this.Pxucamr, 130, 30); }
        set { Encoding.ASCII.GetBytes(value, 0, value.Length, this.Pxucamr, 130); }
    }

    /// <summary>Get/set field xumrtamdadosb31, offset 24, size 4 byte.</summary>
    internal int DataSize
    {
        get
        {
            byte[] bytes = new byte[4];
            Array.Copy(this.Pxucamr, 24, bytes, 0, 4);
            return this.ByteArrayToInt32(bytes);
        }
        set
        {
            byte[] bytes = this.Int32ToByteArray(value);
            Array.Copy(bytes, 0, this.Pxucamr, 24, 4);
        }
    }

    /* ... same pattern to remaining fields ... */   
}

The offsets can be easily found with the Marshal.OffsetOf method.

As I am using this one, I will mark this answer as the correct one. But I can change it if a better answer comes up. I am still willing to try new ideas!

like image 85
Ricardo Nolde Avatar answered Sep 19 '22 21:09

Ricardo Nolde