Search code examples
comracket

Racket COM: cannot get IDispatch interface for object - what's next?


I'm trying to use MS UI Automation from Racket 6.x (UIAutomationCore.idl under MS SDK for UIAutomationcore.dll under system) and the safe racket call fails with cannot get IDispatch error. The code is below:

#lang racket
(require ffi/com)

(define clsid-cuia (string->clsid "{ff48dba4-60ef-4201-aa87-54103eef594e}"))
(define cuia-instance (com-create-instance clsid-cuia))
(com-methods cuia-instance)

The C++ code that works for the same interface:

CoCreateInstance(CLSID_CUIAutomation, NULL,
                        CLSCTX_INPROC_SERVER, IID_IUIAutomation,
                        reinterpret_cast<void**>(ppAutomation));

My question is, what should I do to use this interface? I've been trying to find the answer in the racket reference but have spent a lot of time without answers. I haven't studied the Racket C FFI (except section 5.12 for COM), and wondering whether I should learn the whole FFI before trying to use anything more advanced than the code above.


Solution

  • After spending some more time on the reference (thanks to Hans' initial comment), I now know the answer. It's not an answer to how to fully use the UIA interface, but the way to proceed (as I asked in the question).

    Since UIAutomation et al derives from IUnknown, we need to define the COM interface using (define-com-interface ...).

    A way to start is:

    (define-com-interface (_IUIAutomation _IUnknown)
      ; a better approach would be to write a macro to read the idl file,
      ; and parse it, then use the result to define the methods
      ; and define each corresponding method in order below. The first two
      ; doesn't have the full description yet
      ([CompareElements _fpointer]
      [CompareRuntimeIds _fpointer]
      [GetRootElement (_hmfun (p : (_ptr o _IUIAutomationElement-pointer))
                              -> GetRootElement p)]
      ))
    

    And since _IUIAutomationElement-pointer above is not defined yet, we should define it and the other interfaces we'll use separately with (define-com-interface ...) as well.

    There are other details to take care of as converting the return values of functions to/from Racket values, so knowing the C FFI will help, that's why one should better learn the Racket C FFI before delving any further.

    Here is an update with how to use an IUnknown interface. Note that the _IUIAutomation-pointer definition comes automatically from the define-com-interface above.

    (define clsid-cuia (string->clsid "{ff48dba4-60ef-4201-aa87-54103eef594e}"))
    (define IID_IUIAutomation (string->iid "{30cbe57d-d9d0-452a-ab13-7ac5ac4825ee}"))
    
    (define cuia (com-create-instance clsid-cuia))
    (define iuia (QueryInterface (com-object-get-iunknown cuia)
                                   IID_IUIAutomation
                                   _IUIAutomation-pointer))
    (define root-element (GetRootElement iuia))