Struggling creating a COM object with Visual Studi

2019-08-28 01:53发布

问题:

I am struggling trying to build a COM object in Visual Studio (2010) in VB.NET which can be called from a VBScript program run from the command line. I want the script to be able to do something like ...

Option Explicit
Dim trips
Dim results

Set trips = CreateObject("Hartley.Trips")
Set results = trips.selecttrips(57,now())
wscript.echo results.count

However whatever I do, this script fails with:

ActiveX component can't create object:'Hartley.Trips

The core of the COM object definition is ...

<ComClass(Trips.ClassId, Trips.InterfaceId, Trips.EventsId)> _
Public Class Trips

#Region "COM GUIDs"
    ' These  GUIDs provide the COM identity for this class
    ' and its COM interfaces. If you change them, existing
    ' clients will no longer be able to access the class.
    Public Const ClassId As String = "75184b72-31e3-4d62-add6-438230d17bc3"
    Public Const InterfaceId As String = "c020fef9-0425-4200-a2b4-77931c0a6011"
    Public Const EventsId As String = "4cf93b6e-f554-491b-94bd-5b02c3c6c586"
#End Region
    Private Const connStr As String = "redacted"
    Private dbConnection As SqlClient.SqlConnection
    Private connectionInitialised As Boolean = False

    ' A creatable COM class must have a Public Sub New()
    ' with no parameters, otherwise, the class will not be
    ' registered in the COM registry and cannot be created
    ' via CreateObject.

    Public Sub New()
        MyBase.New()
    End Sub

    Public Class Trip
        Private myTripID As Integer
        Private myIsComplete As Boolean
        Private myStart As DateTime
        Private myEnd As DateTime
        Private myHasStarted As Boolean
        Public Sub New(ByVal iTripID As Integer, ByVal bIsComplete As Boolean, _
                       ByVal dStart As DateTime, ByVal dEnd As DateTime, _
                       ByVal bHasStarted As Boolean)
            myTripID = iTripID
            myIsComplete = bIsComplete
            myStart = dStart
            myEnd = dEnd
            myHasStarted = bHasStarted
        End Sub

        Public ReadOnly Property TripID() As Integer
            Get
                Return myTripID
            End Get
        End Property

        Public ReadOnly Property Iscomplete() As Boolean
            Get
                Return myIsComplete
            End Get
        End Property

        Public ReadOnly Property StartDate() As DateTime
            Get
                Return myStart
            End Get
        End Property

        Public ReadOnly Property EndDate() As DateTime
            Get
                Return myEnd
            End Get

        End Property

        Public ReadOnly Property hasStarted() As Boolean
            Get
                Return myHasStarted
            End Get
        End Property

    End Class

    Public Class Results
        Private mySelectID As Integer
        Private myTripDay As Date
        Private myIsDriver As Boolean
        Private myTripArray As List(Of Trip)
        Public Sub New(ByVal iSelectID As Integer, dTripDay As Date, _
                       bIsDriver As Boolean, iCount As Integer)
            myTripArray = New List(Of Trip)
            mySelectID = iSelectID
            myTripDay = dTripDay
            myIsDriver = bIsDriver
        End Sub

        Public ReadOnly Property numTrip() As Integer
            Get
                Return myTripArray.Count
            End Get
        End Property

        Public ReadOnly Property SelectID() As Integer
             Get
                Return mySelectID
            End Get
        End Property

        Public ReadOnly Property ReportDate() As Date
            Get
                Return myTripDay
            End Get
        End Property

        Public ReadOnly Property isDriver() As Boolean
            Get
                 Return myIsDriver
            End Get
        End Property

        Public ReadOnly Property TripArray() As Array
            Get
                Return myTripArray.ToArray
            End Get

        End Property

        Public Sub Add(aTrip As Trip)
            myTripArray.Add(aTrip)
        End Sub
    End Class

    Public Function SelectTrips(SelectID As Integer, TripDay As Date, _
                                Optional isDriver As Boolean = False) As Results
        Dim myTrip As Trip
        Dim myEndDate As DateTime
        Dim cmd As SqlClient.SqlCommand
        Dim rs As SqlClient.SqlDataReader

        If Not connectionInitialised Then 'If this is first attempt
            dbConnection = New SqlClient.SqlConnection(connStr)
            dbConnection.Open()
            connectionInitialised = True
        End If

        'Test Only
        cmd = New SqlClient.SqlCommand("SELECT * FROM xxx", dbConnection)
        rs = cmd.ExecuteReader
        Dim myTrips As New Results(SelectID, TripDay, isDriver, rs.FieldCount - 1)
        While rs.Read
            If IsDBNull(rs("Completed")) Then
                myEndDate = Now()
            Else
                myEndDate = rs("completed")
            End If
             myTrip = New Trip(rs("runID"), _
                               IsDBNull(rs("completed")), _
                               rs("runDate"), _
                               myEndDate, _
                               rs("started"))
            myTrips.Add(myTrip)
        End While
        Return (MyTrips)
    End Function

End Class

In Visual Studio, the project parameters specify this as sitting within a root namespace of "Hartley" and the assembly information has the "Make Assembly COM visible" checked. I have also generated a key and added that to the project.

When I "build the solution", I have to do so with Visual Studio running as Administrator because it wants to write to the registry. Looking at the registry, I can see that there are registry entries for "Hartley.Trips".

Searching around the Internet, there are plenty of instructions to do this - and I appear to have followed them all EXCEPT running the command line tools. What is confusing here is that Visual Studio 2010 appears to have run them for me - and given all the instructions that I can find apply to earlier versions of Visual Studio - it appears they didn't. I have tried running these programs manually from Visual Studio Command Line, but it didn't seem to change things.

So now I am stuck. It doesn't work, the script is very unforthcoming about why it can't create the object, and I don't know how to move forward. So I am looking to advice as to what I might be doing wrong, how I can find out more about what is not working ,etc.

Just as a postscript, I am wondering if the embedded class within a class is part of the problem and am now trying to build an assembly which separates the classes into separate top level class definitions each as their own COM object. The classes which currently have a New function with parameters will have to have a New function without them and some form of Friend function to initialise them (I don't want the initialize function visible as the other classes are read only results).

回答1:

Write some VB.NET code to test your COM component. The CLR generates much better error messages.

You will have trouble like this on a 64-bit operating system. The IDE only registers the COM component for 32-bit apps. You'll have to use the 32-bit script interpreter (c:\windows\syswow64\wscript.exe) or run the 64-bit version of Regasm.exe to register the component for 64-bit apps with the /codebase option.

Avoid nested classes, COM has no equivalent for that. The most natural way to declare a COM visible declaration is with the Interface keyword. Exactly what you need here for the Results class.