Ensuring the Alignment and Packing of Your C Structures

I l @ ve RuBoard

Ensuring the Alignment and Packing of Your C++ Structures

Because the Garbage Collector does so much of the assignment and layout of the code for you, it's sometimes difficult to know how a particular structure is laid out in memory. This can be of crucial importance if you are bringing in byte-ordered structures from previous C++ applications or DLLs and using them in the context of .NET. The Platform Invoke services has attributes that you can use to specify exactly how your structures are laid out so that you can read them into managed code and still be sure that the order and alignment of memory-based variables are well known.

The StructLayout attribute can be used to lay out a structure sequentially, to place member variables at an exact offset within a structure or to declare a union. Sequential layout is the simplest form, and you can additionally specify the packing of structure members .

The most common scenario that you will come across is one where you already have a pre-defined C++ structure in an application and you need to use that same structure from the managed world, possibly from a managed C++ program or with some code written in C#. This is what interop is all about. Take a look at the simple C++ structure in Listing 1.4.15:

Listing 1.4.15 simplestruct.h : A Simple C++ Structure
 #pragma pack(8) typedef struct{     char name[17],     int age,     double salary,     short ssn[3], } simple; 

The declaration shown in Listing 1.4.15 defines a structure with a particular packing. To see how this structure actually looks in memory, we'll fill it with some well-known values and then write to the variables in the structure. Listing 1.4.16 shows this process and dumps the contents of the structure to the screen.

Listing 1.4.16 simpletest .cpp : A Program to Test the Simple Structure
 1: #include <stdio.h>  2: #include <memory.h>  3: #include "simple.h"  4:  5: void dump(unsigned char *pC,int count)  6: {  7:     printf("Dumping %d bytes\ n",count);  8:     int n=0;  9:     while(n<count) 10:     { 11:         int i; 12:         int toDump=count-n>=16 ? 16 : count-n; 13:         for(i=0;i<toDump;i++) 14:             printf("%02x ",pC[n+i]); 15:         while(i++<16) 16:             printf("   "); 17:         printf("  "); 18:         for(i=0;i<toDump;i++) 19:             printf("%c",pC[n+i]>0x1f ? pC[n+i] : '.'); 20:         printf("\ n"); 21:         n+=toDump; 22:     } 23: } 24: 25: 26: void main(void) 27: { 28:       simple s; 29: 30:       memset(&s,0xff,sizeof(simple)); 31:       memcpy(&s.name[0],"abcdefghijklmnopq",17); 32:       s.age=0x11111111; 33:       s.salary=100.0; 34:       s.ssn[0]=0x2222; 35:       s.ssn[1]=0x3333; 36:       s.ssn[2]=0x4444; 37: 38:       dump((unsigned char *)&s,sizeof(simple)); 39: } 

Compile this code with the following command line:

 cl simpletest.cpp 

Now run it from the command line.

 Dumping 40 bytes 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70   abcdefghijklmnop 71 ff ff ff 11 11 11 11 00 00 00 00 00 00 59 40   q   ..........Y@ 22 22 33 33 44 44 ff ff                           ""33DD 

The code in Listing 1.4.16 shows that the structure simple is filled with data. First, on line 30, it gets filled with hexadecimal ff to contrast the real data with the blanks left over from the structure packing. Line 31 shows the age member being filled with 17 letters , and lines 32 through 36 show all the numeric variables being assigned. The code in lines 5 through 23 simply dump the structure to the screen.

The output shows clearly, wherever the ff code is seen, that there are gaps in the structure. Another clue is that 17+4+8+2+2+2 does not equal 40, the declared size of the structure by sizeof .

NOTE

To pass this structure to your legacy code from managed C++ or C# might be a problem because memory management is different and the CLR does not allow you to create managed structures with fixed size arrays. The data will require both layout and marshaling between the managed and unmanaged world.


Listing 1.4.17 illustrates how a structure can be created with exact placement of the members and how the transportation of array data can be accomplished.

Listing 1.4.17 managedsimple.txt : The Managed Version of the Simple Structure
 [StructLayout(LayoutKind::Explicit,Size=40)] public __gc struct managedsimple {     [FieldOffset(0)]     [MarshalAs(UnmanagedType::ByValTStr,SizeConst=17)]     System::String *name;     [FieldOffset(20)]     int age;     [FieldOffset(24)]     double salary;     [FieldOffset(32)]     [MarshalAs(UnmanagedType::ByValArray,SizeConst=3)]     short ssn __gc[]; }; 

NOTE

Remember that whenever you use these attributes in your software, you should declare that you are using namespace System::Runtime::InteropServices .


Here you see that not only has the physical layout of the structure been carefully controlled by the use of the StructLayout and FieldOffset attributes but also the char and short fixed length C arrays have been replaced by the garbage collector friendly System::String and System::Array types.

So that the program knows how these managed arrays should be transported to the old code, the MarshalAs attribute, which we saw before in conjunction with return types, has been employed to specify that the arrays need to be marshaled as a 17 character string array and an array of three short integers.

Listing 1.4.18 is a modification of the code in Listing 1.4.16. Here, we define an unmanaged C++ DLL that performs the same dump task for us. This DLL will be driven by a managed C++ program, which could equally be a VB.NET or C# module. The results will be shown on the screen again. First is the unmanaged DLL.

Listing 1.4.18 structuredll.cpp : The Unmanaged DLL
 1: #include <windows.h>  2: #include <stdio.h>  3: #include "simple.h"  4:  5: void dump(unsigned char *pC,int count)  6: {  7:     printf("Dumping %d bytes\ n",count);  8:     int n=0;  9:     while(n<count) 10:     { 11:         int i; 12:         int toDump=count-n>=16 ? 16 : count-n; 13:         for(i=0;i<toDump;i++) 14:             printf("%02x ",pC[n+i]); 15:         while(i++<16) 16:             printf("   "); 17:         printf("  "); 18:         for(i=0;i<toDump;i++) 19:             printf("%c",pC[n+i]>0x1f ? pC[n+i] : '.'); 20:         printf("\ n"); 21:         n+=toDump; 22:     } 23: } 24: 25: void __stdcall ShowSimple(simple *s) 26: { 27:     printf("Unmanaged DLL\ n"); 28:     dump((unsigned char *)s,sizeof(simple)); 29:     printf("Name=%17s\ n",&s->name[0]); 30:     printf("Age=%d\ n",s->age); 31:     printf("Salary=%f\ n",s->salary); 32:     printf("SSN=%d-%d-%d\ n\ n",s->ssn[0],s->ssn[1],s->ssn[2]); 33:     printf("Changing the salary...\ n"); 34:     s->salary=999999.0; 35:     printf("Changing the name...\ n"); 36:     strcpy(s->name,"Mavis     "); 37:     printf("Changing the ssn...\ n"); 38:     s->ssn[0]=99; 39:     s->ssn[1]=999; 40:     s->ssn[2]=9999; 41: } 42: 43: BOOL __stdcall DllMain(HINSTANCE hInstance,DWORD dwReason,LPVOID) 44: { 45:     switch(dwReason) 46:     { 47:     case DLL_PROCESS_ATTACH: 48:     case DLL_PROCESS_DETACH: 49:         return TRUE; 50:     default: 51:         return FALSE; 52:     } 53: } 

The DEF file exports the method you will invoke. Save it as structuredll.def .

 EXPORTS     DllMain @1     ShowSimple @2 

Compile this DLL with the following command line:

 Cl /LD structuredll.cpp /link /DEF:structuredll.def 

Listing 1.4.19 shows the managed C++ application that uses the DLL created in Listing 1.4.18 through the platform invoke system, passing the specially-defined and marshaled-managed structure.

Listing 1.4.19 simpledump.cpp : The Managed C++ Test Application
 1: // This is the managed C++ test  2: // program for the simpledump dll  3:  4: #using <mscorlib.dll>  5:  6: using namespace System;  7: using namespace System::Runtime::InteropServices;  8:  9: // Here we declare the managed structure that we will use 10: [StructLayout(LayoutKind::Explicit,Size=40)] 11: public __gc struct managedsimple { 12:     [FieldOffset(0)] 13:     [MarshalAs(UnmanagedType::ByValTStr,SizeConst=17)] 14:     System::String *name; 15:     [FieldOffset(20)] 16:     int age; 17:     [FieldOffset(24)] 18:     double salary; 19:     [FieldOffset(32)] 20:     [MarshalAs(UnmanagedType::ByValArray,SizeConst=3)] 21:     short ssn __gc[]; 22: }; 23: 24: 25: //This class imports the DLL function we need 26: __gc class StDll { 27: public: 28:     [DllImport("structuredll.dll")] 29:     static void ShowSimple([In,Out,MarshalAs(UnmanagedType::LPStruct, 30:     SizeConst=40)]managedsimple *); 31: 32:     // if you import other functions from the DLL 33:     // they can be added here. 34: }; 35: 36: void main(void) 37: { 38:     managedsimple *m=new managedsimple; 39: 40:     m->age=24; 41:     m->salary=180000.0; 42:     m->name=new System::String("Beavis"); 43:     m->ssn=new short __gc[3]; 44:     m->ssn[0]=10; 45:     m->ssn[1]=100; 46:     m->ssn[2]=1000; 47: 48:     managedsimple __pin* pS=m; 49: 50:     StDll::ShowSimple(pS); 51: 52:     pS=0; 53: 54:     Console::WriteLine("Back in managed code"); 55:     Console::WriteLine(m->salary); 56:     Console::WriteLine(m->name); 57:     Console::WriteLine("{ 0} -{ 1} -{ 2} ",__box(m->ssn[0]),__box (m->ssn[1]), 58:     box(m->ssn[2])); 59: } 

Compile this file with the following command line:

 cl /clr simpledump.cpp 

Now run it from the command line. You should see the following output.

 Unmanaged DLL Dumping 40 bytes 42 65 61 76 69 73 00 00 00 00 00 00 00 00 00 00   Beavis.......... 00 00 00 00 18 00 00 00 00 00 00 00 00 80 66 40   .............f@ 0a 00 64 00 e8 03 48 00                           ..d._.H. Name=           Beavis Age=24 Salary=180.000000 SSN=10-100-1000 Changing the salary... Changing the name... Changing the ssn... Back in managed code 999999 Mavis 99-999-9999 

Starting with the main function on line 36, we create the new managed structure on line 38 and populate it in lines 40 to 46. Line 50 calls the DLL function, and we jump to Listing 1.4.18 line 25.

This method does a hex dump of the contents of the structure, showing how our managed structure with both the string and the array of short integers marshaled out to the unmanaged world of the DLL. In lines 29 to 40 of the structured listing, the data in the unmanaged structure is modified by good old unmanaged C++. When this process is finished, the .NET marshaling takes over again and brings the unmanaged structure back into the managed world where lines 54 to 58 of Listing 1.5.19 confirm the changes by printing out the new data to the console.

As you can see from the code in this section, interoperability between the managed and unmanaged world is well catered and will keep your existing intellectual property current well into the .NET years .

I l @ ve RuBoard


C# and the .NET Framework. The C++ Perspective
C# and the .NET Framework
ISBN: 067232153X
EAN: 2147483647
Year: 2001
Pages: 204

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net