Reputation: 347
Hey! I've just begun fiddling with pinvoke and have encountered a problem. I'm getting the AccessViolationException. First of all, is there some way to debug or trace out which field is causing this error? The only thing being written to is the result struct.
The c++ call looks like:
MyFunc(int var1, _tuchar *var2, _tuchar *var3, _tuchar *var4, MyStruct *Result,
_tuchar *var5, _tuchar *var6);
The c++ struct:
typedef struct MyStruct
{
_tuchar *id;
_tuchar *ErrorMessages;
int int1;
_tuchar language[3];
_tuchar *result;
int type;
int number;
int *type2;
_tuchar **blocks;
}
The C# struct:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct MyStruct
{
[MarshalAs(UnmanagedType.LPStr)]
public string Id;
[MarshalAs(UnmanagedType.ByValArray, SizeConst=500)]
public char[] ErrorMessages;
public int int1;
[MarshalAs(UnmanagedType.LPStr)]
public string language;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 15)]
public char[] result;
public int type;
public int number;
public int type2;
[MarshalAs(UnmanagedType.ByValArray)]
public string[] blocks;
The C# method declaration:
[DllImport(MyPath, EntryPoint = "MyEntryPoint", SetLastError = true,
CharSet = CharSet.Unicode)]
internal static extern int MyFunc(int var1, string var2, string var3,
string var4, ref MyStruct Result, string var5, string var6);
The C# Call:
var result = new MyStruct();
MyFunc(0, "var2", "var3", "var4", ref result, "var5", "var6");
Hope I haven't left anything out. Thanks for any help!
Upvotes: 5
Views: 2478
Reputation: 80915
Ooooh, man! You've picked quite a complex case for your first fiddling experience. I recommend doing something simpler first, and then moving on to the real stuff.
Firstly, CharSet=CharSet.Ansi
looks suspicious. All your strings and chars are _tuchar
, and I gather the u
in there means "Unicode", doesn't it? If that's the case, you need CharSet=CharSet.Unicode
.
Secondly, (and this is the most likely culprit) why is the ErrorMessages
field marshaled as ByValArray
? You know that ByVal
here means "by value", don't you? As in, not by reference. And you know that little asterisk thing in C++ means "reference", don't you? So why does your reference field ErrorMessages
marshaled as a by-value array? In case you don't know, an array is generally said to be passed "by value" when all of it's content is being passed, instead of just passing a reference (pointer) to a memory location where all that content is stored. In C++ struct definition, you specify _tuchar*
, which means "a reference (pointer) to some memory containing one or more of _tuchars", whereas in C# you specify [MarshalAs(UnmanagedType.ByValArray, SizeConst=500)]
, which means "500 _tuchars are supposed to be here, no more and no less". Seeing how a reference (pointer) usually takes 4 bytes (or 8 bytes on 64bit machines), and 500 unicode characters take 1000 bytes, you have an obvious mismatch right here.
Thirdly and fourthly, same point goes for result
and blocks
fields.
Fifthly, the language
field is exactly reverse situation: the C++ code says "there are 3 _tuchars here", while C# code says "there is a reference (pointer) to a string here" (in case you don't know, LPStr
means "Long Pointer to STRing")
And finally, after you have fixed all those problems, I recommend you execute your program and print out the result of call to Marshal.SizeOf( typeof( MyStruct ) )
. That will give you exactly how big your struct is, in .NET's opinion. The go on the C++ side and print out sizeof( MyStruct )
. That will give you what C++ thinks about the size.
If they turn out different, see what's wrong. Try to remove fields one by one, until they become same. This will give you the culprit field(s). Work with them.
Overall, I suggest you need a better understanding of how things work first. This case is waaaay too complex for a beginner.
Good luck!
Upvotes: 4
Reputation: 86789
This is a bit of a shot in the dark, but have you tried decorating the string parameters with MarshalAs(UnmanagedType.LPWStr)
:
[DllImport(MyPath, EntryPoint = "MyEntryPoint", SetLastError = true,
CharSet = CharSet.Unicode)]
internal static extern int MyFunc(
int var1,
[MarshalAs(UnmanagedType.LPWStr)]
string var2,
[MarshalAs(UnmanagedType.LPWStr)]
string var3,
[MarshalAs(UnmanagedType.LPWStr)]
string var4,
ref MyStruct Result,
[MarshalAs(UnmanagedType.LPWStr)]
string var5,
[MarshalAs(UnmanagedType.LPWStr)]
string var6);
I believe that the default marshaling chosen for strings is BStr
and _tuchar
should expand to wchar_t
so I'd guess that LPWStr
is the correct marshalling method (pointer to a wide character string).
Update: Various things on MyStruct
don't look quite right:
ErrorMessages
is marked as ByValArray
, and so the .Net interop is probably expecting MyStruct
to look a bit like this:
typedef struct MyStruct
{
_tuchar *id;
_tuchar ErrorMessages[500];
// Rest of MyStruct
Which is probably going to cause problems - same thing for result
.
Also I think that language
should be using ByValArray
with a size of 3.
Finally blocks
should probably be passed using LPArray
- ByValArray
doesn't seem right.
(This is all mostly guesswork btw - I hope this is pointing you in the right direction but I don't have that much experience with P/Invoke interop)
Another update: On MyStruct
you declare the charset to be Ansi, but on MyFunc
its Unicode
... is the unmanaged dll compiled with Unicode or Ansi? If it uses Unicode then I believe that you should use LPWStr
when marshalling strings, and with Ansi it should be LPStr
.
Upvotes: 0