HOW TO: Interoperate with a COM Server That Returns Conformant Arrays by Using Visual BASIC .NET (306801)
The information in this article applies to:
- Microsoft Visual Basic .NET (2002)
This article was previously published under Q306801 For a Microsoft C# .NET version of this article, see 305990.
IN THIS TASKSUMMARY
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:
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:
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 + "Testing hand-edited IL of TlbImp-ed COM class:")
Console.WriteLine("=============================================:")
Console.WriteLine("Creating COM object")
'Dim o As New ComArrs.CComArrsObj()
Dim o As New ComArrs_1.CComArrsObj()
Console.WriteLine("Calling GetArrOfLongs( )")
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("Calling GetArrOfUDTs( )")
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("Calling MyNextLongs( )")
Dim q(20 - 1) As Integer
' The marshalling is performed automatically.
o.MyNextLongs(q.GetLength(0), q, cnt)
Utils.PrintArray(q, elementFormatter)
Console.WriteLine("Calling MyNextUDTs( )")
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.
' ("Cannot use SizeParamIndex for byref array parameters.")
Console.WriteLine("Calling GetArrOfLongs2( )")
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("{0}", 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 "Sample COM Server" section.
- Define the UDT as follows:
' MyUDT as defined in the server.
<Guid("190A418D-B113-40d4-A22C-20EF9EAC3E33"), StructLayout(LayoutKind.Sequential)> _
Structure MyUDT
<MarshalAs(UnmanagedType.BStr)> _
Public aBstr As String
Public aLong As Integer
Public aBool As Boolean
End Structure 'MyUDT
- Define the COM interface as follows:
' A possible Visual Basic representation of the interface.
<InterfaceType(ComInterfaceType.InterfaceIsDual), Guid("78D4F391-B10B-4B80-A2D1-1B4C583DCAEC")> _
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
- 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:
#If SupportComImportAttribute Then
' The coclass.
<ComImport(), _
Guid("056A32CF-D716-4902-BCD2-ED7F070D9E36"), _
ClassInterface(ClassInterfaceType.None), _
Class CComArrsObj
End Class 'CComArrsObj
#End If
- Call the methods on the COM server as follows:
Public Shared Sub Run()
Console.WriteLine(ControlChars.NewLine + _
"Testing VB-declared COM class and interfaces:")
Console.WriteLine( _
"============================================:")
Console.WriteLine("Creating COM object")
' ToDo: Visual Basic does not yet support the ComImportAttribute. See when this will be supported.
#If SupportComImportAttribute Then
Dim icao as ComArrs_0.IComArrsObj = new ComArrs_0.CComArrsObj()
#Else
Dim icao As ComArrs_0.IComArrsObj = CreateObject("ComArrs.ComArrsObj")
#End If
Console.WriteLine("Calling GetArrOfLongs( )")
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("Calling GetArrOfUDTs( )")
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("Calling MyNextLongs( )")
Dim q(20) As Integer
' The marshalling is performed automatically.
icao.MyNextLongs(q.GetLength(0), q, cnt)
Utils.PrintArray(q, elementFormatter)
Console.WriteLine("Calling MyNextUDTs( )")
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
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.
- 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
- Write the client code, as follows:
Public Shared Sub Run()
Console.WriteLine(ControlChars.NewLine + "Testing managed wrapper definition of COM class:")
Console.WriteLine("===============================================:")
Console.WriteLine("Creating managed wrapper")
Dim mcao As New MgdComArrs()
Console.WriteLine("Calling GetArrOfLongs( )")
Dim cnt As Integer = 0
Dim r As Integer() = mcao.GetArrOfLongs(0, cnt)
Utils.PrintArray(r, elementFormatter)
Console.WriteLine("Calling GetArrOfUDTs( )")
cnt = 0
Dim ru As ComArrs_1.MyUDT() = mcao.GetArrOfUDTs(0, cnt)
Utils.PrintArray(ru, elementFormatter)
Console.WriteLine("Calling MyNextLongs( )")
Dim q(15 - 1) As Integer
mcao.MyNextLongs(q.GetLength(0), q)
Utils.PrintArray(q, elementFormatter)
Console.WriteLine("Calling MyNextUDTs( )")
ru = New ComArrs_1.MyUDT(15 - 1) {}
mcao.MyNextUDTs(ru.GetLength(0), ru)
Utils.PrintArray(ru, elementFormatter)
End Sub 'Run
back to the top
back to the top
REFERENCES
For more information, see the following Microsoft Developer Network (MSDN) Web sites:
back to the top
Modification Type: | Minor | Last Reviewed: | 8/5/2004 |
---|
Keywords: | kbdownload kbHOWTOmaster KB306801 kbAudDeveloper |
---|
|