I l @ ve RuBoard |
Ensuring the Alignment and Packing of Your C++ StructuresBecause 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 Structure1: #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 DLL1: #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 Application1: // 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 |