Marshaling Arrays

team lib

This section will discuss how to marshal arrays, first when using Platform Invoke and then in COM method calls.

Marshaling Arrays in Platform Invoke

Platform Invoke will take care of the details of marshaling arrays of both blittable and nonblittable types. Arrays are marshaled as In parameters by default, which means that the array will be passed by value to the unmanaged function. You can change this behavior by explicitly applying the In and Out attributes to individual parameters.

Passing Arrays by Value

Many Windows API functions (and third-party DLL functions written in C) will pass array data by value. A typical function signature will look like this:

 //Passarraybyvalue voidPassArray(short*pArray,intnElements); 

The first parameter is a pointer to the start of the array, and because a C- style array carries no information about the number of elements it contains, this must be explicitly passed as a second parameter. This function could be used via Platform Invoke in Visual C# as follows :

 [DllImport("SomeDll.dll")] publicstaticexternvoidPassArray(short[]pArray,intnElements); 

The marshaler will automatically marshal the managed short[] array object as an unmanaged C-style array and marshal the values over to the unmanaged function. If you want the function to send values back, you will need to tell the marshaler that it needs to marshal the data in both directions, using the In and Out attributes:

 [DllImport("SomeDll.dll")] publicstaticexternvoidPassArray([in,out]short[]pArray,intnElements); 

The unmanaged function can now modify the array elements, and they will be marshaled back to the caller. The unmanaged function cannot, however, change the size of the array.

Passing Arrays by Reference

Arrays can also be passed by reference to unmanaged code:

 //Passarraybyreference voidPassArrayRef(short**ppArray,int*pnElements); 

In this case, the first parameter is a pointer to a short* , which means that the PassArrayRef function can assign a different value to the short* at run time. The pnElements parameter is also a pointer, which lets the function pass back the number of elements that ppArray points to. When youre using Platform Invoke to execute such a function, the first parameter has to be represented by an IntPtr because .NET marshaling cannot deal with pointers that have more than one level of indirection:

 [DllImport("SomeDll.Dll")] publicstaticexternvoidPassArrayRef(refIntPtrppArray, refintpnElements); 

Note how the ref keyword is used to show that the parameters are being passed by reference. To use this function, you have to marshal the data manually to copy the data into unmanaged memory and create the IntPtr needed for the first argument. You also need to marshal the data back into managed memory after the call because Platform Invoke does not know how to deal with the IntPtr :

 //Createandinitializeanarray int[]arr={1,2,3,4,5}; //GetthenumberofelementsforusewithPlatformInvoke intnElems=arr.Length; //Marshalthearrayintounmanagedmemory IntPtrip=Marshal.AllocHGlobal(Marshal.SizeOf(int)*nElems); Marshal.Copy(arr,0,ip,nElems); //Calltheunmanagedfunction PassArrayRef(refip,refnElems); //Marshalthedatabackfromunmanagedmemory if(nElems>0){ //Createanewmanagedarray int[]arr2=newint[nElems]; //Copythedatafromunmanagedmemory Marshal.Copy(ip,arr2,0,nElems); //Freetheunmanagedmemory Marshal.FreeHGlobal(ip); } 

Marshal.AllocHGlobal allocates unmanaged memory on the Windows heap. The argument is the number of bytes to allocate, and the Marshal.SizeOf function can be used to find the size of a managed type or object. Marshal.Copy is used to copy the data between managed and unmanaged memory. When the call has been made, the DLL function might have passed back a different buffer, so we need to create a new managed array and copy the data back from unmanaged memory. After copying, the unmanaged memory can be released.

Note 

If a DLL function allocates memory, you will need to know which mechanism it is using. This example assumes that the Windows GlobalAlloc and GlobalFree APIs are used; if the COM allocator was used instead, you would have to use calls to Marshal.AllocCoTaskMem and Marshal.FreeCoTaskMem instead.

Marshaling Arrays in Structures

Unmanaged structures can contain fixed-size embedded arrays, as shown in the following example:

 structs1 { //Anarrayof16longs longvals[16]; }; 

When you marshal such an embedded array, you must use the MarshalAs attribute to mark the array as UnmanagedType.ByValArray , which indicates to the marshaler that it has to pass the array by value. The .NET array declaration will not include any bound information, so you also need to tell the marshaler, by using the SizeConst parameter, how many elements it needs to marshal. In Visual C#, you would marshal the preceding example like this:

 [StructLayout(LayoutKind.Sequential)] publicstructs1 { [MarshalAs(UnmanagedType.ByValArray,SizeConst=16)] publiclong[]vals; } 

Note that UnmanagedType . ByValArray can be used only for embedded arrays.

Marshaling Arrays of Structures

Platform Invoke will happily marshal arrays of structures, with no need for special coding on the managed side. Consider the following unmanaged structure:

 structStockItem { longstockNumber; intnumberInStock; }; 

This can be represented by the following managed type:

 [StructLayout(LayoutKind.Sequential)] publicstructStockItem { publiclongstockNumber; publicintnumberInStock; publicStockItem(longnum,intstk){ stockNumber=num; numberInStock=stk; } } 

A constructor is provided to make it convenient to create and initialize instances of the structure. An unmanaged function expects to be passed an array of StockItem structures so that it can build a list of items in stock:

 voidSetupStockList(StockItem*pList,intnItems); 

The Platform Invoke prototype declares an array of StockItem s:

 [DllImport("SomeDll.Dll")] publicstaticexternvoidSetupStockList(StockItem[]pList, intpItems); 

The function can be called by simply passing a reference to an array of StockItem s:

 //CreatethearrayofStockItems StockItem[]theList={ newStockItem(123456,10), newStockItem(200001,3), newStockItem(234567,100), newStockItem(301926,17)}; //Callthefunction SetupStockList(theList,theList.Length); 

Marshaling Arrays in COM Interop

Arrays are more complex to marshal in COM than in Platform Invoke because there are two distinct array types used in COM: C-style arrays and SAFEARRAY s.

Passing Arrays to COM

When a .NET object is being used by COM clients , a type library is created to represent the managed object as a COM component. If any exported managed methods contain array parameters, these must be exported in a form that COM can use. Well consider one-dimensional arrays first. When these are exported, the lower bound is always zero and their size will be known to the marshaler at run time.

The default is to marshal one-dimensional arrays as SAFEARRAY s:

 //Managedfunction voidMyFunction(short[]theArray) //Exporteddefinitionintypelibrary HRESULTMyFunction([in]SAFEARRAY(short)theArray) 

You can use the MarshalAs attribute, specifying the type as UnmanagedType.LPArray , to marshal the array as a C-style array instead:

 //Managedfunction voidMyFunction([MarshalAs(UnmanagedType.LPArray,SizeParamIndex=1)] short[]theArray,intsize) //Exporteddefinitionintypelibrary HRESULTMyFunction([in]short*theArray,[in]longsize); 

There are several points to note about this code. The LPArray type parameter indicates that the array must be passed as a C-style pointer and not as a SAFEARRAY . This makes it necessary to tell the marshaler how much data needs to be marshaled, using the second parameter to the MarshalAs attribute. Ive used the SizeParamIndex parameter, which gives the zero-based index of the parameter that will hold the array size at run time. If youve used Interface Definition Language (IDL), youll recognize this as similar to the IDL size_is attribute. If the array is a fixed size, you could also use the SizeConst parameter to specify the size.

When a rectangular two-dimensional array is exported to a type library, it is converted to a SAFEARRAY , which will contain the bound information for the array:

 //Managedfunction voidMyFunction(int[,]arr2d) //Exporteddefinitionintypelibrary HRESULTMyFunction([in]SAFEARRAY(long)arr2d); 

As with one-dimensional arrays, you can also use UnmanagedType.LPArray to marshal a managed array as a C-style unmanaged array. Once again, you will need to specify the number of elements using SizeConst or SizeParamIndex so that the marshaler knows how to marshal the array.

Note 

You cannot pass ragged arrays (or arrays of arrays ) to unmanaged code.

 
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