I've got this struct :
[StructLayout(LayoutKind.Sequential)]
public struct IS
{
public UInt32 ID;
public UInt32 Quality;
public UInt32 Flags;
public UInt32 Flags2;
public UInt32 ContainerSlots;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)]
public Int32[] ItemStatType;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)]
public UInt32[] ItemStatValue;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)]
public Int32[] ItemStatUnk1;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)]
public Int32[] ItemStatUnk2;
public UInt32 ScalingStatDistribution;
public UInt32 DamageType;
public UInt32 Delay;
public float RangedModRange;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)]
public Int32[] SpellId;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)]
public Int32[] SpellTrigger;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)]
public Int32[] SpellCharges;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)]
public Int32[] SpellCooldown;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)]
public Int32[] SpellCategory;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)]
public Int32[] SpellCategoryCooldown;
public UInt32 Bonding;
public string Name;
public string Name2;
public string Name3;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
public UInt32[] Color;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
public UInt32[] Content;
};
And I'm trying to read bytes from a file and copy those bytes to the above struct using Marshal and a GCHandle, my code is as follows:
reader = BinaryReader.FromFile(fileName);
m_rows = new List<IS>();
int size = Marshal.SizeOf(typeof(IS));
if(reader.BaseStream.Length < size)
return;
byte[] buffer = new byte[size];
buffer = reader.ReadBytes(size);
GCHandle handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
m_rows.Add((IS)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(IS)));
handle.Free();
But I'm getting an AccessViolationException : attempt to read or write protected memory
I have no idea why this exception is thrown.
I didn't see the bug immediately and wrote a little test program to repro the problem. Using binary search to find the issue, commenting half the fields out repeatedly until I narrowed it down to:
[StructLayout(LayoutKind.Sequential)]
public struct IS {
public string Name;
}
That cannot work, the pinvoke marshaller assumes the default marshaling for string is from a C string, char*. That cannot possibly correct for data that you read from a file, it can never contain valid pointers. The AccessViolation is triggered when it tries to dereference the pointer.
There are no hints in the question to guess how the string was actually serialized to the file. The normal way is:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct IS {
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 42)]
public string Name;
};
Use a hex viewer if necessary to figure out the proper value of SizeConst. If the encoding is unusual (not the system default page) then you have to declare it as byte[] and use the proper Encoding to convert it.
Access violation as you would understand is due to an attempt to read unallocated memory or the memory being released, you may want to check following stack overflow posting:
AccessViolationException when Marshal.PtrToStructure fires
Which refers to the difference between size of managed and native structure as the reason for the issue, essentially you need to provide an offset during marshalling to match the difference between managed and native allocation for the structure.
Check this posting too, where an offset has been added by the user
Access violation exception when use method Marshal.PtrToStructure in a loop.
Another link with a solution:
http://www.codeproject.com/Questions/585390/AccessplusViolationplusException
In case this doesn't help then it is easy to debug such issues using windbg, I can list out the details, in case you need to do it. Also enable Win32 Aces violation exception in VS, it would break at the line throwing exception with some additional information
Related
I am receiving data packets over UDP that I parse into structs and in a specific situation my data gets all messed up and I can't figure out what the problem might be. Let me give you an overview of what I am doing.
UDP Receiver receives the data packet and passes it to a callback - size of the byte array at this stage is 1307 bytes which is the expected size for the packet
The byte array is marshalled (is that the right term?) into a struct - size of the struct is now 1484 bytes (not sure if that is relevant)
In this example the data is send from the Formula 1 2020 game and the curious thing is that the first entry in the m_carTelemetryData array (see below) is always fine, and the data is all sound. However, every entry of the 22 after the first one is totally messed up with either 0 values, null values or completely outlandish values for all the different fields in the struct.
I tried several things already to pinpoint the issue, but I have now reached the end of my knowledge about the things I am dealing with here. My best guess is that something is going wrong when converting the data into a struct or something else is going on that causes a misalignment (?) of the data.
What I tried so far
Changed my code from "magic marshalling" to manual reading the data byte-by-byte using BinaryReader - no luck
Manually checked the data using BitConverter.ToFoo(bytes, offset) - no luck
Yolo changed the Pack attribute assuming that's where I got wrong - no luck
Double-checked the documentation to make sure I got the data types right - I am fairly confident that I "translated" them correctly
Banged my head against a wall - still no luck
My questions:
Is there something obvious wrong with my code that I am simply not seeing?
Side question: am I wrong in my assumption that the size of the struct should match the size of the byte array it has been created from?
Here is the code for reference (if anything helpful is missing, please let me know):
Packet Header
[StructLayout(LayoutKind.Sequential), Pack = 1]
struct PacketHeader2020 {
public ushort m_packetFormat;
public byte m_gameMajorVersion;
public byte m_gameMinorVersion;
public byte m_packetVersion;
public byte m_packetId;
public ulong m_sessionUUID;
public float m_sessionTime;
public uint m_frameIdentifier;
public byte m_playerCarIndex;
public byte m_secondaryPlayerCarIndex;
}
Packet
public struct CarTelemetryPacket2020
{
public PacketHeader2020 m_header;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)]
public CarTelemetryData2020[] m_carTelemetryData;
public ButtonFlag m_buttonStatus;
public byte m_mfdPanelIndex;
public byte m_mfdPanelIndexSecondaryPlayer;
public byte m_suggestedGear;
};
CarTelemetryData2020
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct CarTelemetryData2020
{
public ushort m_speed;
public float m_throttle;
public float m_steer;
public float m_brake;
public byte m_clutch;
public sbyte m_gear;
public ushort m_engineRPM;
public byte m_drs;
public byte m_revLightsPercent;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
public ushort[] m_brakesTemperature;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
public ushort[] m_tyresSurfaceTemperature;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
public ushort[] m_tyresInnerTemperature;
public ushort m_engineTemperature;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
public float[] m_tyresPressure;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
public SurfaceType[] m_surfaceType;
}
byte[] -> struct
public static T ByteArrayToStructure<T>(byte[] bytes) where T : struct
{
var handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
try
{
return (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T));
}
finally
{
handle.Free();
}
}
I just pasted your code into LINQPad and found a few problems. The CarTelemetryPacket2020 structure needed a pack clause. Also m_tyresSurfaceTemperature and m_tyresInnerTemperature should have been a byte. The following returns a size of 1307 as per the protocol specification. You're right that the sizes should have matched up and I don't see any other obvious problems with your code.
void Main()
{
System.Runtime.InteropServices.Marshal.SizeOf(typeof(CarTelemetryPacket2020)).Dump();
}
[StructLayout(LayoutKind.Sequential, Pack = 1) ]
public struct PacketHeader2020
{
public ushort m_packetFormat;
public byte m_gameMajorVersion;
public byte m_gameMinorVersion;
public byte m_packetVersion;
public byte m_packetId;
public ulong m_sessionUUID;
public float m_sessionTime;
public uint m_frameIdentifier;
public byte m_playerCarIndex;
public byte m_secondaryPlayerCarIndex;
}
// Added pack = 1
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct CarTelemetryPacket2020
{
public PacketHeader2020 m_header;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 22)]
public CarTelemetryData2020[] m_carTelemetryData;
public UInt32 m_buttonStatus;
public byte m_mfdPanelIndex;
public byte m_mfdPanelIndexSecondaryPlayer;
public byte m_suggestedGear;
};
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct CarTelemetryData2020
{
public ushort m_speed;
public float m_throttle;
public float m_steer;
public float m_brake;
public byte m_clutch;
public sbyte m_gear;
public ushort m_engineRPM;
public byte m_drs;
public byte m_revLightsPercent;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
public ushort[] m_brakesTemperature;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
// Changed following to byte
public byte[] m_tyresSurfaceTemperature;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
// Changed following to byte
public byte[] m_tyresInnerTemperature;
public ushort m_engineTemperature;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
public float[] m_tyresPressure;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
public byte[] m_surfaceType;
}
I have a C++ .dll (unable to make changes to the code) from which I am trying to call a function that takes a reference parameter of a Struct type (also defined in the .dll).
The function requires the Struct to have the 'paramName' and 'groupName' fields populated, and based on those fields, it will return the Struct with the remaining fields populated.
I am not getting any marshaling errors, however, the library call returns an error code and a debug log shows that it is receiving an empty string for the two string fields (see below for how fields are set). My assumption is that the size of this struct is not aligning between the managed and unmanaged representation, and thus the struct is not blittable.
Here is the C++ method signature:
int GetConfigs(int contextHandle, Configs* configs);
And the C++ Configs struct:
struct Configs {
int myInt;
float myFloat;
bool flag;
char name[64];
char group[64];
}
The C# function wrapper:
[DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public static extern int GetConfigs(int contextHandle, [MarshalAs(UnmanagedType.Struct), In, Out] ref Configs configs);
The C# struct definition:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct Configs
{
public int myInt;
public float myFloat;
[MarshalAs(UnmanagedType.U1)]
public bool flag;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)]
public string name;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)]
public string group;
}
As per the Microsoft documentation I have declared the C++ char[]'s to be represented in C# by strings and marshaled as ByValTStr with a set size.
Also from Microsoft documentation:
bool is not blittable, so I mark it with an explicit MarshalAs.
float is also not blittable, but declaring these fields as a float or a double made no difference in the original issue.
Lastly, the C# calling code:
var configs = new Library.Configs
{
name = "testName",
group = "testGroup"
};
var returnCode = Library.GetConfigs(GetContextId(), ref configs);
The return code comes back as a failure code and the library debug output file shows that the struct argument had:
name = []
(name is clearly set to what I expect it to be at the time of the call when I debug through the C# code)
Instead of declaring the string fields as
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)]
public string name;
I tried:
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 64)]
public byte[] name;
but that also made no difference.
The float/double was what was throwing off the struct byte size- the library was expecting a 4 byte floating point number but the data marshaler by default keeps those as 8 bytes. Fixing that along with the boolean as mentioned in the comments above solved the problem:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct Configs
{
public int myInt;
[MarshalAs(UnmanagedType.R4)]
public float myFloat;
[MarshalAs(UnmanagedType.U1)]
public bool flag;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)]
public string name;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)]
public string group;
}
Looking at the example below, TestPropSix is an array with 5 elements. How do I Marshal that array to set a constant specific length on the strings inside? Say I want each string in the array to also only allow a size of 100. I think I have Marshaled the array correctly it's just the strings in the array that worry me.
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct TestStructTwo
{
public int TestPropOne;
public int TestPropTwo;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 100)]
public string TestPropThree;
public int TestPropFour;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 100)]
public string TestPropFive;
//How do I set the STRING elements as having a limit of 100
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 5, ArraySubType = UnmanagedType.ByValTStr)]
public string[] TestPropSix;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 100)]
public string TestPropSeven;
}
For brevity and clarity the struct and variables have been shortened and renamed.
A BinaryReader is used to populate the following struct:
[StructLayout(LayoutKind.Sequential, Size = 59, CharSet = CharSet.Ansi, Pack = 1)]
public struct TheStruct
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 4)]
public string MyFirstString;
public int MyInt;
public short MyShort;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 8)]
public string MyNextString;
public byte MyByte;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string MyLastString;
public double MyDouble;
}
After reading in the data, all variables contain the correct information except the first member MyFirstString.
MyFirstString contains only the first three characters of the four characters that were read.
What is causing this and how do correct it?
Thanks to all who have posted struct examples in the past, they have been a great help to get me this far, but I can't seem to find anything that addresses this issue exactly.
The string is zero terminated, so only SizeConst - 1 characters will be placed in the struct, followed by the terminator character.
You can use a character array to get all characters:
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
public char[] MyFirstString;
In C++ header of a library there is the following code
#define STR_DATE 24+1
#define STR_SIZE 32+1
#define STR_SSIZE 64+1
#define STR_MSIZE 128+1
#define STR_LSIZE 1024+1
#define STR_IPSIZE 15+1
#define STR_MOD_SIZE 20+1
#define STR_AGESIZE 4+1
#define STR_GENDERSIZE 1+1
typedef struct ADO_PINFO{
char P_ID[STR_SSIZE];
char F_Name[STR_SSIZE];
char M_Name[STR_SSIZE];
char L_Name[STR_SSIZE];
char Reg_Num[STR_SSIZE];
UINT nGender;
UINT nAge;
COleDateTime BirthDay;
char csBirthDay[STR_SIZE];
COleDateTime V_Date;
char csV_Date[STR_SIZE];
char Address[_MAX_PATH];
char SubAddress[_MAX_PATH];
char Telephone[STR_SIZE];
char H_Phone[STR_SIZE];
char csMail[STR_SSIZE];
char csPicName[_MAX_PATH];
COleDateTime InDate;
char csInDate[STR_SIZE];
}*PADO_PINFO;
_ADODLL long ADO_AddPatientData(const ADO_PINFO &pPatientInfo);
I'm trying to import the dll to my C# application:
[StructLayout(LayoutKind.Sequential)]
public struct ADO_PINFO
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 65)] public string P_ID;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 65)] public string F_Name;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 65)] public string M_Name;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 65)] public string L_Name;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 65)] public string Reg_Num;
public uint nGender;
public uint nAge;
public DateTime BirthDay;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 33)] public string csBirthDay;
public DateTime V_Date;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 33)] public string csV_Date;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] public string Address;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] public string SubAddress;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 33)] public string Telephone;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 33)] public string H_Phone;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 65)] public string csMail;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] public string csPicName;
public DateTime InDate;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 33)] public string csInDate;
}
public class VatechLibrary
{
[DllImport("AdodllE.dll")]
public static extern long ADO_AddPatientData(ref ADO_PINFO patientInfo);
}
But when I try co call it:
var pInfo = new ADO_PINFO();
pInfo.P_ID = "77";
pInfo.F_Name = "name";
var res = VatechLibrary.ADO_AddPatientData(ref pInfo);
I get AccessViolationException. What am I doing wrong?
The problems that I can see:
Your marshalling of the COleDateTime fields is wrong. That's because COleDateTime is a C++ class and they are simply not valid types for binary interop. And .net DateTime certainly does not match. That's surely the source of your access violation.
The function returns a C++ long which is 32 bits wide on Windows. So your C# function declaration is wrong because C# long is 64 bits wide. Change the return value in the C# to int.
Your C# calling convention is stdcall. What is the calling convention of the C++ function? That's presumably contained in _ADODLL. You'll need to check that it is stdcall. If the calling convention is not specified, it is cdecl.
The issue with COleDateTime is the big one here. The others are easily fixed. Not so for COleDateTime. You could change the C++ code to accept an interop friendly representation of the date. If you cannot change the C++ code to deal with the issue of item 1, then your solution will involve writing a mixed mode C++/CLI wrapper.
From the docs for UnmanagedType.ByValTStr:
Used for in-line, fixed-length character arrays that appear within a
structure. The character type used with ByValTStr is determined by the
System.Runtime.InteropServices.CharSet argument of the
System.Runtime.InteropServices.StructLayoutAttribute attribute applied
to the containing structure. Always use the
MarshalAsAttribute.SizeConst field to indicate the size of the array.
.NET Framework ByValTStr types behave like C-style, fixed-size strings
inside a structure (for example, char s[5]).
What is your charset? My guess is something that is unicode.
If I were to guess I would say you need to specify the character encoding as ANSI on the struct layout in the C# strcuture declaration. ByValTStr will carry the encoding of the containing struct and based on your C++ struct using char I think you need them enoded as Ansi.
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)]
From MSDN http://msdn.microsoft.com/en-us/library/s9ts558h(v=vs.110).aspx
UnmanagedType.ByValTStr
A fixed-length array of characters; the array's type is determined by
the character set of the containing structure.
You may also need to add Pack=1 to your StructLayout attribute.
Most of your strings/arrays are an odd number of bytes long and by default .NET will pad each of those out to an even byte boundary.
You should review the structure, the type COleDateTime is a class C++ and not have to create an attribute to interop directly. Review the items you need to access and redo the layout.