Microsoft KB Archive/306801

= HOW TO: Interoperate with a COM Server That Returns Conformant Arrays by Using Visual BASIC .NET =

Article ID: 306801

Article Last Modified on 5/13/2007

-

APPLIES TO


 * Microsoft Visual Basic .NET 2002 Standard Edition

-



This article was previously published under Q306801





For a Microsoft C# .NET version of this article, see 305990.

IN THIS TASK

 * SUMMARY
 * The Sample COM Server
 * Discussion of Approaches to COM Interop
 * Use Interop Assemblies
 * Use Visual Basic Declarations for the COM Server
 * Add a Managed Wrapper Class
 * REFERENCES



SUMMARY
This step-by-step article describes how to interoperate with a COM server that returns conformant arrays.

The following file is available for download from the Microsoft Download Center:

Confarrs.exe

For additional information about how to download Microsoft Support files, click the following article number to view the article in the Microsoft Knowledge Base:

119591 How to Obtain Microsoft Support Files from Online Services

Microsoft scanned this file for viruses. Microsoft used the most current virus-detection software that was available on the date that the file was posted. The file is stored on security-enhanced servers that help to prevent any unauthorized changes to the file. back to the top

The Sample COM Server
The server that is used in this example implements the following interface: interface IComArrsObj : IDispatch {  HRESULT GetArrOfLongs([in] LONG nStartIdx, [in,out] LONG* pnCnt,                         [out,size_is(,*pnCnt)] LONG** ppArr); HRESULT GetArrOfUDTs ([in] LONG nStartIdx, [in,out] LONG* pnCnt,                        [out,size_is(,*pnCnt)] MyUDT** ppArr); HRESULT MyNextLongs ([in] LONG nReq, [out, size_is(nReq)] LONG *rgelt,                         [out] LONG* pnFetched); HRESULT MyNextUDTs  ([in] LONG nReq, [out, size_is(nReq)] MyUDT* rgelt,                         [out] LONG* pnFetched); }; As shown in this interface specification, this article demonstrates how to interoperate with a COM server that returns an array of Long values and an array of a user-defined type (UDT). The code also illustrates the difference between server allocated arrays (that is, the GetArrOfXxx methods) and client allocated arrays (that is, the MyNextXxx methods that mimic the Next method idiom from the IEnumXxx interfaces).

The GetArrOfXxx methods take the following three arguments:
 * nStartIdx is an [in] argument that specifies the index of the first element to pass back to the client.
 * pnCnt is an [in, out] argument that equals the number of items that are requested by the client upon entry, and the number of items that are provided by the server upon exit. If pnCnt is zero upon entry, the server returns all available elements.
 * ppArr is an [out] array that is allocated by the server. Its size equals *pnCnt.

The MyNextXxx methods take the following three arguments:
 * nReq is an [in] argument that specifies the number of items that are requested by the client.
 * rgelt is an [out] argument that is allocated by the client and filled by the server. Only the first *pnFetched elements are valid.
 * pnFetched is an [out] argument that specifies the number of items that are filled in by the server.

back to the top

Discussion of Approaches to COM Interop
Generally, when you interoperate with a COM server, you have the following options:
 * Use an interop assembly to communicate with the COM server. If one is available, you should use a primary interop assembly. Otherwise, you can generate the interop assembly by using the Tlbimp.exe utility or the import functionality of Visual Studio .NET.
 * Declare the COM objects and interfaces directly in the Visual Basic .NET source code. You can use this option if you need to interoperate with a limited number of objects and interfaces that are exposed by the COM server.
 * You may enhance either of these options by adding a managed class that provides a familiar managed interface for the rough import classes that are needed to interoperate with the COM server.

You should always use a primary interop assembly if one is available. When the primary interop assembly is available, you may still use the third option and provide a managed interface for the types that are exported from the primary interop assembly.

When a primary interop assembly is not available, the option that you use depends on whether the application needs to expose types that are imported from the COM server. This may occur when the application consists of multiple assemblies that exchange data of a type that is imported from the COM server.

If the managed application contains multiple assemblies that use the COM server and that need to expose a data type that is imported from the COM server, you should use a shared assembly to define the interop assembly. In such a situation, using a primary interop assembly is strongly recommended.

If you do not need to expose data types from the COM server across assemblies, you can use either private assemblies for the interop assembly (the first option) or define the COM types in Visual Basic .NET code (the second option).

For more information, see the following Microsoft Developer Network (MSDN) Web site:

Deploying an Interop Application

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguide/html/cpcondeployinginteropapplication.asp

back to the top

Use Interop Assemblies
You should always use a primary interop assembly if one is available. Use of a primary interop assembly provides for a well-tested, proven interop layer to access the COM server functionality. In addition, Tlbimp.exe provides an automatic way to generate the interop assembly, resulting in advantages of automatic code generation such as speed and correctness. Also, when the managed client needs to access a large number of the methods and interfaces that are exposed by the COM server, the second option may prove prohibitive. In these situations, you may use an interop assembly to access the functionality that the COM server provides. You can obtain the interop assembly by using one of the following methods: You should always use a Primary Interop Assembly if one is available. Use of a Primary Interop Assembly provides for a well-tested, proven Interop layer to access the COM server functionality. In addition, Tlbimp.exe provides an automatic way to generate the Interop Assembly, resulting in advantages of automatic code generation such as speed and correctness. Also, when the managed client needs to access a large number of the methods and interfaces that are exposed by the COM server, the second option may prove prohibitive. In these situations, you may use an Interop Assembly to access the functionality that the COM server provides. You can obtain the Interop Assembly by using one of the following methods:
 * Obtain the primary interop assembly from the software vendor that publishes the COM server with which you need to interoperate.
 * Generate a primary interop assembly from Visual Studio .NET. To invoke Tlbimp.exe to generate the interop assembly that you need, click Add Reference on the Project menu, click the COM tab, and then select the entry for the COM server.
 * Generate the interop assembly manually. To do this, invoke Tlbimp.exe on the COM server type library (that is, on the .tlb, .dll, .ocx, or .exe file). This allows you to have finer control over the behavior of Tlbimp.exe, rather than relying on the Visual Studio .NET default command line arguments.
 * For the highest degree of flexibility, follow these steps:


 * Use Tlbimp.exe to generate a first version of the interop assembly, then run Ildasm.exe on the assembly. To do this, type ildasm Interop.Srvr.dll /out=Interop.Srvr.il at a command prompt.
 * Hand-edit the generated intermediate language code to specify the marshalling behavior that you need.
 * Reassemble the intermediate language code by using Ilasm.exe to obtain the final interop assembly that you reference in your project. To do this, type ilasm /dll Interop.Srvr.il /out=Interop.Srvr.dll at a command prompt.

Because of COM type library format limitations, Tlbimp.exe cannot generate correct interface definitions for the COM server in the sample that this article provides. For this reason, the sample in this article uses the fourth option. To use this option, follow these steps:   Run Tlbimp.exe on the COM server dynamic-link library (DLL), as follows:

> tlbimp /out:Interop_1.ComArrs_1_0.DLL /namespace:ComArrs_1 ComArrs.DLL Run Ildasm.exe on the generated DLL, as follows: > ildasm /out=Interop_1.ComArrs_1_0.IL Interop_1.ComArrs_1_0.DLL   Modify the method signatures in the generated intermediate language file to modify the marshaling according to your needs. Each method appears twice in the intermediate language, so you must make these changes in both the declaration of the interface and the declaration of the class:

.method public hidebysig newslot virtual abstract instance void GetArrOfLongs([in] int32 nStartIdx,                                        [in][out] int32& pnCnt,                                        // Replace this: [out] native int ppArr) runtime managed internalcall // with this: [out] native int& ppArr) runtime managed internalcall  .method public hidebysig newslot virtual abstract            instance void  GetArrOfUDTs([in] int32 nStartIdx, [in][out] int32& pnCnt, // Replace this: [out] valuetype ComArrs_1.MyUDT& marshal( lpstruct) ppArr) runtime managed internalcall                                       // With this:                                       [out] native int& ppArr) runtime managed internalcall .method public hidebysig newslot virtual abstract instance void MyNextLongs([in] int32 nReq,                                 // Replace this: [out] int32& rgelt,                                 // With this:                                 [out] int32[] marshal([ + 1]) rgelt,                                 [out] int32& pnFetched) runtime managed internalcall .method public hidebysig newslot virtual abstract instance void MyNextUDTs([in] int32 nReq,                                     // Replace this: [out] valuetype ComArrs_1.MyUDT& rgelt,                                     // With this:                                     [out] valuetype ComArrs_1.MyUDT[] marshal([ + 1]) rgelt,                                     [out] int32& pnFetched) runtime managed internalcall   Use Ilasm.exe to rebuild the interop assembly, as follows:

> ilasm /dll /resource=Interop_1.ComArrs_1_0.res Interop_1.ComArrs_1_0.il /out=Interop_1.ComArrs_1_0.dll  Reference the new interop assembly in the Visual Basic .NET client project. To do this, click Add Reference on the Project menu, then browse to the newly-generated DLL.  Write the test code in the managed client, as follows:

Public Shared Sub Run Console.WriteLine(ControlChars.NewLine + &quot;Testing hand-edited IL of TlbImp-ed COM class:&quot;) Console.WriteLine(&quot;=============================================:&quot;)

Console.WriteLine(&quot;Creating COM object&quot;) 'Dim o As New ComArrs.CComArrsObj Dim o As New ComArrs_1.CComArrsObj

Console.WriteLine(&quot;Calling GetArrOfLongs&quot;) Dim cnt As Integer = 0 Dim rAddr As IntPtr o.GetArrOfLongs(0, cnt, rAddr) Dim r(cnt - 1) As Integer ' Marshal array from unmanaged to managed heap. Marshal.Copy(rAddr, r, 0, cnt) ' Release the unmanaged array. Marshal.FreeCoTaskMem(rAddr) Utils.PrintArray(r, elementFormatter)

Console.WriteLine(&quot;Calling GetArrOfUDTs&quot;) cnt = 0 Dim ruAddr As IntPtr o.GetArrOfUDTs(0, cnt, ruAddr) Dim ru(cnt - 1) As ComArrs_1.MyUDT

' Marshal the array, element by element, from an unmanaged to a managed heap. ' Release the unmanaged array. Dim i As Integer Dim elemOffs As Integer = Int(ruAddr.ToInt64) For i = 0 To cnt - 1 ru(i) = Marshal.PtrToStructure(New IntPtr(elemOffs), GetType(ComArrs_1.MyUDT)) elemOffs = elemOffs + Marshal.SizeOf(GetType(ComArrs_1.MyUDT)) Next i     Marshal.FreeCoTaskMem(ruAddr) Utils.PrintArray(ru, elementFormatter)

Console.WriteLine(&quot;Calling MyNextLongs&quot;) Dim q(20 - 1) As Integer ' The marshalling is performed automatically. o.MyNextLongs(q.GetLength(0), q, cnt) Utils.PrintArray(q, elementFormatter)

Console.WriteLine(&quot;Calling MyNextUDTs&quot;) ru = New ComArrs_1.MyUDT(20 - 1) {} ' The marshalling is performed automatically. o.MyNextUDTs(ru.GetLength(0), ru, cnt) Utils.PrintArray(ru, elementFormatter)

' The next COM call will generate a MarshalDirectiveException exception. ' (&quot;Cannot use SizeParamIndex for byref array parameters.&quot;) Console.WriteLine(&quot;Calling GetArrOfLongs2&quot;) Dim o2 As New ComArrs_2.CComArrsObj Dim s As Integer Try o2.GetArrOfLongs(0, cnt, s)        Utils.PrintArray(s, elementFormatter) Catch e As Exception Console.WriteLine(&quot;{0}&quot;, e)     End Try Console.WriteLine End Sub 'Run 

back to the top

Use Visual Basic .NET Declarations for the COM Server
The advantage of this option over using an interop assembly is that it eliminates the need to ship one additional file (that is, the interop assembly).

To specify the necessary elements and call the methods, follow these steps:

NOTE: Start with the COM interface that is provided in the &quot;Sample COM Server&quot; section. <ul>  Define the UDT as follows:

' MyUDT as defined in the server. <Guid(&quot;190A418D-B113-40d4-A22C-20EF9EAC3E33&quot;), StructLayout(LayoutKind.Sequential)> _ Structure MyUDT <MarshalAs(UnmanagedType.BStr)> _ Public aBstr As String Public aLong As Integer Public aBool As Boolean End Structure 'MyUDT </li>  Define the COM interface as follows:

' A possible Visual Basic representation of the interface. <InterfaceType(ComInterfaceType.InterfaceIsDual), Guid(&quot;78D4F391-B10B-4B80-A2D1-1B4C583DCAEC&quot;)> _ Interface IComArrsObj Sub GetArrOfLongs(ByVal startIdx As Integer, _                       ByRef cnt As Integer,      _                        ByRef arrAddr As IntPtr) Sub GetArrOfUDTs (ByVal startIdx As Integer, _                       ByRef cnt As Integer,      _                        ByRef arrAddr As IntPtr) Sub MyNextLongs (ByVal req As Integer,      _                        <MarshalAs(UnmanagedType.LPArray, SizeParamIndex:=0), Out> _                        ByVal rgelt As Integer,  _                        ByRef fetched As Integer) Sub MyNextUDTs  (ByVal req As Integer,      _                        <MarshalAs(UnmanagedType.LPArray, SizeParamIndex:=0), Out> _                        ByVal rgelt As MyUDT,    _                        ByRef fetched As Integer) End Interface 'IComArrsObj </li>  Because Visual Basic .NET does not support the ComImport attribute, you cannot declare the COM server class the same way that you declare the class in Visual C#. When Visual Basic adds support for this attribute, you can define the COM class as follows:

' The coclass. <ComImport, _ Guid(&quot;056A32CF-D716-4902-BCD2-ED7F070D9E36&quot;), _ ClassInterface(ClassInterfaceType.None), _ Class CComArrsObj End Class 'CComArrsObj </li>  Call the methods on the COM server as follows:
 * 1) If SupportComImportAttribute Then
 * 1) End If

Public Shared Sub Run Console.WriteLine(ControlChars.NewLine + _            &quot;Testing VB-declared COM class and interfaces:&quot;) Console.WriteLine( _            &quot;============================================:&quot;)

Console.WriteLine(&quot;Creating COM object&quot;) ' ToDo: Visual Basic does not yet support the ComImportAttribute. See when this will be supported. Dim icao as ComArrs_0.IComArrsObj = new ComArrs_0.CComArrsObj Dim icao As ComArrs_0.IComArrsObj = CreateObject(&quot;ComArrs.ComArrsObj&quot;)
 * 1) If SupportComImportAttribute Then
 * 1) Else
 * 1) End If

Console.WriteLine(&quot;Calling GetArrOfLongs&quot;) Dim cnt As Integer = 0 Dim rAddr As IntPtr icao.GetArrOfLongs(0, cnt, rAddr) Dim r(cnt - 1) As Integer ' Marshal the array from an unmanaged to a managed heap. Marshal.Copy(rAddr, r, 0, cnt) ' Release the unmanaged array. Marshal.FreeCoTaskMem(rAddr) Utils.PrintArray(r, elementFormatter)

Console.WriteLine(&quot;Calling GetArrOfUDTs&quot;) cnt = 0 Dim ruAddr As IntPtr icao.GetArrOfUDTs(0, cnt, ruAddr) Dim ru(cnt - 1) As ComArrs_0.MyUDT ' Marshal the array, element by element, from an unmanaged to a managed heap. Dim i As Integer Dim elemOffs As Integer = Int(ruAddr.ToInt64) For i = 0 To cnt - 1 ru(i) = Marshal.PtrToStructure(New IntPtr(elemOffs), GetType(ComArrs_0.MyUDT)) elemOffs = elemOffs + Marshal.SizeOf(GetType(ComArrs_0.MyUDT)) Next i      ' Release the unmanaged array. Marshal.FreeCoTaskMem(ruAddr) Utils.PrintArray(ru, elementFormatter)

Console.WriteLine(&quot;Calling MyNextLongs&quot;) Dim q(20) As Integer ' The marshalling is performed automatically. icao.MyNextLongs(q.GetLength(0), q, cnt) Utils.PrintArray(q, elementFormatter)

Console.WriteLine(&quot;Calling MyNextUDTs&quot;) ru = New ComArrs_0.MyUDT(20) {} ' The marshalling is performed automatically. icao.MyNextUDTs(ru.GetLength(0), ru, cnt) Utils.PrintArray(ru, elementFormatter)

Marshal.ReleaseComObject(icao) End Sub 'Run </li></ul>

back to the top

Add a Managed Wrapper Class
The managed wrapper class can be based on either of the previous two options. It can use the TlbImp-generated wrapper from the first option, or it can encapsulate a reference to the Visual Basic .NET declarations of the COM server. To use the latter option for building the managed wrapper class, follow these steps:  Add a reference to the interop assembly that contains the managed definition of the COM server.</li>  Define the managed wrapper class in terms of the class that TlbImp has generated, as follows:

Class MgdComArrs

Public Sub New cao_ = New ComArrs_1.CComArrsObj End Sub 'New

' Invoke the COM server GetArrOfLongs. Public Function GetArrOfLongs(ByVal startIdx As Integer, ByRef cnt As Integer) As Integer Dim rAddr As IntPtr cao_.GetArrOfLongs(startIdx, cnt, rAddr) Dim r(cnt - 1) As Integer Marshal.Copy(rAddr, r, 0, cnt) Marshal.FreeCoTaskMem(rAddr)

Return r       End Function 'GetArrOfLongs

' Invoke the COM server GetArrOfUDTs. Public Function GetArrOfUDTs(ByVal startIdx As Integer, ByRef cnt As Integer) As ComArrs_1.MyUDT Dim ruAddr As IntPtr cao_.GetArrOfUDTs(startIdx, cnt, ruAddr) Dim ru(cnt - 1) As ComArrs_1.MyUDT Dim i As Integer Dim elemOffs As Integer = Int(ruAddr.ToInt64) For i = 0 To cnt - 1 ru(i) = Marshal.PtrToStructure(New IntPtr(elemOffs), GetType(ComArrs_1.MyUDT)) elemOffs = elemOffs + Marshal.SizeOf(GetType(ComArrs_1.MyUDT)) Next i           Marshal.FreeCoTaskMem(ruAddr)

Return ru       End Function 'GetArrOfUDTs

' Fill an array of integers with at most req elements. Public Function MyNextLongs(ByVal req As Integer, ByVal r As Integer) As Integer Dim fetched As Integer cao_.MyNextLongs(req, r, fetched) Return fetched End Function 'MyNextLongs

' Fills an array of MyUDT elements with at most req elements. Public Function MyNextUDTs(ByVal req As Integer, ByVal r As ComArrs_1.MyUDT) As Integer Dim fetched As Integer cao_.MyNextUDTs(req, r, fetched) Return fetched End Function 'MyNextUDTs

Private cao_ As ComArrs_1.CComArrsObj End Class 'MgdComArrs </li>  Write the client code, as follows:

Public Shared Sub Run Console.WriteLine(ControlChars.NewLine + &quot;Testing managed wrapper definition of COM class:&quot;) Console.WriteLine(&quot;===============================================:&quot;) Console.WriteLine(&quot;Creating managed wrapper&quot;) Dim mcao As New MgdComArrs

Console.WriteLine(&quot;Calling GetArrOfLongs&quot;) Dim cnt As Integer = 0 Dim r As Integer = mcao.GetArrOfLongs(0, cnt) Utils.PrintArray(r, elementFormatter)

Console.WriteLine(&quot;Calling GetArrOfUDTs&quot;) cnt = 0 Dim ru As ComArrs_1.MyUDT = mcao.GetArrOfUDTs(0, cnt) Utils.PrintArray(ru, elementFormatter)

Console.WriteLine(&quot;Calling MyNextLongs&quot;) Dim q(15 - 1) As Integer mcao.MyNextLongs(q.GetLength(0), q)           Utils.PrintArray(q, elementFormatter)

Console.WriteLine(&quot;Calling MyNextUDTs&quot;) ru = New ComArrs_1.MyUDT(15 - 1) {} mcao.MyNextUDTs(ru.GetLength(0), ru) Utils.PrintArray(ru, elementFormatter) End Sub 'Run </li></ol>

back to the top back to the top

<div class="references_section">