Calypoter
Calypoter

Reputation: 155

Error on size of class

I have a problem with converting a byte array into a class or struct. The class is like this:

[Serializable()]
[StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)]
public class General {
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = Defines.CENTR_NAME_LENGTH + 1)] byte[] centralName;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = Defines.PROJECT_NAME_LENGTH + 1)] byte[] projectName;
    byte misc;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)] byte[] accessCode2;
    byte transmActive;
    byte transmHour;
    byte transmMin;
    [MarshalAs(UnmanagedType.U1)] public int Cp;
    [MarshalAs(UnmanagedType.U1)] public int Rp;
    [MarshalAs(UnmanagedType.U1)] public int Lcd;
    [MarshalAs(UnmanagedType.U1)] public int Relais;
    [MarshalAs(UnmanagedType.U1)] public int Pr;
    [MarshalAs(UnmanagedType.U1)] public int Sc;
    byte rd;
    byte reserve1;
    [MarshalAs(UnmanagedType.U1)] public int LocalCentrId;
    [MarshalAs(UnmanagedType.U1)] public int PrinterSel;
    [MarshalAs(UnmanagedType.U1)] public int Slave1;
    [MarshalAs(UnmanagedType.U1)] public int Slave2;
    [MarshalAs(UnmanagedType.U1)] public int Slave3;
    [MarshalAs(UnmanagedType.U1)] public int Master;
    [MarshalAs(UnmanagedType.U1)] public int AlarmRepeat;
    [MarshalAs(UnmanagedType.U1)] public int FaultRepeat;
    [MarshalAs(UnmanagedType.U1)] public int ResetSil1;
    [MarshalAs(UnmanagedType.U1)] public int ResetSil2;
    [MarshalAs(UnmanagedType.U1)] public int EvacDelayed1;
    [MarshalAs(UnmanagedType.U1)] public int EvacDelayed2;
    [MarshalAs(UnmanagedType.U1)] public int Led1;
    [MarshalAs(UnmanagedType.U1)] public int Led2;
    [MarshalAs(UnmanagedType.U1)] public int GenControl;
    [MarshalAs(UnmanagedType.U1)] public int ExtraGenControl;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = Defines.MAX_NMB_SIL_CONTROL)] byte[] silenceControls ;
    byte autoResetFault;
    byte autoResetSC;
    [MarshalAs(UnmanagedType.U1)] public int InitEvacDelay;
    [MarshalAs(UnmanagedType.U1)] public int SilenceEvacOff;
    byte summerTime;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)] byte[] accessCode1;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] byte[] btPinCode;

    public string CentralName { get { return Encoding.ASCII.GetString(centralName); } set { centralName = Encoding.ASCII.GetBytes(value); } }
    public string ProjectName { get { return Encoding.ASCII.GetString(projectName); } set { projectName = Encoding.ASCII.GetBytes(value); } }
    public bool BackBeep { 
        get { 
            return (misc & 0x01) != 0x00;
        }
        set {
            if (value) {
                misc |= 0x01;
            } else {
                misc ^= 0x01;
            }
        }
    }
    public bool StartStopEvac {
        get {
            return (misc & 0x02) != 0x00;
        }
        set {
            if (value) {
                misc |= 0x02;
            } else {
                misc ^= 0x02;
            }
        }
    }
    public bool GenBehEvacKey {
        get {
            return (misc & 0x04) != 0x00;
        }
        set {
            if (value) {
                misc |= 0x04;
            } else {
                misc ^= 0x04;
            }
        }
    }
    public bool GenBehEvacDet {
        get {
            return (misc & 0x08) != 0x00;
        }
        set {
            if (value) {
                misc |= 0x08;
            } else {
                misc ^= 0x08;
            }
        }
    }
    public bool EvacDelayed {
        get {
            return (misc & 0x10) != 0x00;
        }
        set {
            if (value) {
                misc |= 0x10;
            } else {
                misc ^= 0x10;
            }
        }
    }
    public bool SirService {
        get {
            return (misc & 0x20) != 0x00;
        }
        set {
            if (value) {
                misc |= 0x20;
            } else {
                misc ^= 0x20;
            }
        }
    }
    public bool ResetSilService {
        get {
            return (misc & 0x40) != 0x00;
        }
        set {
            if (value) {
                misc |= 0x40;
            } else {
                misc ^= 0x40;
            }
        }
    }
    public string AccessCode1 { get { return Encoding.ASCII.GetString(accessCode1); } set { accessCode1 = Encoding.ASCII.GetBytes(value); } }
    public string AccessCode2 { get { return Encoding.ASCII.GetString(accessCode2); } set { accessCode2 = Encoding.ASCII.GetBytes(value); } }
    public bool EvacDirect { get { return transmActive == 0x01; } set { transmActive = (Byte)(value ? 0x01 : 0x00); } }
    public TimeSpan EvacDirectTime { get { return new TimeSpan(transmHour, transmMin, 0); } set { transmHour = (Byte)value.Hours; transmMin = (Byte)value.Minutes; } }
    public bool Rd { get { return rd == 0x01; } set { rd = (value ? (Byte)0x01 : (Byte)0x00); } }
    public List<int> SilenceControl {
        get {
            List<int> retVal = new List<int>();
            if (silenceControls != null) {
                foreach (byte b in silenceControls) {
                    retVal.Add(b);
                }
            }
            return retVal;
        } 
        set {
            silenceControls = new byte[Defines.MAX_NMB_SIL_CONTROL];
            for (int t = 0; t < Defines.MAX_NMB_SIL_CONTROL && t < value.Count; ++t) {
                silenceControls[t] = (Byte)value[t];
            }
        }
    }
    public bool AutoResetFault { get { return autoResetFault == 0x01; } set { autoResetFault = (Byte)(value ? 0x01 : 0x00); } }
    public bool AutoResetPower { get { return autoResetSC == 0x01; } set { autoResetSC = (Byte)(value ? 0x01 : 0x00); } }
    public bool SummerTime { get { return summerTime == 0x01; } set { summerTime = (Byte)(value ? 0x01 : 0x00); } }
    public string BtPinCode { get { return Encoding.ASCII.GetString(btPinCode); } set { btPinCode = Encoding.ASCII.GetBytes(value); } }

    public General() {
        Console.WriteLine("Create General");
        SetDefault();
    }

    public void SetDefault() {
        CentralName = "MD2400";
        ProjectName = "Limotec";
        Cp = 1;
        Rp = 0;
        Lcd = 0;
        Relais = 1;
        Pr = 0;
        Sc = 1;
        Rd = true;
        BackBeep = false;
        AccessCode1 = "654321";
        AccessCode2 = "123456";
        EvacDirect = false;
        EvacDirectTime = new TimeSpan( 0, 0, 0 );
        BtPinCode = "1111111111111111";
        SummerTime = true;
        GenControl = 1;
        ExtraGenControl = 0;
        SilenceControl.Add( 3 );
        InitEvacDelay = 2;
        EvacDelayed = false;
        StartStopEvac = false;
        SirService = false;
        ResetSilService = false;
        GenBehEvacKey = true;
        GenBehEvacDet = true;
        Master = 32;
        Slave1 = 0;
        Slave2 = 0;
        Slave3 = 0;
        AlarmRepeat = 0;
        FaultRepeat = 0;
        ResetSil1 = 0;
        ResetSil2 = 0;
        Led1 = 0;
        Led2 = 0;
        EvacDelayed1 = 0;
        EvacDelayed2 = 0;
        LocalCentrId = 0;
        AutoResetFault = true;
        AutoResetPower = true;
    }

    [OnDeserialized()]
    internal void OnDeserialized(StreamingContext contect) {
        Config.Singleton.Log.Info( string.Format( "General loaded: {0} {1}", CentralName, ProjectName ) );
    }
}

When I do Marshall.SizeOf(typeof(General)) I get an ArgumentException. The size or offset cannot be calculated.

Is there a problem if I use private variables and public properties in the same class? The properties are only for converting the variable in something more usefull. (Byte array into string)

How can I solve it so that I can put a byte array into the class using Marshal.PtrToStructure ?

Upvotes: 1

Views: 595

Answers (1)

Marc Gravell
Marc Gravell

Reputation: 1063599

For me it just works: I get 43, which agrees with 2 * 21 + 1. I've checked .NET 2.0, 3.5 and 4.5 (and for 4.5 I checked both x86 and x64) - it just seems fine.

No, properties don't matter: the marshal code is only looking at fields.


Edit: what does happen, though, is that the data doesn't match:

Type could not be marshaled because the length of an embedded array instance does not match the declared length in the layout.

You need to ensure the arrays are of the right length - the following is ugly, but works, sort-of (don't set an over-length string!):

[MarshalAs(UnmanagedType.ByValArray, SizeConst = 21)]
byte[] centralName = new byte[21];
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 21)]
byte[] projectName = new byte[21];
byte misc;

public string CentralName {
    get {
        int i = Array.IndexOf(centralName, (byte)0);
        if (i < 0) i = centralName.Length;
        return Encoding.ASCII.GetString(centralName, 0, i);
    }
    set {
        Array.Clear(centralName, 0, centralName.Length);
        Encoding.ASCII.GetBytes(value, 0, value.Length, centralName, 0);
    } }
public string ProjectName {
    get {
        int i = Array.IndexOf(projectName, (byte)0);
        if (i < 0) i = projectName.Length;
        return Encoding.ASCII.GetString(projectName, 0, i);
    }
    set {
        Array.Clear(projectName, 0, projectName.Length);
        Encoding.ASCII.GetBytes(value, 0, value.Length, projectName, 0);
    } }

with example:

unsafe static void Main()
{
    int i = Marshal.SizeOf(typeof(General));
    General obj = new General { CentralName = "abc", ProjectName = "def" },
            clone;
    byte[] b = new byte[i];
    fixed(byte* a = b)
    {
        IntPtr ptr= new IntPtr(a);
        Marshal.StructureToPtr(obj, ptr, false);
        clone = (General) Marshal.PtrToStructure(ptr, typeof(General));
    }
    Console.WriteLine(clone.CentralName); // abc
    Console.WriteLine(clone.ProjectName); // def
}

or:

unsafe static void Main()
{
    int i = Marshal.SizeOf(typeof(General));
    General obj = new General { CentralName = "abc", ProjectName = "def" },
            clone;
    byte* a = stackalloc byte[i];

    IntPtr ptr= new IntPtr(a);
    Marshal.StructureToPtr(obj, ptr, false);
    clone = (General) Marshal.PtrToStructure(ptr, typeof(General));

    Console.WriteLine(clone.CentralName); // abc
    Console.WriteLine(clone.ProjectName); // def
}

Upvotes: 2

Related Questions