views:

268

answers:

4
typedef union _Value {
    signed char    c; 
    unsigned char  b; 
    signed short   s; 
    unsigned short w; 
    signed long    l; 
    unsigned long  u; 
    float          f; 
    double        *d; 
    char          *p; 
} Value;


typedef struct _Field {
 WORD    nFieldId;
 BYTE    bValueType;
 Value Value;
} Field;


typedef struct _Packet {
 WORD    nMessageType;
 WORD    nSecurityType;
 BYTE    bExchangeId;
 BYTE    bMarketCenter;
 int     iFieldCount;
 char    cSymbol[20];
    Field FieldArr[1];

} Packet;

What are the C# equivalent of these C++ structs?

I am migrating some code from C++ to C# and having problems to migrate these structures. I had tried a few things but I always ended up having marshalling problems.

A: 

See this MSDN article on marshaling structs with PInvoke.

The key is using the StructLayout attribute to ensure that the struct is treated properly by PInvoke, and the MarshalAs attribute for types that don't line up exactly.

Michael Meadows
Sorry I didn't mention; I've already have StructLayout defined on my C# equivalents and I don't want to use PInvoke. Basically data is incoming as bytes from a socket and I want to marshal them into my c# structures.
Otake
If the data comes in from the network, I'd just declare the fixed part of the Packet type as a C# struct, and then use a BinaryReader over the network stream to read the remaining data. Dealing with variable length structs is nontrivial in C#.
Mattias S
A: 

Back in .Net 2.0 days i also had a network socket and to convert the byte stream into a meaningful structure. At this time there was the only solution to do it by hand with BitConverter and Buffer class.

Unfortunately i couldn't find the example on the web again. So i stripped down my old class (gosh, this looks so old and ugly...). Maybe due to strip down, there are some small typo errors within it, but it should give you a good idea on how to accomplish the problem.

using System;
using System.Collections.Generic;
using System.Text;

namespace VehicleSpeedTracer
{
    public class Datagram
    {
        //Offsets im ByteArray
        private const int SizeOffset = 0;
        private const int TimeOffset = SizeOffset + sizeof(uint);
        private const int SpeedOffset = TimeOffset + sizeof(double);
        private const int UnitOffset = SpeedOffset + sizeof(char);
        private const int UnitMaxSize = (int)MaxSize - UnitOffset;

        //Daten Current
        public const uint MaxSize = 128;
        public TimeSpan CurrentTime;
        public double CurrentSpeed;
        public string Unit;

        public uint Size
        {
            get { return MaxSize - (uint)UnitMaxSize + (uint)Unit.Length; }
        }

        public Datagram()
        {
        }

        public Datagram(Datagram Data)
        {
            CurrentTime = Data.CurrentTime;
            CurrentSpeed = Data.CurrentSpeed;
            Unit = Data.Unit;
        }

        public Datagram(byte[] RawData)
        {
            CurrentTime = TimeSpan.FromSeconds(GetDouble(RawData, TimeOffset));
            CurrentSpeed = GetDouble(RawData, SpeedOffset);
            Unit = GetString(RawData, UnitOffset, (int)(GetUInt(RawData, SizeOffset) - UnitOffset));
        }

        public override string ToString()
        {
            return this.CurrentTime.Hours.ToString().PadLeft(2, '0') + ":" +
                    this.CurrentTime.Minutes.ToString().PadLeft(2, '0') + ":" +
                    this.CurrentTime.Seconds.ToString().PadLeft(2, '0') + "." +
                    this.CurrentTime.Milliseconds.ToString().PadLeft(3, '0') + "  " +
                    this.Unit;
        }

        public static implicit operator byte[](Datagram Data)
        {
            byte[] RawData;
            RawData = new byte[Data.Size];
            SetUInt(RawData, SizeOffset, Data.Size);
            SetDouble(RawData, TimeOffset, Data.CurrentTime.TotalDays);
            SetDouble(RawData, SpeedOffset, Data.CurrentSpeed);
            SetString(RawData, UnitOffset, Data.Unit);

            return RawData;
        }

        #region Utility Functions
        // utility:  get a uint from the byte array
        private static uint GetUInt(byte[] aData, int Offset)
        {
            return BitConverter.ToUInt32(aData, Offset);
        }

        // utility:  set a uint into the byte array
        private static void SetUInt(byte[] aData, int Offset, uint Value)
        {
            byte[] buint = BitConverter.GetBytes(Value);
            Buffer.BlockCopy(buint, 0, aData, Offset, buint.Length);
        }

        // utility:  get a ushort from the byte array
        private static ushort GetUShort(byte[] aData, int Offset)
        {
            return BitConverter.ToUInt16(aData, Offset);
        }

        // utility:  set a ushort into the byte array
        private static void SetUShort(byte[] aData, int Offset, int Value)
        {
            byte[] bushort = BitConverter.GetBytes((short)Value);
            Buffer.BlockCopy(bushort, 0, aData, Offset, bushort.Length);
        }

        // utility:  get a double from the byte array
        private static double GetDouble(byte[] aData, int Offset)
        {
            return BitConverter.ToDouble(aData, Offset);
        }

        // utility:  set a double into the byte array
        private static void SetDouble(byte[] aData, int Offset, double Value)
        {
            byte[] bushort = BitConverter.GetBytes(Value);
            Buffer.BlockCopy(bushort, 0, aData, Offset, bushort.Length);
        }

        // utility:  get a unicode string from the byte array
        private static string GetString(byte[] aData, int Offset, int Length)
        {
            String sReturn = Encoding.ASCII.GetString(aData, Offset, Length);
            return sReturn;
        }

        // utility:  set a unicode string in the byte array
        private static void SetString(byte[] aData, int Offset, string Value)
        {
            byte[] arr = Encoding.ASCII.GetBytes(Value);
            Buffer.BlockCopy(arr, 0, aData, Offset, arr.Length);
        }
        #endregion
    }

    public delegate void DatagramEventHandler(object sender, DatagramEventArgs e);

    public class DatagramEventArgs : EventArgs
    {
        public Datagram Data;

        public DatagramEventArgs(Datagram Data)
        {
            this.Data = Data;
        }
    }
}
Oliver
-1 This method is really dated now. C# 3.0 (may have been available in earlier versions, I only started in 3) has excellent support for mostly automating translation of data. I haven't had to do anything complex myself to get any c function working in c#, even structures containing variable length strings are easy to do.
Grant Peters
Yes, for a function you want to access by DLLImport this is true. But if you have a byte[] (e.g. from a socket) there isn't such a thing (as far as i know).
Oliver
A: 

I'm assuming the 'char' is being used as an 8 byte number, if so, then here are you're mappings:

signed char    c; -> SByte    c; 
unsigned char  b; -> Byte     b;
signed short   s; -> Int16    s;
unsigned short w; -> UInt16   w;
signed long    l; -> Int32    l;
unsigned long  u; -> UInt32   u;
float          f; -> Single   f; (though 'float' still works)
double        *d; -> Double   d; (was this meant to be a pointer???)
char          *p; -> String   s; (assuming its a string here, in the marshaling you can tell it whether it is ASCII or wide char format)

With this info it should be relatively easy to translate those strucutres (just make sure you keep them as a struct and give it the attribute "[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]", which will make sure that the marshaller keeps all the data in the same order.

Also, I recommend looking through both the classes and attributes in System.Runtime.InteropServices as they do provide quite a few methods for automating marshaling of data to c/c++ code (and it doesn't require "unsafe" c# code either).

Grant Peters
`signed long` and `unsigned long` should map to `IntPtr` and `UIntPtr`, respectively. `double*` in C++ will map to `IntPtr` or `double*` in C#, the latter is obviously only allow in unsafe code. The `StructLayout` attribute can specify the `CharSet`.
280Z28
A: 

Some of the other posts already have great information, I thought I would share a quick tip. I had to go through this kind of issue recently. It may be obvious but if you own the code to both sides of the interface I found that commenting out all but the one fields and making sure that works and then adding them back slowly one by one was a much safer way to get this working.

Steve Sheldon