VB.NET Structures and Union Help.

mavromatis

Member
Joined
Feb 7, 2006
Messages
19
Programming Experience
1-3
I have a chunk of VC.NET code (below) that I need to convert to VB.NET syntax. Could someone help me get started? I'm new to structures and unions and I don't understand how to nest them in VB.NET. Ideally, I'd like to see an example using the code I have below, so I it can start me off or is there a better way to handle this type of structure in VB.NET?

VB.NET:
' ----- VC.NET CODE THAT I NEED TO CONVERT TO VB.NET ----
 
struct R_OMNI_LINK_MESSAGE {
  //unsigned char StartChar;
  unsigned char MessageLength;
  union {
    unsigned char Data[255];
    struct {
      unsigned char MessageType;
      union {
        struct /* olmNAME_DATA (8 bit) */ {
          unsigned char ItemType8;
          unsigned char ItemNumber8;
          unsigned char ItemName8[16];
        };
        struct /* olmNAME_DATA (16 bit) */ {
          unsigned char ItemType16;
          unsigned char ItemNumber16MSB;
          unsigned char ItemNumber16LSB;
          unsigned char ItemName16[16];
        };
        struct /* olmEVENT_LOG_DATA */ {
          unsigned char EventNumber;  // (1-N, With 1 Being Most Recent)
          unsigned char EventTimeDateValid;
          unsigned char EventMonth;  // (1-12)
          unsigned char EventDay;  // (1-31)
          unsigned char EventHour;  // (0-23)
          unsigned char EventMinute;  // (0-59)
          unsigned char EventType;
          unsigned char EventParameter1;
          unsigned char EventParameter2High;
          unsigned char EventParameter2Low;
        };
        struct /* olmCOMMAND_MESSAGE */ {
          unsigned char Command;
          unsigned char Parameter1;
          unsigned char Parameter2High;
          unsigned char Parameter2Low;
        };
        struct /* olmSET_TIME */ {
          unsigned char stYear;
          unsigned char stMonth;
          unsigned char stDay;
          unsigned char stDOW;
          unsigned char stHour;
          unsigned char stMinute;
          unsigned char stDST;
        };
        struct /* olmSYSTEM_INFORMATION */ {
          unsigned char ModelNumber;
          unsigned char MajorVersion;
          unsigned char MinorVersion;
          unsigned char Revision;
          unsigned char LocalPhoneNumber[25];
        };
        struct /* olmSYSTEM_STATUS */ {
          unsigned char TimeDateValidFlag;
          unsigned char Year;  // (0-99)
          unsigned char Month;  // (1-12)
          unsigned char Day;  // (1-31)
          unsigned char DayOfWeek;  // (1-7)
          unsigned char Hour;  // (0-23)
          unsigned char Minute;  // (0-59)
          unsigned char Second;  // (0-59)
          unsigned char DaylightSavingsTimeFlag;
          unsigned char CalculatedSunriseHour;  // (0-23)
          unsigned char CalculatedSunriseMinute;  // (0-59)
          unsigned char CalculatedSunsetHour;  // (0-23)
          unsigned char CalculatedSunsetMinute;  // (0-59)
          unsigned char BatteryReading;
          unsigned char AreaSecurityMode[8];  // index 0-7
          struct {
            unsigned char Status;
            unsigned char BatteryReading;
          } ExpansionEnclosure[8];  // index 0-7
        };
        struct /* olmREQUEST_ZONE_STATUS */ {
          unsigned char StartingZone;
          unsigned char EndingZone;
        };
        struct /* olmZONE_STATUS */ {
          unsigned char ZoneStatus;
          unsigned char AnalogLoopReading;
        } Zone[127];
        struct /* olmREQUEST_UNIT_STATUS (8 bit) */ {
          unsigned char StartingUnit;
          unsigned char EndingUnit;
        };
        struct /* olmREQUEST_UNIT_STATUS (16 bit) */ {
          unsigned char StartingUnitMSB;
          unsigned char StartingUnitLSB;
          unsigned char EndingUnitMSB;
          unsigned char EndingUnitLSB;
        };
        struct /* olmUNIT_STATUS */ {
          unsigned char CurrentCondition;
          unsigned char HighByteOfTime;
          unsigned char LowByteOfTime;
        } Unit[84];
        struct /* olmREQUEST_AUXILIARY_STATUS */ {
          unsigned char StartingTemperatureSensor;
          unsigned char EndingTemperatureSensor;
        };
        struct /* olmAUXILIARY_STATUS */ {
          unsigned char RelayStatus;
          unsigned char CurrentTemperature;
          unsigned char LowHeatTemperatureSetpoint;
          unsigned char HighCoolTemperatureSetpoint;
        } TempSensor[63];
        struct /* olmREQUEST_THERMOSTAT_STATUS */ {
          unsigned char StartingThermostat;
          unsigned char EndingThermostat;
        };
        struct /* olmTHERMOSTAT_STATUS */ {
          unsigned char StatusByte;
          unsigned char CurrentTemperature;
          unsigned char HeatSetpoint;
          unsigned char CoolSetpoint;
          unsigned char SystemMode;
          unsigned char FanMode;
          unsigned char HoldStatus;
        } Thermostat[36];
        struct /* olmLOGIN */ {
          unsigned char LoginCode1;
          unsigned char LoginCode2;
          unsigned char LoginCode3;
          unsigned char LoginCode4;
        };
        struct /* olmSYSTEM_EVENTS */ {
          unsigned char High;
          unsigned char Low;
        } SystemEvent[127];
        struct /* olmMESSAGE_STATUS */ {
          unsigned char Data;
        } MessageStatus[33];
        struct /* olmREQUEST_SECURITY_CODE_VALIDATION */ {
          unsigned char AreaNumber;  // (1-8)
          unsigned char Code1;  // First Digit Of Code
          unsigned char Code2;  // Second Digit Of Code
          unsigned char Code3;  // Third Digit Of Code
          unsigned char Code4;  // Fourth Digit Of Code
        };
        struct /* olmSECURITY_CODE_VALIDATION */ {
          unsigned char UserCodeNumber;  // (1-99, 251 for duress, 0 if 
invalid)
          unsigned char AuthorityLevel; 
//(0=Invalid,1=Master,2=Manager,3=User)
        };
      };  // union
    };  // struct
  }; // union
};

Thanks,
Danny
 
Last edited:
Ahh... I see where you are going...

So I would read the first byte from Data(), if it's &H0B (olmNAME_DATA), then remove the first byte from Data() and pass it to the NAME_DATA structure. Which would let me reference the data by name. Am I reading you right?

Makes sense... don't see why it wouldn't work... :confused:

However, I still think there are problems with the structures that have arrays... how should those be handled?

Specifically,
VB.NET:
' olmNAME_DATA
<StructLayout(LayoutKind.Explicit)> _
PublicStructure olmNAME_DATA
' ++++++++++++++++++++++++++++++++++++
' 8 bit
<FieldOffset(3)> Public ItemType8 AsByte
<FieldOffset(4)> Public ItemNumber8 AsByte
<FieldOffset(5), MarshalAs(UnmanagedType.ByValArray, SizeConst:=16)> _
Public ItemName8() AsByte
'
' 16 bit
<FieldOffset(3)> Public ItemType16 AsByte
<FieldOffset(4)> Public ItemNumber16MSB AsByte
<FieldOffset(5)> Public ItemNumber16LSB AsByte
<FieldOffset(6), MarshalAs(UnmanagedType.ByValArray, SizeConst:=16)> _
Public ItemName16() AsByte
' ++++++++++++++++++++++++++++++++++++
EndStructure
 
That is not a problem since they are Seqential, there is no overlapping of different type objects for sequential data.

(Actually the problem for those is not only overlapping, I tested with only one member, and it errors out still, then runs fine when member positioned at another offset, this is not possible to do when running the code in actual environment since the bytes are positioned where they are supposed to be. I think this is a bug with InterOpServices.)

It's the same thing with R_OMNI_LINK_MESSAGE_data structure I posted previous message, that also got an array member.
 
John...

If I set up the structure as StructLayout(LayoutKind.Auto), it seems to compile!? :D

This now compiles!! I take it the Auto, is what we need?

VB.NET:
<StructLayout(LayoutKind.Auto)> _
Public Structure R_OMNI_LINK_MESSAGE
<FieldOffset(0)> Public MessageLength As Byte
<FieldOffset(1), MarshalAs(UnmanagedType.ByValArray, SizeConst:=255)> Public Data() As Byte
<FieldOffset(1)> Public MessageType As Byte
<FieldOffset(2)> Public NAME_DATA As olmNAME_DATA ' 8bit / 16bit
<FieldOffset(2)> Public EVENT_LOG_DATA As olmEVENT_LOG_DATA
<FieldOffset(2)> Public COMMAND_MESSAGE As olmCOMMAND_MESSAGE
<FieldOffset(2)> Public SET_TIME As olmSET_TIME
<FieldOffset(2)> Public SYSTEM_STATUS As olmSYSTEM_STATUS
<FieldOffset(2)> Public REQUEST_ZONE_STATUS As olmREQUEST_ZONE_STATUS
<FieldOffset(2), MarshalAs(UnmanagedType.ByValArray, SizeConst:=127)> Public Zone() As olmZONE_STATUS
<FieldOffset(2)> Public REQUEST_UNIT_STATUS As olmREQUEST_UNIT_STATUS '8bit / 16bit
<FieldOffset(2), MarshalAs(UnmanagedType.ByValArray, SizeConst:=84)> Public Unit() As olmUNIT_STATUS
<FieldOffset(2)> Public REQUEST_AUXILIARY_STATUS As olmREQUEST_AUXILIARY_STATUS
<FieldOffset(2), MarshalAs(UnmanagedType.ByValArray, SizeConst:=63)> Public TempSensor() As olmAUXILIARY_STATUS
<FieldOffset(2)> Public REQUEST_THERMOSTAT_STATUS As olmREQUEST_THERMOSTAT_STATUS
<FieldOffset(2), MarshalAs(UnmanagedType.ByValArray, SizeConst:=36)> Public Thermostat() As olmTHERMOSTAT_STATUS
<FieldOffset(2)> Public LOGIN As olmLOGIN
<FieldOffset(2), MarshalAs(UnmanagedType.ByValArray, SizeConst:=127)> Public SystemEvent() As olmSYSTEM_EVENTS
<FieldOffset(2), MarshalAs(UnmanagedType.ByValArray, SizeConst:=33)> Public MessageStatus() As olmMESSAGE_STATUS
<FieldOffset(2)> Public REQUEST_SECURITY_CODE_VALIDATION As olmREQUEST_SECURITY_CODE_VALIDATION
<FieldOffset(2)> Public SECURITY_CODE_VALIDATION As olmSECURITY_CODE_VALIDATION
End Structure
 
John,

I found this http://www.dotnet247.com/247reference/msgs/5/27796.aspx, read last post. I think we need to do the overloaded method.


Chetan N ParmarHi,
Unions are not natively supported by the .NET runtime. However you can
workaround it by defining structs. In managed code, value types and
reference types are not permitted to overlap. For e.g : if you have the
following
union MYUNION
{
int number;
double d;
}

union MYUNION2
{
int i;
char str[128];
};
In managed code, unions are defined as structures. The MYUNION structure
contains two value types as its members: an integer and a double. The
StructLayoutAttribute attribute is set to control the precise position of
each data member. The FieldOffsetAttribute attribute provides the physical
position of fields within the unmanaged representation of a union. Notice
that both members have the same offset values, so the members can define
the same piece of memory. So it can be expressd in C# as follows:
[ StructLayout( LayoutKind.Explicit )]
public struct MyUnion
{
[ FieldOffset( 0 )]
public int i;
[ FieldOffset( 0 )]
public double d;
}

However for MYUNION2 you cannot do the same bacuse string is a reference
type and explicit layouts are not permitted with reference types. To work
around that you can use method overloading to use both types when calling
the same unmanaged function. This requires your to have two mini structs
for your union so that you split them based on value types in one and
reference types in others. So how is it going to look:
[ StructLayout( LayoutKind.Explicit, Size=128 )]
public struct MyUnion2_1
{
[ FieldOffset( 0 )]
public int i;
}
[ StructLayout( LayoutKind.Sequential )]
public struct MyUnion2_2
{
[ MarshalAs( UnmanagedType.ByValTStr, SizeConst=128 )]
public String str;
}
Mark the Sequential layout for MyUnion2_2. You would also have to add the
following DllImport for these overloaded methods
[ DllImport( "..\\LIB\\PinvokeLib.dll" )]
public static extern void TestUnion( MyUnion u, int type );
[ DllImport( "..\\LIB\\PinvokeLib.dll" )]
public static extern void TestUnion2( MyUnion2_1 u, int type );
[ DllImport( "..\\LIB\\PinvokeLib.dll" )]
public static extern void TestUnion2( MyUnion2_2 u, int type );

The Microsoft .NET Framework SDK includes the complete Visual Basic .NET
and C# versions of this sample in
Samples\Technologies\Interop\Platform-Invoke.

Thanks!
Hope that helps.

Regards
 
Auto can't be used outside managed code.

I read that bug detail earlier this week actually, it's not applicable in this case, because there is noe room to move before the required offset and expand it, and it can't be moved to a later offset because the specified offset is where the unmanaged code provides the data. I called it a 'bug', they call it a 'feature', leading to say that the unmanaged provider here is not compatible with the rules of managed code, so this Omni stuff is layed out wrong in relation to managed world (but is valid in unmanaged).

I didn't get passed the problem with any combination of 'features' using Class instead of Structure either. Seems like when it's not an overlapping problem it's an alignment problem, and both are required to work with the full structure layout here.

So I can't say more than to try to 'hack' the Data bytes array on the go.
 
I just that last message of yours, I didn't understand how that was intended to work.
 
The MSFT documented method for doing unions in .NET is located here -- http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguide/html/cpconUnionsSample.asp

I'm thinking that is what we need to do... one is set as explicit the other as sequential. The Omni data structure is laidout so it can be viewed as (union between) the data array (255) or by name (structures).

I believe the following code shows how to do this... In the following example, it shows how MyUnion2_1 and MyUnion2_2 is actually TestUnion2. Is this it.. ?

VB.NET:
[Visual Basic]
' Declares managed structures instead of unions.
< StructLayout( LayoutKind.Explicit )> _
Public Structure MyUnion
   < FieldOffset( 0 )> Public i As Integer
   < FieldOffset( 0 )> Public d As Double
End Structure 'MyUnion
 
< StructLayout( LayoutKind.Explicit, Size := 128 )> _
Public Structure MyUnion2_1
   < FieldOffset( 0 )> Public i As Integer
End Structure 'MyUnion2_1
 
< StructLayout( LayoutKind.Sequential )> _
Public Structure MyUnion2_2
   < MarshalAs( UnmanagedType.ByValTStr, SizeConst := 128 )> _
   Public str As String
End Structure 'MyUnion2_2
 
Public Class LibWrap
   ' Declares managed prototypes for unmanaged function.
   Declare Sub TestUnion Lib "..\LIB\PinvokeLib.dll" ( _
      ByVal u As MyUnion, ByVal type As Integer )
   Overloads Declare Sub TestUnion2 Lib "..\LIB\PinvokeLib.dll" ( _
      ByVal u As MyUnion2_1, ByVal type As Integer )
   Overloads Declare Sub TestUnion2 Lib "..\LIB\PinvokeLib.dll" ( _
      ByVal u As MyUnion2_2, ByVal type As Integer )
End Class 'LibWrap


So, in our code, it would look something like this?

VB.NET:
< StructLayout( LayoutKind.Explicit, Size := 256 )> _
 Public Structure R_OMNI_MESSAGE_1
 <FieldOffset(0)> Public MessageLength As Byte
<FieldOffset(1)> Public MessageType As Byte
<FieldOffset(2)> Public NAME_DATA As olmNAME_DATA ' 8bit / 16bit
<FieldOffset(2)> Public EVENT_LOG_DATA As olmEVENT_LOG_DATA
<FieldOffset(2)> Public COMMAND_MESSAGE As olmCOMMAND_MESSAGE
<FieldOffset(2)> Public SET_TIME As olmSET_TIME
<FieldOffset(2)> Public SYSTEM_STATUS As olmSYSTEM_STATUS
<FieldOffset(2)> Public REQUEST_ZONE_STATUS As olmREQUEST_ZONE_STATUS
<FieldOffset(2), MarshalAs(UnmanagedType.ByValArray, SizeConst:=127)> Public Zone() As olmZONE_STATUS
<FieldOffset(2)> Public REQUEST_UNIT_STATUS As olmREQUEST_UNIT_STATUS '8bit / 16bit
<FieldOffset(2), MarshalAs(UnmanagedType.ByValArray, SizeConst:=84)> Public Unit() As olmUNIT_STATUS
<FieldOffset(2)> Public REQUEST_AUXILIARY_STATUS As olmREQUEST_AUXILIARY_STATUS
<FieldOffset(2), MarshalAs(UnmanagedType.ByValArray, SizeConst:=63)> Public TempSensor() As olmAUXILIARY_STATUS
<FieldOffset(2)> Public REQUEST_THERMOSTAT_STATUS As olmREQUEST_THERMOSTAT_STATUS
<FieldOffset(2), MarshalAs(UnmanagedType.ByValArray, SizeConst:=36)> Public Thermostat() As olmTHERMOSTAT_STATUS
<FieldOffset(2)> Public LOGIN As olmLOGIN
<FieldOffset(2), MarshalAs(UnmanagedType.ByValArray, SizeConst:=127)> Public SystemEvent() As olmSYSTEM_EVENTS
<FieldOffset(2), MarshalAs(UnmanagedType.ByValArray, SizeConst:=33)> Public MessageStatus() As olmMESSAGE_STATUS
<FieldOffset(2)> Public REQUEST_SECURITY_CODE_VALIDATION As olmREQUEST_SECURITY_CODE_VALIDATION
<FieldOffset(2)> Public SECURITY_CODE_VALIDATION As olmSECURITY_CODE_VALIDATION
 End Structure
 
< StructLayout( LayoutKind.Sequential )> _
Public Structure R_OMNI_MESSAGE_2
<MarshalAs(UnmanagedType.ByValArray, SizeConst:=255)> [SIZE=2][COLOR=#0000ff]Public[/COLOR][/SIZE][SIZE=2] Data() [/SIZE][SIZE=2][COLOR=#0000ff]As[/COLOR][/SIZE][SIZE=2][COLOR=#0000ff]Byte[/COLOR][/SIZE]
End Structure
 
Public Class LibWrap
   ' Declares managed prototypes for unmanaged function.
     Overloads Declare Sub R_OMNI_LINK_MESSAGE Lib "..\LIB\PinvokeLib.dll" ( _
      ByVal u As R_OMNI_MESSAGE_1, ByVal type As Integer )
   Overloads Declare Sub R_OMNI_LINK_MESSAGE Lib "..\LIB\PinvokeLib.dll" ( _
      ByVal u As R_OMNI_MESSAGE_2, ByVal type As Integer )
End Class 'LibWrap

Maybe?
 
What they do there is just dividing parts of structures that are not compatible (e.g. member types blittable/non-blittable). That is the problem of overlapping types.

The other problem is the 'alignment', that is the offset of specific types (e.g. non-blittable).

I'm starting to have doubts even for the Data byte array hack because of this. The following illustrates this, remember the small Data structure I posted not long ago? Here it is again:


VB.NET:
 [LEFT]<StructLayout(LayoutKind.[COLOR=red]Sequential[/COLOR], Size:=256)> _
Public Structure R_OMNI_LINK_MESSAGE_data
  Public MessageLength As Byte
  <MarshalAs(UnmanagedType.ByValArray, SizeConst:=255)> _
  Public Data() As Byte
End Structure

[/LEFT]

Now this looks all fine, but when you try it with Explicit it will error out like this: (same both as Structure and Class)
VB.NET:
<StructLayout(LayoutKind.[COLOR=red]Explicit[/COLOR], Size:=256)> _
Public Structure R_OMNI_LINK_MESSAGE_data
  <FieldOffset(0)> Public MessageLength As Byte
  <MarshalAs(UnmanagedType.ByValArray, SizeConst:=255)> _
  <FieldOffset([COLOR=red]1[/COLOR])> Public Data() As Byte 
End Structure
Now, here is the clue about alignment, change the offset of the Data array to 4 and it falls into a place that is allowed by the InterOpServices and compiles.
VB.NET:
[LEFT]<StructLayout(LayoutKind.[COLOR=red]Explicit[/COLOR], Size:=256)> _[/LEFT]
Public Structure R_OMNI_LINK_MESSAGE_data
  <FieldOffset(0)> Public MessageLength As Byte
  <MarshalAs(UnmanagedType.ByValArray, SizeConst:=255)> _
  <FieldOffset([COLOR=red]4[/COLOR])> Public Data() As Byte 
End Structure
This leads me to conclude that using the Sequential setup actually will mis-read/write the bytes.

There is an article here that touches this subject: http://www.vsj.co.uk/articles/display.asp?id=501

If I have drawn the right conclusions and understood the concept of alignment (this is transparent in the managed-only world), then it is not an bug of InterOpServices after all, but a mis-aligment of the structures by those who created these in C. Remember the data that comes out of the controller is only a series of bytes, each one mapped to have a specific meaning, and after analyzing and categorizing this data they created convience structures that worked in the unmanaged world. Translating and passing data of these structures between the managed and unmanaged world through the InterOpServices that superimpose the conformance of type alignment in memory makes this seemingly impossible to handle through .Net I'm afraid.

There may be other workarounds, but I clueless from here.


 
I have to add, I think this alignment was intruduced from the Pentium processor architecture and known optimizations of this. Not only does it look like this is not an important issue in unmanaged world, but it may have been an unknown issue in the times that this complex C structure was created.
Then again, I don't know this in depth and is not an authority of these matters.
 
Ok, so to recap, you are saying that there is no easy way to read in the data from the provided DLL in any way. So until they provide a true COM interface, I'm SOL? Damn! :(
 
Basically yes, if my qualified assumptions are correct.

As for 'easy' it should be possible to get/send a 'blob' of 256 bytes and chop it up yourself, including getting parts into relevant managed structures as I sketched out with the 'hack' stuff. Copying bytes in allocated memory is the only way I know of to pass data between byte arrays and structures.
 
So I can still use the "hack" model you are suggesting and do everything manually without the structures... worst case.

I have also emailed Fred Hebert, the guy that wrote the DLL, let's see if he responds with any suggestions... :D

Again John, Thanks for all your time. I wish the outcome was a bit better...
 
mavromatis said:
So I can still use the "hack" model you are suggesting and do everything manually without the structures...
...without the structures when getting/passing data to/from the dll, when you know message length and type there is the possibility to throw the relevant bytes to the relevant structure. And the other way, fill out byte/structure and throw it into a byte array to pass the dll. or something like that..

After all this I actually still don't know exactly how this dll is supposed to be used, I assume it works both ways? You pass some structure/bytes setup like a command with for instance required message type and then get the relevant info filled and returned, is it not so? :D
 
Back
Top