将 Delphi 变体记录转换为 C#

4
我该如何将这段Delphi代码转换成C#?我需要结构体与非托管代码交互。
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;

这段代码抛出了 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;
}

这个问题出现在cShortStr:(dtShortString : ShortString);转换中...

以下是实际代码:

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

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

    }

并将RawDeserialize代码添加如下:

    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;
    }

"这段代码抛出 System.TypeLoadException 异常。但您并没有展示任何实际的 代码,只有结构体定义。请展示异常发生的位置。" - Jonathon Reinhart
@Jonathon Reinhart,Marshal.PtrToStructure 方法中抛出了异常。 - Alexnadr
1
@Ken 变体记录与联合相同。但使用 FieldOffset 处理。据我所知,real 已经是 double 的别名很长一段时间了(我猜测是 Delphi 2)。而 SizeOf(ShortString)=256 - David Heffernan
1个回答

5
你有几个问题。
首先,C#的DateTime不能映射到Delphi的TDateTime。你需要在C#代码中使用double,并编写一个从C#日期/时间到Delphi日期/时间的映射。
第二个问题,也是更严重的问题,确实与字符串有关。在Delphi中,ShortString宽度为256字节。第一个字节包含字符串长度,剩余的255个字节是有效负载。
你不能在C#联合中用非引用类型叠加引用类型。这就是你的问题。除了字符串是引用类型之外,所有叠加变量都是值类型。Hans Passant在这里讨论了这个问题: C# Platform-invoke, c-style union with reference and value types。请注意,他明确指出了你遇到的异常。
MSDN上,您可以找到相同的信息:
在托管代码中,值类型和引用类型不能重叠。
解决这个问题的常规方法是停止混合使用引用类型和值类型。最好只使用可平面化的值类型。但我没有看到明显的方法让您这样做。您可以使用固定大小的字节数组,但这将强制您使用不安全代码,并且处理固定数组并不是很有趣。该方法的示例可以在此处找到:Marshaling structure with reference-type and value-type members inside a union
因此,我建议您手动编排记录的变体部分。将其声明为byte[],并使用UnmanagedType.ByValArray
[StructLayout(LayoutKind.Sequential)]
[Serializable]
internal struct DataTypeParam
{
    public DataType dtType;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst=256)]
    public byte[] dtUnion;
};

然后你需要编写辅助函数来从/向字节数组中读取/写入单个字段。有了这些帮助程序,你的结构体可能会像这样:

[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); }
    }
};

我会让你写剩下的助手。


1
谢谢你的问题。我总是喜欢这些问题。P/Invoke和Delphi。啊哈哈哈。 - David Heffernan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接