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.
Exporting IUnknown from MCode
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
.
Exporting IDispatch from MCode
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 inobject.Name(…)
.DISPATCH_PROPERTYGET
- The name was invoked as a property retrieval, as inMsgBox object.Name
.DISPATCH_PROPERTYPUT
- The name was invoked as a property put, as inobject.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 anyEXCEPINFO *pExcepInfo
- Any exception to return to the callerUINT *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.
Consuming AHK Objects from MCode
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.
Implementing QuickSort
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: