guides:machine_code:com

Using COM with MCode

Please read the basic MCode guide before this one!

If you have not, consider referencing the COM APIs guide to learn more about how COM works.

When building integrations for AutoHotkey using MCode, it is often convenient to be able to work with COM objects. Either consuming COM objects in your MCode, or exporting COM objects from your MCode so they can be consumed by AHK.

The most basic COM object you can export from your MCode is an IUnknown object. To export an IUnknown object, you must implement the IUnknown interface's three methods: QueryInterface, AddRef, and Release.

IUnknown can't actually do anything worth demonstrating, so on top of those three methods we'll implement an additional three: SetFirstAddend, SetSecondAddend, and GetSum. Together, these will form our own custom interface, which we'll call IMyObj.

By implementing the IDispatch interface, your custom COM object can be wrapped by AHK. Once wrapped, users can interact with the object using native object syntax.

IDispatch extends IUnknown, so it has the three IUnknown methods. After those, it includes these four methods:

  • GetTypeInfoCount - Allows debug-style inspection of the COM object. (MSDN)
  • GetTypeInfo - Allows debug-style inspection of the COM object. (MSDN)
  • GetIDsOfNames - Translates script-given method and property names to unique DISPID values. (MSDN)
  • Invoke - Dispatches MyObj's method and property implementations. (MSDN)

The methods GetTypeInfo and GetTypeInfoCount are optional. We will leave as unimplemented stubs.

The GetIDsOfNames method accesses the object's internal name store, and returns an object-specific "DISPID" number. For objects which have a fixed set of properties, this can be implemented by hard coding some constants into the MCode. As an example, if you were creating an object which just had a single method called Compute, you might make GetIDsOfNames return 1 when given the name "Compute". When the script makes a call to your object like object.Compute(), the string "Compute" is passed to this GetIDsOfNames function. AutoHotkey will take the result (1) and cache it for the lifetime of the object.

If your object allows for new properties to be created dynamically, GetIDsOfNames might implement something like a counter and hash map. Whenever a new property is created, the counter would be incremented and then its value will be saved inside the hash map so that the mapping can be resolved again later.

The Invoke method is where all the magic happens. This function takes the DISPID specified by GetIDsOfNames and runs the custom code associated with the DISPID/name. Invoke receives a set of flags, which can be one or more of the following:

  • DISPATCH_METHOD - The name was invoked as a method, as in object.Name(…).
  • DISPATCH_PROPERTYGET - The name was invoked as a property retrieval, as in MsgBox object.Name.
  • DISPATCH_PROPERTYPUT - The name was invoked as a property put, as in object.Name := "New Name".
  • DISPATCH_PROPERTYPUTREF - The name was invoked as a property put "by reference". This is uncommon.

Invoke also receives a DISPPARAMS argument, which contains an array of VARIANTs passed as arguments, and an array of VARIANTs passed as keyword arguments (not applicable when calling from AHK). Invoke has three output parameters,

  • VARIANT *pVarResult - The result of the call, if any
  • EXCEPINFO *pExcepInfo - Any exception to return to the caller
  • UINT *puArgErr - If an argument was bad, which argument was it?

By implementing Invoke to check the DISPIDs against the constants you created for each field that you want to be available on your object, and checking the flags against the appropriate flags for those fields, you can create virtual methods that AutoHotkey can call with regular object syntax.

AutoHotkey objects are, at their basic, IDispatch objects. Just like we can create IDispatch objects using basic C code, we can interact with IDispatch objects using basic C code.

By invoking DISPID_VALUE with DISPATCH_PROPERTYGET and DISPATCH_PROPERTYPUT, we can get and set values from an object's item store. This allows us to implement a basic Quicksort algorithm that applies to a provided array: