我在编组C字符数组时遇到问题。我有以下C#结构:
[StructLayout(LayoutKind.Explicit, Size = 16, CharSet = CharSet.Ansi), Serializable]
internal struct Header
{
[MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = 4)]
[FieldOffset(0)]
public string header;
[FieldOffset(4)]
public int version;
[FieldOffset(8)]
public int diroffset;
[FieldOffset(12)]
public int direntries;
}
以及以下代码从流中读取此结构:
public static T ReadStruct<T>(this Stream stream) where T : struct
{
var sz = Marshal.SizeOf(typeof(T));
var buffer = new byte[sz];
stream.Read(buffer, 0, sz);
var pinnedBuffer = GCHandle.Alloc(buffer, GCHandleType.Pinned);
var structure = (T) Marshal.PtrToStructure(
pinnedBuffer.AddrOfPinnedObject(), typeof(T));
pinnedBuffer.Free();
return structure;
}
现在我的问题是header
字段在读取结构后错过了一个字符。从中读取结构的文件包含四个字节VPVP
,但在ReadStruct
读取结构后,标题字符串仅包含VPV
。如果我看一下调试器中read函数中的字节数组,那么该数组包含的值为86,80,86,80,即VPVP
。我也尝试将LayoutKind.Sequential
用于StructLayout
,但这并未改变任何内容。
我做错了什么或为什么我的字符串中缺少一个字符?
答案 0 :(得分:3)
你遇到的问题在于结构定义,而不是将字节写入它。
问题就出在这里:
[MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = 4)]
正如你所说的那样,你会写出VPVP
这个长度为4个字符的文字。然而,事实并非如此。在C中,您可以将字符串声明为:
char mystring[] = { 'V', 'P', 'V', 'P', '\0' };
您需要在结尾处使用空字符(\0
)来标记字符串的结尾。编组时需要考虑到这一点,因为你需要为“null终结符字节”保留空间,如果不这样做,C#字符串会在可用内存中为你添加它,所以它会吞掉你的最后一个字符。因此,如果您要使用以null结尾的字符串,则必须使其长度为5。
编辑:这是一个更好的解决方案,您不必担心空终止符,只需使用char[]
(并且还可以保持魔术的16字节大小) ):
[StructLayout(LayoutKind.Explicit, Size = 16, CharSet = CharSet.Ansi), Serializable]
internal struct Header
{
[MarshalAsAttribute(UnmanagedType.ByValArray, SizeConst = 4)]
[FieldOffset(0)]
private char[] headerCharArray;
public string header
{
get { return new string(headerCharArray); }
set
{
if (value.Length == 4)
{
headerCharArray = value.ToArray();
}
else
{
throw new InvalidOperationException("String length was not 4.");
}
}
}
[FieldOffset(4)]
public int version;
[FieldOffset(8)]
public int diroffset;
[FieldOffset(12)]
public int direntries;
}
这样char[]
存储在内存中,您可以通过属性将其作为字符串访问,而不会占用结构本身的任何内存。