Question

How can I convert this code from Delphi to C#? I need struct to interact with unmaneged code.

TDataTypeParam = packed record
 dtType : integer;
 case integer of
   cInt     :(dtInt      : integer);
   cFloat   :(dtFloat    : real);
   cLongInt :(dtLongInt  : Int64);
   cDateTime:(dtDateTime : TDateTime);
   cShortStr:(dtShortString : ShortString);
end;

TDataParam =   packed record
 NumberParam : integer;
 Param       : array [1..MaxParam] of TDataTypeParam;
end;

TEvData =   packed record
 dm       : TDateTime;
 CodeEV   : integer;
 IDCAM    : integer;
 Reserv1  : integer;

 Data     : TDataParam;
end;

TArrSrvData =   packed record
 NumberPack : integer;
 Address  : Cardinal;
 tpCL     : integer;
 tpEv     : integer;
 Reserv   : integer;
 Packs      : array [1..MaxPacks] of TEvData;
end;

This code is throw System.TypeLoadException:

//TDataTypeParam = packed record
//dtType : integer;//data type
// case integer of
//   cInt     :(dtInt      : integer);
//   cFloat   :(dtFloat    : real);
//   cLongInt :(dtLongInt  : Int64);
//   cDateTime:(dtDateTime : TDateTime);
//   cShortStr:(dtShortString : ShortString);
//end;

[StructLayout(LayoutKind.Explicit)]
[Serializable]
internal struct DataTypeParam
{
    [FieldOffset(0)]
    public DataType dtType;
    [FieldOffset(4)]
    public int dtInt;
    [FieldOffset(4)]
    public double dtFloat;
    [FieldOffset(4)]
    public long dtLongInt;
    [FieldOffset(4)]
    public double dtDateTime;
    [FieldOffset(4)]
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 256)]
    public byte[] dtShortString;
};

//TDataParam =   packed record
//  NumberParam : integer;
//  Param       : array [1..MaxParam] of TDataTypeParam;
// end;
[StructLayout(LayoutKind.Sequential, Pack = 1)]
[Serializable]
internal struct DataParam
{
    public int NumberParam;
    [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.Struct, SizeConst = Consts.MaxParam)]
    public DataTypeParam[] Param;
};

// TEvData =   packed record
//  dm       : TDateTime;
//  CodeEV   : integer;
//  IDCAM    : integer;
//  Reserv1  : integer;

//  Data     : TDataParam;
// end;
[StructLayout(LayoutKind.Sequential, Pack = 1)]
[Serializable]
internal struct EvData
{
    public DateTime dm;
    public int CodeEV;
    public int IDCAM;
    public int Reserv1;
    public DataParam Data;
}

// TArrSrvData =   packed record
//  NumberPack : integer;
//  Address  : Cardinal;
//  tpCL     : integer;
//  tpEv     : integer;
//  Reserv   : integer;
//  Packs      : array [1..MaxPacks] of TEvData;
// end;
// PArrSrvData = ^TArrSrvData;

[StructLayout(LayoutKind.Sequential, Pack = 1)]
[Serializable]
internal struct ArrSrvData
{
    public int NumberPack;
    public uint Address;
    public int tpCL;
    public int tpEv;
    public int Reserv;
    [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.Struct, SizeConst = Consts.MaxPacks)]
    public EvData[] Param;
}

Thi problem is in the cShortStr:(dtShortString : ShortString); conversion...

Here is actual code:

    void mySink_NewEvent(ref object Comm)
    {
        byte[] arr = Comm as byte[];
        if(arr == null) return;

        var data = (ArrSrvData)MarshalSerializer.RawDeserialize(arr, typeof(ArrSrvData));

    }

And RawDeserialize code:

    public static object RawDeserialize(byte[] rawData, Type type)
    {
        if (rawData == null)
            throw new ArgumentNullException(MethodBase.GetCurrentMethod().GetParameters()[0].Name);

        int rawsize = Marshal.SizeOf(type);
        if (rawsize > rawData.Length)
            return null;

        object retobj;

        GCHandle handle = GCHandle.Alloc(rawData, GCHandleType.Pinned);
        try
        {
            IntPtr buffer = handle.AddrOfPinnedObject();
            retobj = Marshal.PtrToStructure(buffer, type);
        }
        finally
        {
            handle.Free();
        }

        return retobj;
    }
Was it helpful?

Solution

You have a couple of issues.

First of all, C# DateTime does not map to Delphi TDateTime. You would need to use double in the C# code and code up a mapping from C# date/time to Delphi date/time.

The second problem, and much more serious, is indeed the string. In Delphi, ShortString is 256 bytes wide. The first byte contains the string length, the remaining 255 are the payload.

You cannot overlay a reference type with a non-reference type in a C# union. And this is your problem. All your overlaid variables are value types, apart from the string which is a reference type. Hans Passant discusses the issue here: C# Platform-invoke, c-style union with reference and value types. Note that he explicitly calls out the exception that you have encountered.

Over at MSDN, you can find the same information:

In managed code, value types and reference types are not permitted to overlap.

The normal response to this problem is to stop mixing reference and value types. It's best if you can use only blittable value types. But I don't see an obvious way for you to do that. You could use a fixed byte array, but that would force you to use unsafe and handling fixed arrays is not much fun. An example of that approach can be found here: Marshaling structure with reference-type and value-type members inside a union.

So, I recommend that you marshal the variant part of the record by hand. Declare it as byte[] with UnmanagedType.ByValArray.

[StructLayout(LayoutKind.Sequential)]
[Serializable]
internal struct DataTypeParam
{
    public DataType dtType;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst=256)]
    public byte[] dtUnion;
};

And then you need to write helpers to read/write individual fields from/to the byte array. With those helpers your struct might look at little like this:

[StructLayout(LayoutKind.Sequential)]
[Serializable]
internal struct DataTypeParam
{
    public int dtType;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 256)]
    private byte[] dtUnion;

    public int cInt
    {
        get { return BitConverter.ToInt32(dtUnion, 0); }
        set { dtUnion = BitConverter.GetBytes(value); }
    }

    public double cFloat
    {
        get { return BitConverter.ToDouble(dtUnion, 0); }
        set { dtUnion = BitConverter.GetBytes(value); }
    }
};

I'll leave you to write the rest of the helpers.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top