custom enumerator, com interop, and vb6 client

Context:

I have a legacy VB6 application that uses DAO and Jet. The owner of the company does not want to change any of the DAO code as we move into VB.Net, so I've been working on a set of VB wrappers to present an interface that looks just like DAO. The advantage to this is that we can subsequently replace the underlying DAO code in the wrappers with ADO.Net to use SQL Server in the future.

The design:

I have a set of abstract classes that look an awful lot like the DAO object model. Underlying that, I have implemented a set of concrete classes from each of the abstract classes and implemented a pass-through to each of the DAO routines. DAO continues to manage the database access, for now. Ultimately, I or someone else, will write a new set of concrete classes for some other DB engine. So far, this is working out pretty well.

The problem:

I have implemented custom enumerators for each of the DAO collection objects as per http://msdn2.microsoft.com/en-us/library/1t2267t6(VS.80).aspx.

The legacy VB6 code now looks like

dim idx as ABXDB.Index

For Each idx in ABXDB.TableDefs.Indexes

This throws a Type Mismatch error on the For Each because, I think, the Current property of the custom enumerator is returning an Object and VB6 can't make the conversion to the ABXDB.Index type. I thought I might change the definition of Current to return an ABXDB.Index type as well, but that didn't fly as the IEnumerator doesn't define such a thing.

I've also tried changing the VB6 code to

dim obj as object
dim idx as ABXDB.Index

For each obj in ABXDB.TableDefs.Indexes

set idx = obj

But that fails in the same way. Similar results if I declare the loop var as variant.

So I think I've got a com interop problem and I have no clue how to move forward. What are my options

Sean



Answer this question

custom enumerator, com interop, and vb6 client

  • Wayner7

    Hot Dawg, Peter! This was what I needed to get over the hump. It turns out that I was darn close on my own, with your earlier posts. The problem that I had was in the custom enumerator class, I was returning a DAO object, and not one of my abstract objects. Looking at your code gave me the clue, and it's now working!

    Thanks ever so much!

    Sean


  • Aldrenn

    Well, after posting a while ago, I discovered that the syntax in question is intended to decorate the return type of the GetEnumerator property in VB, so it ought to look something like:

    Public Function GetEnumerator() As <System.Runtime.InteropServices.MarshalAsAttribute(UnmanagedType.CustomMarshaler, MarshalType = "System.Runtime.Interop-Services.CustomMarshalers.EnumeratorToEnumVariantMarshaler,CustomMarshalers, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")> System.Collections.IEnumerator Implements System.Collections.IEnumerable.GetEnumerator

    However, it's now complaining that the

    Name 'MarshalType' is not declared.

    I'm guessing that C# (which I admittedly know little about) handles named parameters while VB does not. I thought that I rememberd VB6 doing this, but I may be thinking of something else.

    I still don't understand all of the implications of the MarshalAsAttribute decoration - I see that it has lots of parameters, but so far, I've not found a construction that compiles.

    I must be getting closer, but I still feel far, far away.

    Sean


  • mjoc69

    Hi ,

    Based on my understanding, you wanted to expose the .NET class to COM. So that the COM client(VB6 application) can access to the class via foreach.
    If I misunderstood, please feel free let me know.

    I think you may try to decorate the GetEnumerator with the Marshal attribute.
    e.g.
    [DispId(-4)]
    [return:
    MarshalAs(UnmanagedType.CustomMarshaler,MarshalType="System.Runtime.Interop-S
    ervices.CustomMarshalers.EnumeratorToEnumVariantMarshaler,CustomMarshalers,
    Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")]
    public IDictionaryEnumerator GetEnumerator()

    Also as you said, we need to expose the type we hope the current to return too.
    What do you mean by "ABXDB.Index"
    Do you mean the type DAO.Index in VB6
    If so, I think you may try add reference to the DAO COM Library, so that you will get a .NET wrapper for the type. Or you can define one yourself.

    Here is a link for your reference.
    http://groups.google.com/group/microsoft.public.dotnet.framework.interop/browse_frm/thread/7ca14c08352adb69/94ca86ddae382ead lnk=st&q=%22v-phuang%22+IEnumerable+for+each+vbscript&rnum=1&hl=en#94ca86ddae382ead


    If you still have any concern, please feel free to post here.

    Best regards,
    Peter Huang



  • LiLo

    Digging deeper, I discovered that named parameters in attributes use :=, so my declaration needed to look like

    <System.Runtime.InteropServices.DispId(-4)> _

    Public Function GetEnumerator() As <System.Runtime.InteropServices.MarshalAsAttribute(UnmanagedType.CustomMarshaler, MarshalType:="System.Runtime.Interop­Services.CustomMarshalers.EnumeratorToEnumVariantMarshaler,CustomMarshalers, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")> System.Collections.IEnumerator Implements System.Collections.IEnumerable.GetEnumerator

    Return New CTableDefsEnumerator(moInternal_DAO_TableDefs)

    End Function

    By the way, I discovered this by reading through the O'Reilly VB.Net 2005 in a Nutshell. I LOVE the O'Reilly Nutshell books - I've got a half dozen of them for various topics. If you don't use them, you should!

    So, it now compiles cleanly, but the GetEnumerator routine is never called. I've set a breakpoint on the on the return statement and on the appropriate routines in the CTableDefsEnumerator class and they are never hit. Something is still quite wrong.

        Sean

     


  • windiswebwerkstatt

    Hi,

    I am sorry for that.
    I am considered the issue as a ComInterop issue and I did not notice this is a VB.NET forum.
    Anyway, here goes the code in VB.NET version for your reference.
    '[.NET class] which will expose a customer class TestObj.
    Imports System.Runtime.InteropServices
    '<ClassInterface(ClassInterfaceType.AutoDual)> _
    Public Class TestObj
    Dim _name As String
    Public Sub New(ByVal index As Integer)
    _name = "Object " & index.ToString() & ": " & Rnd().ToString()
    End Sub
    Public Property Name() As String
    Get
    Return _name
    End Get
    Set(ByVal value As String)
    _name = value
    End Set
    End Property
    End Class
    '<ClassInterface(ClassInterfaceType.AutoDual)> _
    Public Class TestObjCol
    Implements ICollection, IEnumerator
    Dim _objs As ArrayList = New ArrayList()
    Dim ide As IEnumerator
    Public Sub New()
    For i As Integer = 1 To 10
    _objs.Add(New TestObj(i))
    Next
    End Sub
    Public Sub CopyTo(ByVal array As System.Array, ByVal index As Integer) Implements System.Collections.ICollection.CopyTo
    _objs.CopyTo(array, index)
    End Sub

    Public ReadOnly Property Count() As Integer Implements System.Collections.ICollection.Count
    Get
    Return _objs.Count
    End Get
    End Property

    Public ReadOnly Property IsSynchronized() As Boolean Implements System.Collections.ICollection.IsSynchronized
    Get
    Return _objs.IsSynchronized
    End Get
    End Property

    Public ReadOnly Property SyncRoot() As Object Implements System.Collections.ICollection.SyncRoot
    Get
    Return _objs.SyncRoot
    End Get
    End Property
    <DispId(-4)> _
    Public Function GetEnumerator() As <MarshalAs(UnmanagedType.CustomMarshaler, MarshalType:="System.Runtime.InteropServices.CustomMarshalers.EnumeratorToEnumVariantMarshaler,CustomMarshalers,Version=2.0.0.0, Culture=Neutral, PublicKeyToken=b03f5f7f11d50a3a")> System.Collections.IEnumerator Implements System.Collections.IEnumerable.GetEnumerator
    Debug.WriteLine("GetEnumerator")
    ide = _objs.GetEnumerator()
    Dim i As IEnumerator = Me
    Return i
    End Function

    Public ReadOnly Property Current() As Object Implements System.Collections.IEnumerator.Current
    Get
    Debug.WriteLine(ide.Current.ToString())
    Dim o As TestObj = ide.Current
    Return o
    End Get
    End Property

    Public Function MoveNext() As Boolean Implements System.Collections.IEnumerator.MoveNext
    Return ide.MoveNext()
    End Function

    Public Sub Reset() Implements System.Collections.IEnumerator.Reset
    ide.Reset()
    End Sub
    End Class


    I call it from VB6 via the code below.

    Private Sub Command1_Click()
    Dim obj As Enumerators2COM.TestObj
    Dim objs As New Enumerators2COM.TestObjCol
    For Each obj In objs
    Debug.Print obj.Name
    Next
    End Sub

    If you still have any concern, please feel free to post here.

    Best regards,
    Peter Huang



  • Matt Ahlgren

    See the next post for updates:

    ----------------------------------------------

    Thanks for your reply, Peter.

    I should have mentioned that my wrapper classes are written in VB.Net and I'm having some trouble converting the decoration to a syntax that VB likes. Specifically, I can't seem to find a construct for

                    [return:
    MarshalAs(UnmanagedType.CustomMarshaler,MarshalType="System.Runtime.Interop­S
    ervices.CustomMarshalers.EnumeratorToEnumVariantMarshaler,CustomMarshalers,
    Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")]
                    public IDictionaryEnumerator GetEnumerator()

    that the VB compiler will accept. I've looked around on the web to see if there's an example, but I so far haven't come up with one. The closest I've come (by guessing) is

    <System.Runtime.InteropServices.MarshalAs(UnmanagedType.CustomMarshaler, MarshalType = "System.Runtime.Interop­Services.CustomMarshalers.EnumeratorToEnumVariantMarshaler,CustomMarshalers, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")> _
    Public Function GetEnumerator() As System.Collections.IEnumerator Implements System.Collections.IEnumerable.GetEnumerator

    Return New CFieldsEnumerator(moInternal_DAO_Fields)

    End Function

    but this generates a compiler error that says

    Error 5 Attribute 'MarshalAsAttribute' cannot be applied to 'GetEnumerator' because the attribute is not valid on this declaration type. 

    I have been able to apply the Dispid(-4) attribute without error, but by itself, that doesn't solve my problem.

    This is the deepest that I've gotten so far into Interop, and I'm at the very ragged edge of what I understand. Any clues are most appreciated.

    To answer your other question,

        What do you mean by "ABXDB.Index"
        Do you mean the type DAO.Index in VB6

    I have defined abstract database classes for nearly every corresponding DAO object. So I have a

        Public Class AbstractDatabase
        Public Class AbstractTableDef

        Public Class AbstractIndex

    each with suitable MustInherit interfaces to ensure a child correctly implements the right things.

    Then, I have defined a set of concrete classes for Jet

        Public Class JetDatabase
             Inherits AbstractDatabase
        Public Class JetTableDef
             Inherits AbstractTableDef

    and so on.

    Lastly, in my legacy code, any declaration that relates to a DAO object is changed to refer to the corresponding Abstract object, and I can then maintain very nearly all of the syntactical constructs that were written for the DAO objects, but now do essentially the same thing through an extra layer of indirection (i.e., through my wrappers). As I said in my earlier post, the goal of this exercise is to allow someone else to write another set of concrete classes for SQL Server using ADO.Net. The fact that they present the same interface to the legacy code means that I shouldn't have to make any other changes to connect to a new DB.

    So far, with the exception of the enumerators not working as I think they should, this approach is working well, albeit with some negative performance hits. It remains to be seen whether we can shoehorn ADO.Net into this scheme.

    Thanks for your help.

       Sean


     

     


  • custom enumerator, com interop, and vb6 client