Reputation: 3072
I have the following structs defined in my c dll:
typedef struct {
char Name[10];
char Flag[10];
} CountryData;
typedef struct {
int NumElements;
TrackData Elements[1000];
} CountryArray;
Which is exposed like so:
__declspec( dllexport ) CountryArray* _cdecl GetAllCountries()
{
CountryArray fakedata;
CountryData fakecountry = CountryData();
strcpy_s(fakecountry.Name, "TEST");
strcpy_s(fakecountry.Flag, "TEST");
fakedata.Elements[0] = faketrack;
fakedata.NumElements = 1;
return new CountryArray(fakedata);
}
Now in c# I have these structs defined:
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)]
public struct COUNTRY
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 10)]
public string Name;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 10)]
public string Flag;
}
[StructLayout(LayoutKind.Sequential)]
public struct COUNTRY_ARRAY
{
public int NumElements;
public IntPtr Elements;
}
And I access it through this import:
[DllImport("Countries.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "?GetAllCountries@@YAPAUCountryArray@@XZ")]
public static extern IntPtr GetAllCountries();
And finally I attempt to marshal the data like so:
IntPtr countryPtr = Natives.GetAllCountries();
Natives.COUNTRY_ARRAY countries = (Natives.COUNTRY_ARRAY)Marshal.PtrToStructure(countryPtr, typeof(Natives.COUNTRY_ARRAY));
for (int i = 0; i < countries.NumElements; i++)
{
IntPtr iptr = (IntPtr)(countries.Elements.ToInt32() + (i * Marshal.SizeOf(typeof(Natives.COUNTRY))));
try
{
//fails here
Natives.COUNTRY country = (Natives.COUNTRY)Marshal.PtrToStructure(iptr, typeof(Natives.COUNTRY));
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
The marshalling of the country is where I receive this error:
System.AccessViolationException: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
at System.Runtime.InteropServices.Marshal.PtrToStructureHelper(IntPtr ptr, Object structure, Boolean allowValueClasses)
I've tried modifying the size of the COUNTRY struct and changing the charset but still get this error. I'm completely stuck, what can be the issue here?
Upvotes: 0
Views: 1745
Reputation: 14688
So your issue is that you're treating the value of Elements
like a pointer, when in fact it's the data of the first element of the array. In your test case, this gives Elements
a value of 0x54455354
on a 32-bit system and 0x5445535400000000
on a 64-bit system ("TEST" in hex). You are offsetting that pointer. Instead, you want to get the pointer to the Elements
field, which can be done like so:
IntPtr elementsOffset = Marshal.OffsetOf(typeof(COUNTRY_ARRAY), "Elements");
You would then take the pointer you get back from the function and add the offset to it when calculating the location of each element.
Also, you should always use IntPtr's ToInt64()
method. It's a tad bit slower on 32-bit systems but avoids the terrible edge case of truncating memory addresses in 64-bit systems.
Additionally, the name mangling on your entry point leads me to believe that this is being compiled with a C++ compiler. Your bindings will fail if you compile with a different compiler or even a different version of the same compiler. What you should do is export these methods as C functions so that they don't get mangled. To do this, wrap the methods you're exporting in an extern "C" { ... }
block.
Also, you can avoid this issue entirely by letting the struct do the work for you:
[StructLayout(LayoutKind.Sequential)]
public struct COUNTRY_ARRAY
{
public int NumElements;
[MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.Struct, SizeConst = 1000)]
public COUNTRY[] Elements;
}
Upvotes: 1