need help with offsets for LayoutKind.Explicit - unravelling a returned struct from unmanage
Mathew Gertz pointed me in the right direction I believe in an earlier post.
My P/Invoke is returning valid values for 'name' below at field offset 0 and other values as I can see in QuickWatch.
StructA below is passed and returned from the unmanaged C++ dll with valid values.
An error is thrown however;
System.ExecutionEngineException was unhandled
Message="Exception of type 'System.ExecutionEngineException' was thrown."
I would like to try Mathew's idea of usingLayoutKind.Explicit rather than LayoutKind.Sequential which I am currently using.
If I had <FieldOffset(0)> for the first member of StructA, can anyone help me with how the rest would look?
I would say Offsets would increase by
1. 4 after each Integer
2. 8 after each double
But how much offset do I allocate for
1. the VBFixedString(128) datatype
2. StructB
3. StructC
Do StructB and StructC require the same attributes applied to them as well ("<StructLayout(Layout.......) ?
Thank you kindly for any help on this 'interesting challenge'
interesting that vb6 handled this translation but I am having to work at it to get it to work for .NET
-Greg
<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Ansi)> Public StructureStructA
<VBFixedString(128),System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.ByValArray, SizeConst:=128)> Publicname() As Char
Dim var2 As Integer
Dim var3 As Integer
Dim var4() As Double
Dim var5 As Double
Dim var6() As Double
Dim var7 As Double
Dim var8 As Double
Dim var9 As Integer
Dim var10 As Integer
Dim var11() AsStructB
Dim var12 AsStructC
Public Sub Initialize()
ReDim var4(2)
ReDim var6(2)
ReDim var11 (19)
var12.Initialize()
End Sub
End Structure
where
Public StructureStructB
Dim ivar1 As Integer
Dim ivar2 As Integer
Dim var3 As Double
Dim var4() As Double
Public Sub Initialize()
ReDim var4(2)
End Sub
End Structure
and
Public StructureStructC
Dim SegmentType As SEGMENTTYPE ' enumerated value
Dim var2 As Integer
Dim var3() As Double
Dim var4(,) As Double
Dim var5(,) As Double
Dim var6(,) As Double
Public Sub Initialize()
ReDim var3(2)
ReDim var4(2, 2)
ReDim var5(1, 5)
ReDim var6(1, 5)
End Sub
End Structure
int EXPORTED GetStructStuff(long Index, sStructA* pStructA) ' C++ function being called
[2815 byte] By [
hazz] at [2007-12-25]
Let me make a comment that if can avoid the sequential mode, it's probably better :)
Anyway: - the size of a char is dependent on encoding (1 byte for ascii, 2 bytes for unicode) - the vbfixedarray attribute should only affect visual basic io functions such as print, input, etc - not pinvoke/declare. however, the marshalas attribute with the byval array, should infact do what you want - and you have 128 chars in that array, so that's 128 or 256 bytes, depending on encoding.
Note, on your initialize routine, you're not initializing the array - if it contains a different size (or nothing) from what the attribute is telling it to marshal as when you pass it to the c++ function, it will throw an exception.
Arrays without the marshalas attribute set to byvalarray will be marshalled as pointers, so 4 bytes or 8 bytes depending on processor architecture.
For StructB and StructC, you'll have to add the size of all the elements in them. (you seem to only have an array of structB type, so that should only take 4 bytes on a 32 bit processor irrespective of the structure size or array length)
Thank you Alex.. I didn't really want to give up on this. ;-) (especially since the same method calls into the c++ dll were successfully done with vb6)
The C++ is not throwing an exception, that is happening on the vb.net side (FatalExecutionEngine Error) Are you indicating the array of chars need to be initialized?
Here is how I am picturing this so far(gulp.) I am hoping I don't have to resort to getting field and struct offsets from the C++ side in similart fashion to code at bottom.
<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Ansi)> Public Structure StructA
<FieldOffset(0)> <VBFixedString(128), System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.ByValArray, SizeConst:=128)> Public name() As Char
<FieldOffset(128)> Dim var2 As Integer
<FieldOffset(132)> Dim var3 As Integer
<FieldOffset(136)> Dim var4() As Double
<FieldOffset(144)> Dim var5 As Double
<FieldOffset(152)> Dim var6() As Double
<FieldOffset(160)> Dim var7 As Double
<FieldOffset(168)> Dim var8 As Double
<FieldOffset(176)> Dim var9 As Integer
<FieldOffset(180)> Dim var10 As Integer
<FieldOffset(184)> Dim var11() As StructB
<FieldOffset(208)> Dim var12 As StructC 'field offset = 184 + 24 from StructB below
Public Sub Initialize()
ReDim var4(2)
ReDim var6(2)
ReDim var11 (19)
var12.Initialize()
End Sub
End Structure
' total of 16 + 8(var4) = 24
Public Structure StructB
<FieldOffset(0)> Dim ivar1 As Integer
<FieldOffset(4)> Dim ivar2 As Integer
<FieldOffset(8)> Dim var3 As Double
<FieldOffset(16)> Dim var4() As Double
Public Sub Initialize()
ReDim var4(2)
End Sub
End Structure
************************************************* sample code to get struct and field offsets from within vc++ **********************************
http://72.14.253.104/search?q=cache:lmAKtkUCMWsJ:www.informit.com/guides/content.asp%3Fg%3Ddotnet%26seqNum%3D527+StructLayout+packing&hl=en&gl=us&ct=clnk&cd=5
void ShowField(char * name, void * pField, void * pRec, int size)
{
printf("%s: offset=%d, size=%d\n", name, (int)((char *)pField - (char *)pRec), size);
}
void ShowInputRecord()
{
INPUT_RECORD rec;
printf("Input record\n");
printf("\n");
printf("size = %d\n", sizeof(rec));
ShowField("EventType", &rec.EventType, &rec, sizeof(rec.EventType));
ShowField("KeyEvent", &rec.Event.KeyEvent, &rec, sizeof(rec.Event.KeyEvent));
ShowField("MouseEvent", &rec.Event.MouseEvent, &rec, sizeof(rec.Event.MouseEvent));
ShowField("WindowBufferSizeEvent", &rec.Event.WindowBufferSizeEvent, &rec,
sizeof(rec.Event.WindowBufferSizeEvent));
ShowField("MenuEvent", &rec.Event.MenuEvent, &rec, sizeof(rec.Event.MenuEvent));
ShowField("FocusEvent", &rec.Event.FocusEvent, &rec, sizeof(rec.Event.FocusEvent));
printf("\n");
}
Reading Dan Applemans book, Moving to VB.NET, he implies that you will almost always use Sequential, but he also uses a Pack:=1 parameter, in a specific example. You may need to use a different 'pack' parameter:
<StructLayout(LayoutKind.Sequential, Pack:=1)> Public Structure...
And he also indicates that any arrays must be initialized prior to using the structure. Perhaps that helps...
Thanks again Stephen. I tried Pack:= 1,2, and 4 (like a code monkey) and that didn't seem to help. Without that (for sequential) it actually makes it to the unmanaged code and back, with known good variable values for some of the fields of the structure( eg. the name with the array of chars(128)
When I decorate the struct with the packed attribute, it never gets out of the vb.net runtime without erroring out. (Attempted to read or write protected memory)
arrays need to be initialized...not just redim'd?
Thanks,
-Greg
You do need to initialize Public name() As Char with a new char(128) {}
In general what happens here when you do a call with declare, is that there is a marshalling layer that looks at the vb managed data, and creates a new memory block(s) according to either the default marshalling behavior or the attributes placed on the data and/or method declaration, copies the data to the new memory representation, and passes that to the C++ dll.
But if you tell the marshaller that it's supposed to create a structure with 128 ansi chars, it expects the original array to hold 128 chars. You need to do that initialization even if you use the attribute.
I really appreciate your explanations Alex.
When I new up the 'name' field
Public Sub Initialize()name =
New Char(128) {}I still get the successful return of that "space capsule", as I did before, even if I didn't do so. I wonder if the
SizeConst:=128 has something to do with that in that fields marshalling definition?
A quickwatch inspection of the returned value in either case:
Dim
sStruct As StructACall
GetStructValues(i, sStruct)sStruct.name =
sStruct.name(0) "M"c
sStruct.name(1) "y"c
sStruct.name(2) "W"c
sStruct.name(3) "o"c
sStruct.name(4) "r"c
sStruct.name(5) "d"c
sStruct.name(6) Nothing
sStruct.name(7) Nothing
sStruct.name..
sStruct.name(128) Nothing
Interesting - I was pretty sure that you had to initialize the array for it to work - maybe the marshaller is doing more than I remember. (Btw, redimming an array and assigning a new array to it are pretty much the same thing)
In any case, now I'm confused - is the result you have above incorrect?
Also, I'd suggest for you to post your vb6 code - maybe we can figure out what needs to be corrected from it.
Yes the above was correct. The dll sent what I have listed below back, as I F11'd through the vb.net code.Simulaneously the following error is thrown. I wonder if the values of 'Nothing' are the source of the problem? (value for StructB and 2 of the doubles)Note char(0) - (127)
FatalExecutionEngineError was detected
Message: The runtime has encountered a fatal error. The address of the error was at 0x79e8a634, on thread 0x19c8. The error code is 0xc0000005. This error may be a bug in the CLR or in the unsafe or non-verifiable portions of user code. Common sources of this bug include user marshaling errors for COM-interop or PInvoke, which may corrupt the stack.
Here are actual values (select all from watch window)...var names were changed, but otherwise all from the returned struct from the dll. Quickwatch displays the values in a different order from which they exist in Struct A.
name value type
structA {MainForm.DataTypes.StructA} MainForm.DataTypes.StructA
var 11 Nothing MainForm.DataTypes.StructB()
var 10 0 Integer
var 2 0 Integer
+ var12 {MainForm.DataTypes.StructB} MainForm.DataTypes.StructB
var8 0.0 Double
- name {Length=128} Char()
(0) "M"c Char
(1) "y"c Char
(2) "W"c Char
(3) "o"c Char
(4) "R"c Char
(5) "d"c Char
(6) "e"c Char
(7) "e"c Char
(8) Nothing Char
(9) Nothing Char
(10) Nothing Char
(11) Nothing Char
(12) Nothing Char
(13) Nothing Char
(14) Nothing Char
(15) Nothing Char
(16) Nothing Char
(17) Nothing Char
(18) Nothing Char
(19) Nothing Char
(20) Nothing Char
(21) Nothing Char
(22) Nothing Char
(23) Nothing Char
(24) Nothing Char
(25) Nothing Char
(26) Nothing Char
(27) Nothing Char
(28) Nothing Char
(29) Nothing Char
(30) Nothing Char
(31) Nothing Char
(32) Nothing Char
(33) Nothing Char
(34) Nothing Char
(35) Nothing Char
(36) Nothing Char
(37) Nothing Char
(38) Nothing Char
(39) Nothing Char
(40) Nothing Char
(41) Nothing Char
(42) Nothing Char
(43) Nothing Char
(44) Nothing Char
(45) Nothing Char
(46) Nothing Char
(47) Nothing Char
(48) Nothing Char
(49) Nothing Char
(50) Nothing Char
(51) Nothing Char
(52) Nothing Char
(53) Nothing Char
(54) Nothing Char
(55) Nothing Char
(56) Nothing Char
(57) Nothing Char
(58) Nothing Char
(59) Nothing Char
(60) Nothing Char
(61) Nothing Char
(62) Nothing Char
(63) Nothing Char
(64) Nothing Char
(65) Nothing Char
(66) Nothing Char
(67) Nothing Char
(68) Nothing Char
(69) Nothing Char
(70) Nothing Char
(71) Nothing Char
(72) Nothing Char
(73) Nothing Char
(74) Nothing Char
(75) Nothing Char
(76) Nothing Char
(77) Nothing Char
(78) Nothing Char
(79) Nothing Char
(80) Nothing Char
(81) Nothing Char
(82) Nothing Char
(83) Nothing Char
(84) Nothing Char
(85) Nothing Char
(86) Nothing Char
(87) Nothing Char
(88) Nothing Char
(89) Nothing Char
(90) Nothing Char
(91) Nothing Char
(92) Nothing Char
(93) Nothing Char
(94) Nothing Char
(95) Nothing Char
(96) Nothing Char
(97) Nothing Char
(98) Nothing Char
(99) Nothing Char
(100) Nothing Char
(101) Nothing Char
(102) Nothing Char
(103) Nothing Char
(104) Nothing Char
(105) Nothing Char
(106) Nothing Char
(107) Nothing Char
(108) Nothing Char
(109) Nothing Char
(110) Nothing Char
(111) Nothing Char
(112) Nothing Char
(113) Nothing Char
(114) Nothing Char
(115) Nothing Char
(116) Nothing Char
(117) Nothing Char
(118) Nothing Char
(119) Nothing Char
(120) Nothing Char
(121) Nothing Char
(122) Nothing Char
(123) Nothing Char
(124) Nothing Char
(125) Nothing Char
(126) Nothing Char
(127) Nothing Char
var9 0 Integer
var3 -1 Integer
var5 0.0 Double
var6 Nothing Double()
var7 0.0 Double
var4 Nothing Double()
The VB6 code structure declarations are the same except for the longs which were replaced with integers for VB.net. and the char * 128 as replaced with the new marshalled char type. And the declare just changed from Long to Integer as needed. Initialization was handled a bit differently
var11(19) As StructB vs var11() as StructB followed by the Sub Initialize ReDim Var11(19)
Public Declare Function GetStructA _
Lib "foo.dll" _
(ByVal lIndex As Long, ByRef sStructA As StructA ) As Long
There are a couple of weird things here - var11, var6 and var4 are nothing, even though you seem to initialize them in code.
Second thing, you initialize var11 to have 20 elements, but you don't initialize each of them - that means that each of the structb instances will have an array with nothing when passed to the c++ function, you may want to try also initializing that.
thanks for spotting that!
Now I'm wondering why none of the ReDims (it seems) in StructA's Initialize's Sub took effect then? Those are all the ones with the value of Nothing ?!
<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Ansi)> Public Structure StructA
<VBFixedString(128), System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.ByValArray, SizeConst:=128)> Public name() As Char
........
var4
var5
var6
.......
Public Sub Initialize()
name = New Char(128) {}
ReDim var4(2)
ReDim var6(2)
ReDim var11(19)
var12.Initialize()
End Sub
End Structure
I seem to recall some issue with array copy back on marshalling (for non ByVal arrays) - it's a been a while since I've dealt with this, though. In any case, if you have the vb6 code laying around, and/or the C++ structure definition, we may be able to figure out what needs to be done.
Alex
Thanks Alex.
As I included at the tail end of a previous post, the vb6 fields withing the structs intialize as they are declared rather than redimming in the Initiliaze Sub.That Initiaze Sub does not seem to doing its job. I instantiated the Struct to see if that would make a difference before using it (rather than just declaring it) but that didn't help. hmm.
vb6 var11(19) As StructB
.net var11() as StructB
Public Sub Initialize
ReDim Var11(19)
End Sub
Ouch. Now alll struct types have been initialized before being sent through the p/invoke layer but I am only an F11 away from
- ex {"The parameter is incorrect. (Exception from HRESULT: 0x80070057 (E_INVALIDARG))"} System.Exception
+ System.ArgumentException {"The parameter is incorrect. (Exception from HRESULT: 0x80070057 (E_INVALIDARG))"} System.ArgumentException
+ Data {System.Collections.ListDictionaryInternal} System.Collections.IDictionary
HelpLink Nothing String
InnerException Nothing System.Exception
Message "The parameter is incorrect. (Exception from HRESULT: 0x80070057 (E_INVALIDARG))" String
StackTrace " at MainForm.Module1.GetStructA(Int32 lIndex, StructA& objStructA)
+ TargetSite {System.Reflection.RuntimeMethodInfo} System.Reflection.MethodBase
question for me is: Which parameter is incorrect? I'm using LayoutKind.Sequential here.
I was rereading Matt's observation in my previous thread,
"Looking at your code, unless I'm reading this wrong, the library function is essentially expecting a pointer to a pointer to a char "
Well, if that's the case then it looks like a structure won't work. I need to use a class now as per msdn help
ms-help://MS.VSCC.v80/MS.MSDN.v80/MS.VisualStudio.v80.en/dv_fxinterop/html/9b92ac73-32b7-4e1b-862e-6d8d950cf169.htm
Use either a structure passed by reference or a class passed by value when the unmanaged function demands one level of indirection.
Use a class passed by reference when the unmanaged function demands two levels of indirection.
I think this is a serious enough question to spawn a new thread.