views:

833

answers:

3

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. :)

A: 

I am not sure this is what you meant but macros can make your life easier

Create a file that contains a description of the struct's members with enough information to rebuild it correctly:

struct.h
------- 
MEMBER(char, xumrversaocomc01)
ARRAY(char, xumrretcomc02, 2)

Then, include the list file with the correct declaration for the required format:

struct_c.h
----------
#define MEMBER(type, name) type name;
#define ARRAY(type, name, size) type name[size];
typedef struct PXUCAMR
{
#include "struct.h"
}
#undef MEMBER
#undef ARRAY

The same goes for C#

eyalm
Are you sure? I could be wrong, but judging by this link (http://msdn.microsoft.com/en-us/library/ed8yd1ha%28VS.80%29.aspx), I can't use #define as you've shown, and looks like C# lacks the #include directive too.
Ricardo Nolde
I am not sure, I am not working with c#. I thought this generic code may you help you. I guess I was wrong.
eyalm
+1  A: 

When you tried with "strings to no avail" what happened? It crashed? Didn't map properly? Missed some fields?

I've used the following constructs in the past...

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct PXUCAMR
{
    [MarshalAs(UnmanagedType.I1)]
    public sbyte xumrversaocomc01;

    // null terminated string?
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 5)]
    public string xumrcodfalhac05;

    // or just raw data?
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)]
    public sbyte[] xumrcodfalhac05;
}

The CharSet.Ansi is needed for the ByValTStr setting. If xumrcodfalhac05 is really data and not a string, use the sbyte array.

Justin Rudd
Well, it has been a while since I have tried all the variants, but now there is a need to speed up the application, so I am once again diving into this routine. But I remember having more than one problem: wrong mapping of fields, exceptions from Marshal.AllocHGlobal telling me it could not "measure" the correct size of the struct, the extern C routine returning errors in the format of the fields... I never had the time to document every case properly.
Ricardo Nolde
Check the edit for some feedback to your answer! ;)
Ricardo Nolde
The reason you're having problems after the change is that C# `char` doesn't map to C++ `char`, even with `CharSet.Ansi` (the latter only applies to `string`/char array mappings). A single `char` field either has to be mapped to `string` with `SizeConst=1`, or to `sbyte`. Also, don't map pointers as `int` - use `IntPtr`, it's intended specifically for that.
Pavel Minaev
@Pavel - indeed, C/C++ char does not map to C# char, as the C# one is an unicode char. And the pointer is mapped as an int because I don't really need it, considering the way I use the struct. So, I didn't care about using an IntPtr.
Ricardo Nolde
A: 

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!

Ricardo Nolde
Consider evaluating the performance tips on the site "Improving .NET Application Performance and Scalability", at http://msdn.microsoft.com/en-us/library/ms978943.aspx
lsalamon