Using ActiveX Components from .NET

Using ActiveX Components from .NET

This section discusses common issues that you may encounter when using an ActiveX control or component in your Visual Basic application after upgrading to Visual Basic .NET.

When ByRef Bites

The Visual Basic .NET compiler introduces a new semantic when passing properties ByRef to a function. If the property has write access, it is set to the return value of the ByRef parameter. In Visual Basic 6 this is not the case. If you pass a property ByRef, the property is never set.

As an example, consider the following Visual Basic 6 code contained in Form1 of a default standard EXE project:

Private Sub Form_Load() SetString Me.Caption MsgBox Me.Caption End Sub Private Sub SetString(ByRef str As String) str = "String changed by SetString" End Sub

When this code is run, the message box displays Form1. Me.Caption is not changed by calling the function SetString.

Consider the following code that has been upgraded to Visual Basic .NET:

Private Sub Form1_Load(ByVal eventSender As System.Object, _ ByVal eventArgs As System.EventArgs) _ Handles MyBase.Load SetString(Me.Text) MsgBox(Me.Text) End Sub Private Sub SetString(ByRef str_Renamed As String) str_Renamed = "String changed by SetString" End Sub

If you run the Visual Basic .NET version, you will see that the Text property of the form gets set to String changed by SetString as a result of calling SetString.

This is both good and bad. It s good that Visual Basic .NET offers this new semantic so that properties passed ByRef are set the way you would expect. It s bad, however, in that this change could lead to subtle, hard-to-find problems in your upgraded code.

This issue does not end with your own code. You may run into it when attempting to call an ActiveX component that takes ByRef parameters. Consider the following Visual Basic .NET code written against a ListView control located on Form1 in a default Windows application project:

Dim lvItem As MSComctlLib.ListItem AxListView1.ListItems.Add(, , "Item1") lvItem = AxListView1.ListItems(AxListView1.ListItems.Count)

Run this code and you will encounter a COMException on the last line, telling you that Property is read-only. What property is read-only? you might ask. How am I going to figure this one out?

The problem in this case is that the ListItems default property Item takes a ByRef Variant argument. AxListView1.ListItems.Count is passed as an argument to this ByRef parameter. Shouldn t this be okay? Isn t the Count property read-only, so the compiler won t try to set it? Yes and no. The Count property is implemented with both a Get and a Let. Therefore, the compiler thinks the property can be written. When it tries to write to the property, however, the Let implementation for the Count property throws an exception.

The reason the ListView ListItems subobject has a settable Count property is a long, twisted story stretching back three versions of Visual Basic. To work around these types of issues, you can include parameters that you don t want to be set in parentheses. Using parentheses tells the compiler to pass the property read-only. For example, if you change the code in the previous example to the following, it will work. Note the extra parentheses around AxListView1.ListItems.Count:

' Pass AxListView1.ListItems.Count ByVal to avoid Set being called lvItem = AxListView1.ListItems((AxListView1.ListItems.Count))

When a Collection Is Not a Collection

Just because a .NET collection looks like a collection does not mean that it really is a collection at least not in the Visual Basic 6 sense. A .NET collection is almost the same as a Visual Basic 6 collection. It has the same methods, such as Add, Remove, and Item, but you cannot assign a .NET collection to a Visual Basic 6 collection. Why would you need to use a Visual Basic 6 collection? Isn t this .NET? Don t you want all of your types to be declared in .NET? Isn t the .NET collection a new and improved Collection object? Yes on all counts, but there is one case in which you will need to use a Visual Basic 6 collection: when you call a COM object that takes a Visual Basic 6 Collection object as a parameter or that returns a Visual Basic 6 Collection object. A .NET collection will not do in this case.

Take, for example, the following Visual Basic 6 code declared in a public class module of an ActiveX DLL called RetCollection.dll:

Option Explicit Private m_Collection As New Collection Public Function ReturnCollection() As Collection Set ReturnCollection = m_Collection End Function Private Sub Class_Initialize() m_Collection.Add "MyItem1", "MyKey1" m_Collection.Add "MyItem2", "MyKey2" End Sub

Now consider the following Visual Basic .NET code, which calls the ReturnCollection function:

Dim c As Collection Dim rc As New RetCollectionLib.RetCollection() c = rc.ReturnCollection MsgBox(c.Item(1))

The code compiles, runs up to a point, at least and throws an InvalidCastException on the attempt to assign c to rc.ReturnCollection. The problem is that a .NET collection represented by the variable c cannot be assigned to the Visual Basic 6 Collection object returned by the function ReturnCollection.

To fix this problem, you need to add a reference to the Visual Basic 6 run time Msvbvm60.dll. Follow these steps to do so:

  1. Right-click the References list in Solution Explorer, and choose Add Reference.

  2. Select the COM tab.

  3. Select Visual Basic For Applications Version 6.0 from the list.

  4. Change your Visual Basic .NET code to use the Visual Basic 6 Collection object as follows:

     Dim c As VBA.Collection

The variable c now matches the type returned by the ReturnCollection function, so everything will now work as expected.

Nonzero-Bound Arrays

You may encounter problems when attempting to call a COM object method that returns an array defined with a nonzero-bound lower dimension, such as 10 or 1. Take, for example, the following code defined in a Visual Basic 6 ActiveX server DLL:

Option Base 1 Public Function RetStringArray() As String() Dim i As Long 'Array is 1-based. We're using Option Base 1 Dim s(10) As String For i = LBound(s) To UBound(s) s(i) = i Next RetStringArray = s End Function

Suppose that you are trying to call the RetStringArray function, using the following Visual Basic .NET code:

Dim rs As New RetStringArrayLib.RetStringArray Dim s() As String s = rs.RetStringArray MsgBox(s(1))

The code will compile, but you will encounter an InvalidCastException at run time when trying to assign s the return value of rs.RetStringArray. The problem occurs because you are attempting to assign a nonzero-based string array to a zero-based string array variable. To fix this, you need to change the declaration of s from a strongly typed String array always zero based to a generic System.Array type, as follows:

Dim s As System.Array

The generic System.Array type can represent a nonzero-based array but cannot be assigned to a strongly typed array unless it is zero bound.

Alias Types Are Not Supported

Type aliasing is declaring a new type based on an existing type. A common example is the OLE_ types defined in the Standard OLE Automation type library (StdOle2.tlb). The type OLE_COLOR, for example, is an alias for an unsigned 32-bit long integer. When you reference a COM object in .NET, type aliases are lost. Instead you need to use the base type. For example, you need to use the .NET System.UInt32 type instead of OLE_COLOR.

note

Neither Visual Basic 6 nor Visual Basic .NET allows you to declare a type that is an alias of another type. In the case of Visual Basic 6, however, you can use aliased types from other type libraries. For example, you can create a Visual Basic 6 component with a public BackColor property of type OLE_COLOR. If you attempt to use the component in .NET, the property type will show up as System.UInt32.

Module Methods Are Not Supported

If you have a Visual Basic 6 project that references a COM component that exports module methods, the module methods are not available to be called by a .NET client. For example, the DirectX Visual Basic type library Dx8vb.dll contains a large number of module methods that can be called from Visual Basic 6. When you upgrade Visual Basic 6 code that calls module methods, the calls are left in the code, but the code doesn t compile.

Since module methods delegate to exported functions within a DLL, the trick to solving this problem is finding the DLL-exported function that the module method calls. To do this, you can dump the list of exported functions for the DLL, but first you need to find the DLL containing the module method you want to call. The easiest way to find it is to load the original Visual Basic 6 project in Visual Basic 6 and find the DLL reference within the References list. To view the References list, choose References from the Project menu. Once you have located the reference within the list for example, DirectX 8 For Visual Basic Type Library note the location of the DLL for that reference. Open a DOS command window and dump the exported functions contained in the DLL by executing the following command:

DumpBin /Exports DX8VB.DLL > Exports.Txt

Launch Notepad and open Exports.txt. Look for a function that matches the name of the module method that your Visual Basic 6 code calls. For example, if your Visual Basic 6 code calls the DirectX function D3DXColorAdd, you will find the following entry in the DLL exports list:

 105 15 0002DB8F VB_D3DXColorAdd

You can use this information to declare the API entry point in your Visual Basic code. The most useful information in this cryptic entry is the entry ID contained in the first column and the name of the function. In this case the entry ID is 105 and the function name is VB_D3DXColorAdd.

Suppose, for example, that you have upgraded a Visual Basic 6 function that calls the DirectX function D3DXColorAdd and you end up with the following Visual Basic .NET code:

Dim COut As DxVBLibA.D3DCOLORVALUE Dim CRed As DxVBLibA.D3DCOLORVALUE Dim CBlue As DxVBLibA.D3DCOLORVALUE CRed.r = 255 CBlue.b = 255 'UPGRADE_ISSUE: COM expression not supported: Module methods of 'COM objects. Click for more: 'ms-help://MS.VSCC/commoner/redir/ 'redirect.htm?keyword="vbup1060"' DxVBLibA.D3DXMATH_COLOR.D3DXColorAdd(COut, CRed, CBlue)

This code leads to a compiler error, since D3DXColorAdd, as suggested by the UPGRADE_ISSUE comment, is not available. Based on the Exports.txt DLL export information obtained earlier, you can declare the function as follows:

Private Declare Function D3DXColorAdd Lib "dx8vb.dll" _ Alias "#105" (ByRef COut As DxVBLibA.D3DCOLORVALUE, _ ByRef C1 As DxVBLibA.D3DCOLORVALUE, ByRef c2 As _ DxVBLibA.D3DCOLORVALUE) As Integer

In this case we re using the entry ID 105 as the name of the function. You could also declare the function to use the API function name VB_D3DXColorAdd in the Alias clause. To obtain the full declaration for the function, you can load your original Visual Basic 6 project in Visual Basic 6 and start the Object Browser. With the Object Browser running, you can search for the module method declaration for D3DXColorAdd. The module method declaration will match the API declaration you need to create using the Declare statement.



Upgrading Microsoft Visual Basic 6.0to Microsoft Visual Basic  .NET
Upgrading Microsoft Visual Basic 6.0 to Microsoft Visual Basic .NET w/accompanying CD-ROM
ISBN: 073561587X
EAN: 2147483647
Year: 2001
Pages: 179

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