?
Solved

Class Not Registered (80040154) when I CoCreateInstance of CUIAutomation in VBA

Posted on 2010-04-05
10
Medium Priority
?
2,223 Views
Last Modified: 2012-06-27
I'm working at place where the workstations are still on XP, and I'm trying to automate some tasks involving IE and Excel.

I'm using VBA to open IE, navigate, and fill out forms, but there comes a point where I need to automate a Save As dialog.

I want to use the Windows Automation API for this, so I've included a reference to UIAutomationClient (uiautomationcore.dll).  A call to CoInitialize returns S_FALSE, indicating that COM is already initialized.  So I follow the next step described in http://msdn.microsoft.com/en-us/library/ee671202(v=VS.85).aspx, which is to create an instance of the CUIAutomation object.

comRes = CoCreateInstance(CUIAutID, 0, ByVal &H1, IUIAutID, uiAutClient)

where I have the clsids defined here

    Set CUIAutID = New UUID
    Set IUIAutID = New UUID

    With CUIAutID
        .Data1 = &HFF48DBA4
        .Data2 = &H60EF
        .Data3 = &H4201
        .setData4 0, &HAA
        .setData4 1, &H87
        .setData4 2, &H54
        .setData4 3, &H10
        .setData4 4, &H3E
        .setData4 5, &HEF
        .setData4 6, &H59
        .setData4 7, &H4E
    End With
   
    With IUIAutID
        .Data1 = &H30CBE57D
        .Data2 = &HD9D0
        .Data3 = &H452A
        .setData4 0, &HAB
        .setData4 1, &H13
        .setData4 2, &H7A
        .setData4 3, &HC5
        .setData4 4, &HAC
        .setData4 5, &H48
        .setData4 6, &H25
        .setData4 7, &HEE
    End With

The return value is 80040154, so one of the classes isn't registered.  I can see that the CUIAutomation clsid is in the registry as "UIAutomation Client Central Class", but the IUIAutomation clsid, which I got from UIAutomationClient.h, is not.

At this point it looks to me like the key to my problem lies in getting the IUIAutomation interface registered.  But I'm not a COM expert.  Can you register an interface the same way you register a class?  Could it be that the CUIAutomation class isn't actually registered?  Could there be a problem with using a Class instead of a Type to represent the UUID?

Any pointers would be greatly appreciated!  Thanks.
0
Comment
Question by:giles_baxter
  • 4
  • 4
  • 2
10 Comments
 
LVL 38

Expert Comment

by:puppydogbuddy
ID: 29911417
Have not worked with the UI automation objects, but had a reference to this tutorial in my files and thought it might help:
                           http://devhood.com/Tutorials/tutorial_details.aspx?tutorial_id=478
excerpt:
2. You'll also benefit from the Messenger API documents (CHM - or Compiled HTML Help - files), which you can get from http://messenger.msn.com/for_developers/default.asp. If you have a newer version of Messenger, you already have the automation interfaces registered on your system.

OLEView.exe showed me the light once again, however, and I ended up using the CLSID's for both the Messenger.MsgrObject and Messenger.UIAutomation objects, the latter of which implements all the things that Visual Basic could use so easily.

Writing a Advanced Script using the Messenger API's
We will finally create a page that works. We will use two objects, one of which is undocumented, but is described thoroughly by OLEView.exe if you understand IDL (the "COM language").

We can't rely on the Messenger.UIAutomation object since it apparently returns "undefined" when hosted in IE (or perhaps any scripting language). The other object (Messenger.MsgrObject) we will use does the trick, but it's by no means as elegant as Messenger.UIAutomation, which provides more functionality that actually does work in a scripting environment.
0
 
LVL 38

Expert Comment

by:puppydogbuddy
ID: 29912125
As to Com objects in general:
applications using some COM objects (dlls created in vb6 or c++, etc), must register them on the server/development machine.

To register a dll file:
   1- Start -> Run. Type cmd, click OK
   2- On the command prompt type regsvr32 "path_to_your_dll\dll_name", then press enter. This will register your dll file.
 
0
 

Author Comment

by:giles_baxter
ID: 29987410
Thanks puppydogbuddy for your comments.

I have registered the uiautomationcore.dll - I believe I wouldn't be able to see the UIAutomationClient library in the VBA object browser if that hadn't worked.  Also, I can see the CLSID for the CUIAutomation class in the registry.

I'm not sure how the link to the Messenger.UIAutomation article can help me...  This place is locked down so tight the machines don't even have messenger, so I can't play with that.  I did find an article http://visualbasic.ittoolbox.com/documents/createobject-vs-new-12138 showing that the New and CreateObject keyword/call make calls to CoCreateInstance...so the failure of CoCreateInstance explains the previous failures of those.

I guess I should be definitive about what I'll accept as a solution!

1. steps that I can follow so that this code will result in uic holding a reference to a valid object:

    Dim uic As UIAutomationClient.CUIAutomation
    Set uic = New UIAutomationClient.CUIAutomation

or

2. an alternate solution that will allow me to programmatically, from VBA, choose the path and set the filename in a Save As dialog in IE (not using SendKeys, because if the directory structure changes I'd have to use a different sequence of keys)

Further, the Save As dialog I'm trying to automate comes from a File Download dialog.  I'm using the HTMLDocument object and members to follow links, SendKeys to deal with File Download - but am stuck on Save As.

I can't go lower level (WinHTTP) because there's a chain of JavaScript calls opening new windows that I couldn't handle.
0
Never miss a deadline with monday.com

The revolutionary project management tool is here!   Plan visually with a single glance and make sure your projects get done.

 
LVL 29

Accepted Solution

by:
nffvrxqgrcfqvvc earned 1200 total points
ID: 30134830
I wrote an easy to use automation class that might help you with dialogs.
The first thing it does is look for any dialogs that are visible. The base class Dialog() has Start() and Cancel() methods respectively and an Interval property at which rate to check for dialogs.
When a dialog is found it will raise the OnDialog() event. You can then use the IDialog interface to work with that dialog instance. This includes a few useful methods for automating the dialogs see example (Usage) in code section.
 
1) Add a new (Module.bas) named: Dialogs.bas
2) Add a new (Class.cls) named: Dialog.cls
3) Add a new (Class.cls) named: IDialog.cls
' Add the following code as appropriate for each module/class.
 

'//
'// Dialogs.bas
'//


Option Explicit

' egl1044

Public Const DialogClass As String = "#32770"

Public Enum DialogFlags
  ' FileDownload Dialog
  [FileDownloadDialog_Button_Open] = &H114A&
  [FileDownloadDialog_Button_Save] = &H114B&
  [FileDownloadDialog_Button_Cancel] = &H2&
  ' Download Complete
  [DownloadCompleteDialog_Button_Open] = &H1119&
  [DownloadCompleteDialog_Button_OpenFolder] = &H1118&
  [DownloadCompleteDialog_Button_Close] = &H2&
  ' SaveAs, Save Picture As
  [DlgButton_Save] = &H1&
  [DlgButton_Cancel] = &H2&
  ' Overwrite Modal Dialog box
  [DlgButton_Yes] = 6
  [DlgButton_No] = 7
  '
  [DlgFileName] = &H47C&
  [DlgType] = &H470&
  [DlgStatic] = &HFFFF&
End Enum


Public Const MAX_PATH = 260
Public Const WM_GETTEXT = &HD&
Public Const WM_SETTEXT = &HC&
Public Const WM_TIMER = &H113&
Public Const BM_CLICK = &HF5&
Public Const SMTO_NORMAL = &H0&

Public Declare Function CreateWindowExW Lib "user32" ( _
  ByVal dwExStyle As Long, _
  ByVal lpClassName As Long, _
  ByVal lpWindowName As Long, _
  ByVal dwStyle As Long, _
  ByVal x As Long, _
  ByVal y As Long, _
  ByVal nWidth As Long, _
  ByVal nHeight As Long, _
  ByVal hWndParent As Long, _
  ByVal hMenu As Long, _
  ByVal hInstance As Long, _
  ByVal lpParam As Long) As Long
  
Public Declare Function SendMessageTimeoutW Lib "user32.dll" ( _
  ByVal hWnd As Long, _
  ByVal Msg As Long, _
  ByVal wParam As Long, _
  ByVal lParam As Long, _
  ByVal fuFlags As Long, _
  ByVal uTimeout As Long, _
  ByRef lpdwResult As Long) As Long
  
Public Declare Function GetDlgItem Lib "user32" ( _
  ByVal hDlg As Long, _
  ByVal nIDDlgItem As Long) As Long
  
Public Declare Function SetTimer Lib "user32" ( _
  ByVal hWnd As Long, _
  ByVal nIDEvent As Long, _
  ByVal uElapse As Long, _
  ByVal lpTimerFunc As Long) As Long
  
Public Declare Function KillTimer Lib "user32" ( _
  ByVal hWnd As Long, _
  ByVal nIDEvent As Long) As Long
  
Public Declare Function FindWindowW Lib "user32" ( _
  ByVal lpClassName As Long, _
  ByVal lpWindowName As Long) As Long
  
Public Declare Function GetModuleHandleW Lib "kernel32" ( _
  ByVal lpModuleName As Long) As Long
  
Public Declare Function IsWindowVisible Lib "user32" ( _
  ByVal hWnd As Long) As Long
  
Public Declare Function SetForegroundWindow Lib "user32" ( _
  ByVal hWnd As Long) As Long
  
Public Declare Function DestroyWindow Lib "user32" ( _
  ByVal hWnd As Long) As Long
  
Public Declare Sub Sleep Lib "kernel32" ( _
  ByVal dwMilliseconds As Long)

Public Sub TimerProc( _
  ByVal hWnd As Long, _
  ByVal uMsg As Long, _
  ByVal idEvent As Dialog, _
  ByVal dwTime As Long)
  
  ' Forwards the call to the Dialog class safely
  ' to RaiseEvents.
  
  If uMsg = WM_TIMER Then
    ' Dialog:::DialogCallback
    idEvent.DialogCallback uMsg
  End If
  
End Sub

Public Function CreateWindow( _
  ByVal szWinName As String) As Long
  ' Creates a hidden STATIC window
  ' Returns the window handle to the created window.
   CreateWindow = CreateWindowExW(0, StrPtr("STATIC"), StrPtr(szWinName), _
                                  0, 0, 0, 0, 0, 0, 0, GetModuleHandleW(0), 0)
End Function

Public Function SendDlgItemMessageTimeout( _
  ByVal hWnd As Long, _
  ByVal Msg As Long, _
  ByVal wParam As Long, _
  ByVal lParam As Long) As Long
  
  ' Custom implementation of SendDlgItemMessage that doesn't
  ' block the current thread. This is useful for example when
  ' dialog opens a modal dialog.
  
  Dim Success     As Long
  Dim lpResult    As Long
  
  Success = SendMessageTimeoutW( _
      hWnd, _
      Msg, _
      wParam, _
      lParam, _
      SMTO_NORMAL, _
      1000, _
      lpResult)
  
  SendDlgItemMessageTimeout = lpResult
  
End Function

Public Sub ClickMethod( _
  ByVal hDlg As Long, _
  ByVal dwId As Long)
  
  ' Click button on dialog.
  Dim hWndId            As Long
  
  hWndId = GetDlgItem(hDlg, dwId)
  Sleep 100
  Call SendDlgItemMessageTimeout(hWndId, BM_CLICK, 0, 0)
  
End Sub

Public Function GetMainWindowText( _
  ByVal hDlg As Long) As String
  
  ' Returns the main window text associated with the dialog.
  Dim Buffer(260 * 2)   As Byte
  Dim cbSize            As Long
  
  cbSize = SendDlgItemMessageTimeout(hDlg, WM_GETTEXT, MAX_PATH, VarPtr(Buffer(0)))

  If cbSize <> 0 Then
    GetMainWindowText = Left$(Buffer, cbSize)
  Else
    GetMainWindowText = vbNullString
  End If
  Erase Buffer
  
End Function

Public Function GetDialogControlText( _
  ByVal hDlg As Long, _
  ByVal dwId As Long) As String
  
  ' Returns text associated with the control.
  Dim Buffer(260 * 2)   As Byte
  Dim cbSize            As Long
  Dim hWndId            As Long
  
  hWndId = GetDlgItem(hDlg, dwId)
  cbSize = SendDlgItemMessageTimeout(hWndId, WM_GETTEXT, MAX_PATH, VarPtr(Buffer(0)))
  
  If cbSize <> 0 Then
    GetDialogControlText = Left$(Buffer, cbSize)
  Else
    GetDialogControlText = vbNullString
  End If
  Erase Buffer
  
End Function

Public Function SetDialogControlText( _
  ByVal hDlg As Long, _
  ByVal dwId As Long, _
  ByVal data As String) As Long
    ' Sets text into the associated control.
    
  Dim hWndId            As Long
  hWndId = GetDlgItem(hDlg, dwId)
  SetDialogControlText = SendDlgItemMessageTimeout(hWndId, WM_SETTEXT, 0, StrPtr(data))
 
End Function








'//
'// Dialog.cls
'//

Option Explicit

' Dialog implements the IDialog interface.
' egl1044
Implements IDialog

' Event
Public Event OnDialog()

' Variables
Private hDlg          As Long
Private hDlgPrev      As Long

Private TimerhWnd     As Long
Private TimerId       As Long

Private m_DialogText  As String
Private m_IsRunning   As Boolean
Private m_Interval    As Long

'
' Dialog Class
'


Public Sub Start()
  ' Starts the process of polling for dialogs.
  ' Already running use Cancel()
  If TimerhWnd = 0 Then
    Debug.Print "no timer window"
    Exit Sub '_leave
  End If
  If TimerId <> 0 Then
    Debug.Print "Already Running"
    Exit Sub '_leave
  End If
  TimerId = SetTimer(TimerhWnd, ObjPtr(Me), m_Interval, AddressOf TimerProc)
  If TimerId <> 0 Then
    m_IsRunning = True
  End If
End Sub

Public Sub Cancel()
  ' Cancels the process of polling for dialogs.
  If KillTimer(TimerhWnd, TimerId) <> 0 Then
    TimerId = 0
    m_IsRunning = False
  Else
    m_IsRunning = True
  End If
End Sub

 Friend Sub DialogCallback(ByVal uMsg As Long)  'Callback

  ' If the class isn't compiled the method will be exposed to the
  ' caller. This ensures the call will never be directly invoked by
  ' the calee.
  
  If uMsg = WM_TIMER Then
    hDlg = FindWindowW(StrPtr(DialogClass), 0)
    ' Make sure the dialog is visible.
    If IsWindowVisible(hDlg) Then
      If hDlg <> 0 Then
        'If hDlg <> hDlgPrev Then           ' <--- Enable this to check only once.
          hDlgPrev = hDlg
          ' Allocate property information.
          m_DialogText = GetMainWindowText(hDlg)
          ' push event
          RaiseEvent OnDialog
        'End If
      End If
    End If
  End If
  
End Sub

Public Property Get IsRunning() As Boolean
  ' Determines if currently polling.
  IsRunning = m_IsRunning
End Property

' The rate at which to check for new/existing dialogs.
Public Property Let Interval(ByVal ms As Long)
  m_Interval = ms 'milliseconds
End Property
Public Property Get Interval() As Long
  Interval = m_Interval
End Property

Private Sub Class_Initialize()
  ' Initalize to zero.
  TimerId = 0
  TimerhWnd = 0
  ' Set default interval to use.
  m_Interval = 1000
  ' Create a hidden window for the timer object.
  TimerhWnd = CreateWindow("WinDialogCallback")
End Sub

Private Sub Class_Terminate()
  ' If the caller never initated the Cancel()
  ' method then perform the cleanup.
  If TimerId <> 0 Then
     Call Cancel
  End If
  ' Destroy timer window.
  DestroyWindow TimerhWnd
End Sub



'
' IDialog Interface
'


Private Sub IDialog_Click(ByVal dwId As DialogFlags)
  Call ClickMethod(hDlg, dwId)
End Sub

Private Function IDialog_GetControlText(ByVal dwId As DialogFlags) As String
  IDialog_GetControlText = GetDialogControlText(hDlg, dwId)
End Function

Private Property Get IDialog_MainWindowText() As String
  IDialog_MainWindowText = m_DialogText
End Property

Private Function IDialog_SetControlText(ByVal dwId As DialogFlags, ByVal data As String) As Long
  IDialog_SetControlText = SetDialogControlText(hDlg, dwId, data)
End Function

Private Property Get IDialog_MainWindowHandle() As Long
  IDialog_MainWindowHandle = hDlg
End Property





'//
'// IDialog.cls
'//


Option Explicit

' IDialog Interface
' egl1044
Public Sub Click(ByVal dwId As DialogFlags)
End Sub
Public Function SetControlText(ByVal dwId As DialogFlags, ByVal data As String) As Long
End Function
Public Function GetControlText(ByVal dwId As DialogFlags) As String
End Function
Public Property Get MainWindowText() As String
End Property
Public Property Get MainWindowHandle() As Long
End Property




'//
'// Usage (WindowsForm)
'//


Option Explicit

Dim WithEvents CDlg     As Dialog
  
Private Sub CDlg_OnDialog()
  
  ' The event which will be raised when a dialog
  ' instance has been found.
  
  ' Use the IDialog interface to interact with
  ' the dialog instance.
  Dim Dlg               As IDialog
  Set Dlg = CDlg
  
  Select Case Dlg.MainWindowText
  
    Case "File Download"
      Call Dlg.Click(FileDownloadDialog_Button_Save)
      
    Case "Save As"
      Call Dlg.SetControlText(DlgFileName, "c:\egl")
      Call Dlg.Click(DlgButton_Save)
      Call Dlg.Click(DlgButton_Yes)  'overwrite existing.
      
    Case "Download complete"
      Call Dlg.Click(DownloadCompleteDialog_Button_Open)
  End Select
  
End Sub

Private Sub Form_Load()

  Set CDlg = New Dialog
  
  CDlg.Interval = 2000
  CDlg.Start
  
  Debug.Print CDlg.IsRunning
  
End Sub

Private Sub Form_QueryUnload(Cancel As Integer, UnloadMode As Integer)
  
  CDlg.Cancel

End Sub

Open in new window

0
 

Author Comment

by:giles_baxter
ID: 30370505
Thanks egl1044 - this looks promising, but so far it's not doing what I expect.

To test your code, I made a single form with a button that opens an instance of IE and navigates to a page.

When I right-click to open a Save As dialog, no buttons are clicked.  I added some debugging statements, and it looks like DialogCallback is executing and setting m_DialogText correctly, but we never get to CDlg_OnDialog.

Any idea why my form isn't picking up the OnDialog event?

'=================================

Option Explicit

Dim WithEvents CDlg     As Dialog
Dim ieObj As InternetExplorer
 
Private Sub CDlg_OnDialog()
 
  ' The event which will be raised when a dialog
  ' instance has been found.
 
  ' Use the IDialog interface to interact with
  ' the dialog instance.
  Dim Dlg               As IDialog
  Set Dlg = CDlg
 
  Debug.Print Dlg.MainWindowText & " from CDlg_OnDialog()\n"
  Select Case Dlg.MainWindowText
 
    Case "File Download"
      Call Dlg.Click(FileDownloadDialog_Button_Save)
     
    Case "Save As"
      Call Dlg.SetControlText(DlgFileName, "c:\egl")
      Call Dlg.Click(DlgButton_Save)
      Call Dlg.Click(DlgButton_Yes)  'overwrite existing.
     
    Case "Download complete"
      Call Dlg.Click(DownloadCompleteDialog_Button_Open)
   
    Case "Save Picture"
        Call Dlg.SetControlText(DlgFileName, "P:\Giles\dlg_auto_pict_test.txt")
      Call Dlg.Click(DlgButton_Save)
      Call Dlg.Click(DlgButton_Yes)
  End Select
 
End Sub



Private Sub CommandButton1_Click()
    Set ieObj = New InternetExplorer
    ieObj.Visible = True
    ieObj.Navigate "www.experts-exchange.com"
End Sub

Private Sub UserForm_Activate()
Set CDlg = New Dialog
 
  CDlg.Interval = 2000
  CDlg.Start
 
  Debug.Print CDlg.IsRunning
End Sub

Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
    CDlg.Cancel
End Sub

0
 
LVL 29

Expert Comment

by:nffvrxqgrcfqvvc
ID: 30371708
Have you tried using the UseFrom_Initialize() instead of UserForm_Activate? I know VBA doesn't have _Load() however I think Initialize() would be better to set your references. It's possible every time the userform activates a new instance is created....
0
 

Author Comment

by:giles_baxter
ID: 30482838
I tried UserForm_Initialize(), and it's definitely only being called once - Debug.Print CDlg.IsRunning only runs at the start.

Both of the debugging statements execute as expected when a WM_TIMER message is received

          Debug.Print "getting window text"
          ' Allocate property information.
          m_DialogText = GetMainWindowText(hDlg)
          Debug.Print m_DialogText & " from DialogCallback"
          ' push event
          RaiseEvent OnDialog

But still nothing from CDlg_OnDialog.
0
 
LVL 29

Expert Comment

by:nffvrxqgrcfqvvc
ID: 30486401
I don't see a reason for failure at this point.... The only way to know what the problem is if another expert would be willing to try the code to see if it works for them. I don't see a reason why you wouldn't get the event unless VBA is has some funny business happening behind the scenes... Working as intended in all my test cases. :(
0
 

Author Closing Comment

by:giles_baxter
ID: 31711130
Probably seems like a ridiculous mistake to make, but I had put the Dialog class implementations of the IDialog methods in the IDialog.cls file...  :-s

It's working great now.  Thanks!
0
 
LVL 29

Expert Comment

by:nffvrxqgrcfqvvc
ID: 30845833
Np, Good to see you have made it work :)
0

Featured Post

Never miss a deadline with monday.com

The revolutionary project management tool is here!   Plan visually with a single glance and make sure your projects get done.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

There are many ways to remove duplicate entries in an SQL or Access database. Most make you temporarily insert an ID field, make a temp table and copy data back and forth, and/or are slow. Here is an easy way in VB6 using ADO to remove duplicate row…
When designing a form there are several BorderStyles to choose from, all of which can be classified as either 'Fixed' or 'Sizable' and I'd guess that 'Fixed Single' or one of the other fixed types is the most popular choice. I assume it's the most p…
Get people started with the process of using Access VBA to control Outlook using automation, Microsoft Access can control other applications. An example is the ability to programmatically talk to Microsoft Outlook. Using automation, an Access applic…
Get people started with the process of using Access VBA to control Excel using automation, Microsoft Access can control other applications. An example is the ability to programmatically talk to Excel. Using automation, an Access application can laun…
Suggested Courses

601 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question