Marshaling Structs

team lib

Up to this point, all the Platform Invoke examples have used simple values such as integers and strings. Many unmanaged functions, however, require you to pass structures as arguments.

You can define a managed type that is the equivalent of an unmanaged structure. The problem with marshaling such types is that the common language runtime controls the layout of managed classes and structures in memory. Because you do not reference members by address but by reference, it should not matter to you exactly how the runtime chooses to arrange the type members in memory. The layout does matter, however, when you define a structure that is to be passed to unmanaged code.

The StructLayout Attribute

This attribute allows a developer to control the layout of managed types; it is mainly used in interop applications. A StructLayout attribute must be initialized with a member of the LayoutKind enumeration. The following Visual C# code provides an example:

 //UseSequentiallayout [StructLayout(LayoutKind.Sequential)] 

The values defined by the LayoutKind enumeration are listed in Table 13-3.

Table 13-3: Values in the LayoutKind Enumeration

Member

Description

Auto

The runtime will choose the best layout for the managed type. This value cannot be used on types that are to be used with interop.

Explicit

The position of each member of the type will be specified by using the FieldOffset attribute.

Sequential

The runtime will lay out the type members sequentially, in the order in which they are defined.

Three optional parameters can be used with StructLayout :

  • CharSet can be used to specify how string fields within the type are to be marshaled. As with the DllImport attribute used by Platform Invoke, this can take the values Ansi , Auto , or Unicode . If Auto is specified, the conversion will be platform dependent.

  • Pack controls the alignment of data fields in memory. The value used with Pack must be a value between 0 and 128 that is a power of two. A value of zero indicates the default packing for the platform will be used. If this parameter is not specified, a packing of 4 bytes is used for unmanaged structures (meaning that members can be aligned on boundaries that are multiples of 4 bytes), and 8 bytes is used for managed types.

    Note 

    If you don't use packing, structure members will be aligned on natural boundaries . This means that members can be placed only at offsets that are a multiple of the member's size -that is, 4-byte integers will be placed at offsets that are multiples of 4, and so on. This can end up leaving unused space in a structure, and packing might help to arrange members more efficiently . When you specify packing, fields align either to their natural boundary or to the pack value, whichever results in the smallest memory use.

  • Size indicates the absolute size of the type in bytes. This field is mainly used by compiler writers and is not often found in application code.

The following Visual C# code fragment shows how these parameters can be used with StructLayout :

 //UseSequentiallayout,withapackingof8 //andmarshalstringsasANSI [StructLayout(LayoutKind.Sequential),Pack=8, CharSet=CharSet.Ansi] 

Explicit Layout

When you choose explicit layout, you specify the byte offsets of each member of the type, using the FieldOffset attribute.

 //UseExplicitlayoutinVisualC#code [StructLayout(LayoutKind.Explicit)] publicstructMyStruct{ [FieldOffset(2)]publicshortMemberOne; [FieldOffset(5)]publicintMemberTwo; [FieldOffset(11)]publiccharMemberThree; } 

The memory layout produced by this definition produces a structure 12 bytes in size, which can be seen in Figure 13-1. MemberOne occupies two bytes starting at offset 2, MemberTwo occupies four bytes starting at offset 5, and MemberThree occupies two bytes starting at offset 11.

click to expand
Figure 13-1: Using explicit layout lets a developer control exactly how a structure is laid out in memory.

It should be obvious that the Pack parameter isn't applicable when using explicit layout because you are providing all the information necessary for structure layout.

By using an offset of zero for more than one member, you can create a C- style union . You can only do this for fields that are blittable types. The following Visual C# example shows how to define a union in this way:

 //Defineaunion [StructLayout(LayoutKind.Explicit)] publicstructMyUnion{ [FieldOffset(0)]publicshortarr[2]; [FieldOffset(0)]publicintval; } 

Because both members are defined with an offset of zero, they effectively occupy the same memory locations, and the size of the structure is four bytes. For those unfamiliar with C-style unions, this enables you to view the four bytes of memory as either a single int or a pair of short s.

Handling Nested Structures

Structures are very often built up out of other structures-for example, a Rectangle is usually built from two Point s. If structures are nested by value, marshaling them is straightforward. Consider the following definitions in C:

 typedefstructtagPoint { intx,y; }PNT,*LPPNT; typedefstructtagRect { PNTp1,p2; }RCT,*LPRCT; 

An RCT s tructure contains two PNT objects. These can be represented by the following Visual C# structs:

 //ThePointandRectstructures [StructLayout(LayoutKind.Sequential)] publicstructPoint{ publicintx,y; } [StructLayout(LayoutKind.Sequential)] publicstructRect{ publicPointp1,p2; } 

Note how two Point instances can simply be embedded within the Rect struct. Using them is also simple. Assume there is an unmanaged C function in a DLL that calculates the area of a rectangle:

 __declspec(dllexport)intGetArea(LPRCTpRect) { intwidth=(pRect->p2.x>pRect->p1.x)? pRect->p2.x-pRect->p1.x: pRect->p1.x-pRect->p2.x; intheight=(pRect->p2.y>pRect->p1.y)? pRect->p2.y-pRect->p1.y: pRect->p1.y-pRect->p2.y; returnheight*width; } 

The function simply takes a pointer to a rectangle object and returns its area. A Platform Invoke prototype can easily be constructed and used for this function, as shown in the following Visual C# code fragment:

 [DllImport("TestInterop.dll")] publicstaticexternintGetArea(refRectrct); ... Rectr; r.p1.x=r.p1.y=10; r.p2.x=r.p2.y=15; intarea=GetArea(refr); Console.WriteLine("Areais{0}",area); 

One important point to note about this code is the use of the ref keyword, which passes value types by reference rather than by value. It is necessary to use ref here because the function is expecting the address of an object.

If a structure contains a pointer to another structure, the situation is slightly more complex. Consider the following two C structures that are used to define a bank account:

 typedefstructtagPerson { char*firstName; char*lastName; intage; }PERSON,*LPPERSON; typedefstructtagAccount { PERSON*accountHolder; char*accountName; longaccountNumber; doublebalance; }ACCOUNT,*LPACCOUNT; 

The ACCOUNT class holds a pointer to a PERSON object that represents the person owning the account. Using a pointer means, among other things, that one PERSON could be associated with several ACCOUNT s. To provide a .NET equivalent for use with Platform Invoke, an IntPtr should be used to represent the pointer:

 //ThePersonstructure [StructLayout(LayoutKind.Sequential,CharSet=CharSet.Ansi)] publicstructPerson{ publicstringfirstName; publicstringlastName; publicintage; } //TheBankaccountstructure.NotetheuseofanIntPtr //toholdthepointertotheaccountholder [StructLayout(LayoutKind.Sequential,CharSet=CharSet.Ansi)] publicstructAccount{ publicIntPtraccountHolder; publicstringaccountName; publiclongaccountNumber; publicdoublebalance; } 

Note how the CharSet parameter is used to set the marshaling type for string members to ANSI; this is needed because the DLL is expecting ANSI characters , but on Windows NT, Windows 2000, and Windows XP systems the default marshaling would use Unicode.

To show how such a structure could be used, suppose that a DLL contains a function that takes a pointer to an account object and returns information about the account in the form of a string:

 //Returndetailsforanaccount,intheform "AccountName(Holder)" __declspec(dllexport)char*GetAccountDetails(LPACCOUNTacct) { //Allocateabufferforthereturnstring char*buff=(char*)malloc(strlen(acct->accountName)+ strlen(acct->accountHolder->firstName)+ strlen(acct->accountHolder->lastName)+ 4);//terminatingnullplustwobracketsandtwospaces //Buildthereturnstring strcpy(buff,acct->accountName); strcat(buff, " ("); strcat(buff,acct->accountHolder->firstName); strcat(buff, " "); strcat(buff,acct->accountHolder->lastName); strcat(buff,")"); returnbuff; } 

The Platform Invoke prototype is very simple, and the only thing you need to note is that you should use a ref parameter so that the address of the Account is passed:

 [DllImport("TestInterop.dll")] publicstaticexternstringGetAccountDetails(refAccountact); 

Here's a Visual C# example of how you would construct Person and Account objects and call the function:

 Personp; p.firstName= "Fred"; p.lastName= "Smith"; p.age=45; Accounta; a.accountName= "Fred'saccount"; a.accountNumber=10000; a.balance=0.0; //AllocatememoryforaPerson IntPtrip=Marshal.AllocHGlobal(Marshal.SizeOf(p)); //Marshalthepersonstructureintothememory,butdon'tdelete //theoriginal Marshal.StructureToPtr(p,ip,false); a.accountHolder=ip; //Makethecall stringdetails=GetAccountDetails(refa); Console.WriteLine("Account:{0}",details); //Freethememory Marshal.FreeHGlobal(ip); 

The DLL function is expecting a pointer, and that means the data has to be in unmanaged memory. The Marshal.AllocHGlobal method can be used to allocate memory from the unmanaged memory of the process, which is later freed by calling Marshal.FreeHGlobal . The Person data is marshaled into the unmanaged memory by calling Marshal.StructureToPtr . This method will copy the contents of a managed object into unmanaged memory, performing any necessary marshaling. The third parameter is used to prevent memory leaks.

The IntPtr used as an argument could refer to a block of unmanaged memory that itself contains pointers to further unmanaged blocks. If this is the case, these blocks will be orphaned when StructureToPtr marshals the new data over the old, resulting in memory leaks. Setting this parameter to true will ensure that any existing dynamic memory allocations will be freed up before the call to StructureToPtr . In this case, the memory does not contain any pointers, so it is safe to pass false as the third argument.

 
team lib


COM Programming with Microsoft .NET
COM Programming with Microsoft .NET
ISBN: 0735618755
EAN: 2147483647
Year: 2006
Pages: 140

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