1
votes

I have a COM+ dll installed in a COM application on 2 remote servers. I have exported a non-queued proxy from one of the servers and installed it on a client machine.

I want to be able to specify which remote server the COM object is instantiated on at runtime - the proxy installation contains the Remote Server Name of the machine I exported the proxy from, so simply creating the proxy on the client will always call the machine I exported it from, since it's part of the proxy properties.

The proxy is not queued, so I can't use the method of calling GetObject with a PathName like queue:ComputerName=Server01/new:ComClass.Class.

For non-queued proxies that will always call the remote server they were exported from I just use CreateObject(objectName) and it will use the Remote Server Name from the proxy properties.

After some searching I found a solution (in my self-answer below) but is there a simpler way of doing it within VB6 without having to use the ole32.dll functions?

Edit: After testing the solution proposed by @Bob77 in the comments, called CreateObject with the server name parameter has no effect. Only using the CreateRemoteObject method outlined actually calls the COM component on the specified server.

This may be because the client call is from an IIS process and the user identity of the remote server's COM+ application is different.

1
Why not just provide the server name in the second parameter of a CreateObject call? That's what it is there for. - Bob77
@Bob77: I believe that's the answer being sought here. Finding documentation of VB6 calls can be quite a challenge, given how obsolete the language is. - user65839
@Bob77 We use proxies because the server component has to run in a COM+ application under a specific user identity, whereas the proxy can be instantiated in an IIS process. I honestly don't know whether my solution is using the IIS user identity or the remote COM+ application identity - finding useful VB6 documentation is a nightmare! - Matt Hogan-Jones
@PeterCooperJr. You're right about the lack of documentation! I need to maintain this code, and I'm not 100% sure why it's like this and not like Bob77's suggestion so I think a lot of testing might be called for... - Matt Hogan-Jones
@Bob77 - your proposed solution doesn't work. Simply putting the server name in the call to CreateObject does not result in the COM class getting called on the remote server. No idea why though, but I suspect this is why the current solution is present. - Matt Hogan-Jones

1 Answers

0
votes

This can be done using the CoCreateInstanceEx function from the ole32.dll library.

First declare the required functions from ole32.dll and the corresponding data structures:

Private Type SERVER_STRUCTURE
   reserved1   As Long
   pServer     As Long
   AuthInfo    As Long
   reserved2   As Long
End Type

Private Type MULTI_QI
   pIID        As Long
   pInterface  As Object
   hResult     As Long
End Type

Private Declare Function CLSIDFromProgID Lib "ole32.dll" _
                 (progid As Any, clsid As Any) As Long

Private Declare Function OleInitialize Lib "ole32.dll" _
                 (ByVal Nullptr As Long) As Long

Private Declare Function CoCreateInstanceEx Lib "ole32.dll" _
                 (clsid As Any, ByVal pUnkOuter As Long, _
                  ByVal Context As Long, server As SERVER_STRUCTURE, _
                  ByVal nElems As Long, mqi As MULTI_QI) As Long

I then use this function that takes in the object name and server name and returns an instance of the object, which would be a proxy to the required server:

Private Function CreateRemoteObject(ByVal ObjectName As String, _
                   ByVal ByVal serverName As String) As Object

    Dim clsid(256) As Byte
    Dim progid() As Byte
    Dim server() As Byte
    Dim queryInterface As MULTI_QI
    Dim serverStructure As SERVER_STRUCTURE
    Dim refiid(16) As Byte
    Dim longReturnCode As Long
    Dim errorString As String

    errorString = ""

    GetInterfaceIDforIDispatch refiid()     ' set an interface ID for IDispatch
    queryInterface.pIID = VarPtr(refiid(0)) ' point to the interface ID
    progid = ObjectName & Chr$(0)           ' specify the object to be launched
    server = serverName & Chr$(0)           ' specify the server
    OleInitialize 0                         ' initialise OLE
    longReturnCode = CLSIDFromProgID(progid(0), clsid(0))   ' get the CLSID for the object

    If longReturnCode <> 0 Then
        errorString = "Unable to obtain CLSID from progid " & ObjectName
        App.LogEvent errorString, vbLogEventTypeError
        Exit Function
    End If

    ' point to server name and invoke a remote instance of the desired object
    serverStructure.pServer = VarPtr(server(0))
    longReturnCode = CoCreateInstanceEx(clsid(0), 0, 16, serverStructure, 1, queryInterface)

    If longReturnCode <> 0 Then
        errorString = "CoCreateInstanceEx failed with error code " & Hex$(longReturnCode)
        App.LogEvent errorString, vbLogEventTypeError
        Exit Function
    End If

    ' Pass back object ref
    Set CreateRemoteObject = queryInterface.pInterface
End Function

Private Sub GetInterfaceIDforIDispatch(p() As Byte)
    ' fills in the well-known IID for IDispatch into the byte array p.

    p(1) = 4
    p(2) = 2
    p(8) = &HC0
    p(15) = &H46
End Sub