Show pageOld revisionsBacklinksFold/unfold allBack to top This page is read only. You can view the source, but not change it. Ask your administrator if you think this is wrong. ====== Using COM with MCode ====== Please read the basic [[guides:machine_code|MCode]] guide before this one! If you have not, consider referencing the [[guides:com:start|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''. <runner ahk2> #Requires AutoHotkey v2.0 #include <MCL> lib := MCL.FromC(" ( #include <mcl.h> #include <stdlib.h> #include <oaidl.h> // --- Interface Identifiers --------------------------------------------------- // IID_IUnknown {00000000-0000-0000-C000-000000000046} GUID IID_IUnknown = { 0x00000000, 0x0000, 0x0000, { 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46 } }; // IID_IMyObj (generated at random) GUID IID_IMyObj = { 0x2D055F08, 0xC092, 0x44D4, { 0x82, 0xA9, 0xF7, 0x57, 0x75, 0x1D, 0x0D, 0x80 } }; // --- MyObj Structural Definition --------------------------------------------- // Forward declare the MyObj type so it can be referenced in MyObjVtbl typedef struct MyObj MyObj; // Define the virtual method table for objects of type MyObj typedef struct MyObjVtbl { // Implements IUnknown __stdcall HRESULT(*QueryInterface) (MyObj *this, REFIID riid, void **ppvObject); __stdcall ULONG(*AddRef) (MyObj *this); __stdcall ULONG(*Release) (MyObj *this); // Implements IMyObj (I'm inventing this interface here) __stdcall HRESULT(*SetFirstAddend) (MyObj *this, LONG addend); __stdcall HRESULT(*SetSecondAddend) (MyObj *this, LONG addend); __stdcall HRESULT(*GetSum) (MyObj *this, LONG *result); } MyObjVtbl; // Define the basic structure of MyObj instances so it can be referenced in // method implementations struct MyObj { const MyObjVtbl* lpVtbl; int refcount; LONG firstAddend; LONG secondAddend; }; // --- MyObj Method Definitions ------------------------------------------------ // Returns the requested interface of the object when requested // https://learn.microsoft.com/en-us/windows/win32/api/unknwn/nf-unknwn-iunknown-queryinterface(refiid_void) HRESULT __stdcall MyObj_QueryInterface(MyObj *this, REFIID riid, void** ppvObject) { // If the interface requested is implemented by MyObj, return it if (memcmp(riid, &IID_IUnknown, sizeof(GUID)) == 0 || memcmp(riid, &IID_IMyObj, sizeof(GUID)) == 0) { this->lpVtbl->AddRef(this); *ppvObject = this; return S_OK; } // Here are some other interfaces that are sometimes requested by AutoHotkey // IID_IDispatch {00020400-0000-0000-C000-000000000046} // Implements Microsoft's automation interface, like most COM objects used in AutoHotkey // IID_IProvideClassInfo {B196B283-BAB4-101A-B69C-00AA00341D07} // Implements a "GetClassInfo" method for callers to look up information about the object // IID_IObjectComCompatible {619F7E25-6D89-4EB4-B2Fb-18E7C73C0EA6} // Identifies an AutoHotkey object which was passed to a COM API return E_NOINTERFACE; } // Increments the reference counter // https://learn.microsoft.com/en-us/windows/win32/api/unknwn/nf-unknwn-iunknown-addref ULONG __stdcall MyObj_AddRef(MyObj *this) { ++this->refcount; return this->refcount; } // Decrements the reference counter and frees the object when it hits 0 // https://learn.microsoft.com/en-us/windows/win32/api/unknwn/nf-unknwn-iunknown-release ULONG __stdcall MyObj_Release(MyObj* this) { --this->refcount; if (this->refcount != 0) { return this->refcount; } // Make sure to perform any code needed to free individual fields before // freeing the entire object free(this); return 0; } // Sets the first addend property HRESULT __stdcall MyObj_SetFirstAddend(MyObj *this, LONG addend) { this->firstAddend = addend; return S_OK; } // Sets the second addend property HRESULT __stdcall MyObj_SetSecondAddend(MyObj *this, LONG addend) { this->secondAddend = addend; return S_OK; } // Sets the second addend property HRESULT __stdcall MyObj_GetSum(MyObj *this, LONG *result) { *result = this->firstAddend + this->secondAddend; return S_OK; } // --- Object Factory ---------------------------------------------------------- // Create a constant instance of the virtual method table to be shared // among the instance objects. Similar to AHK's prototype object. static const MyObjVtbl MY_OBJ_VTBL = { .QueryInterface = MyObj_QueryInterface, .AddRef = MyObj_AddRef, .Release = MyObj_Release, .SetFirstAddend = MyObj_SetFirstAddend, .SetSecondAddend = MyObj_SetSecondAddend, .GetSum = MyObj_GetSum, }; // Define a factory for MyObj instances that can be called by AutoHotkey MCL_EXPORT(NewMyObj, Cdecl_Ptr); MyObj* NewMyObj() { MyObj* obj = malloc(sizeof(MyObj)); // Initialize all fields obj->lpVtbl = &MY_OBJ_VTBL; obj->refcount = 1; obj->firstAddend = 0; obj->secondAddend = 0; return obj; } )") ; Retrieve an IMyObj COM Object MyObj := lib.NewMyObj() ComCall(3, MyObj, "Int", 1) ; Call IMyObj::SetFirstAddend (MyObjVtbl[3]) ComCall(4, MyObj, "Int", 2) ; Call IMyObj::SetSecondAddend (MyObjVtbl[4]) ComCall(5, MyObj, "Int*", &result := 0) ; Call IMyObj::GetSum (MyObjVtbl[5]) MsgBox "1 + 2 = " result </runner> ===== 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. ([[https://learn.microsoft.com/en-us/windows/win32/api/oaidl/nf-oaidl-idispatch-gettypeinfocount|MSDN]]) * ''GetTypeInfo'' - Allows debug-style inspection of the COM object. ([[https://learn.microsoft.com/en-us/windows/win32/api/oaidl/nf-oaidl-idispatch-gettypeinfo|MSDN]]) * ''GetIDsOfNames'' - Translates script-given method and property names to unique DISPID values. ([[https://learn.microsoft.com/en-us/windows/win32/api/oaidl/nf-oaidl-idispatch-getidsofnames|MSDN]]) * ''Invoke'' - Dispatches MyObj's method and property implementations. ([[https://learn.microsoft.com/en-us/windows/win32/api/oaidl/nf-oaidl-idispatch-invoke|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. <runner ahk2> #Requires AutoHotkey v2.0 #include <MCL> lib := MCL.FromC(" ( #include <mcl.h> #include <stdlib.h> #include <oaidl.h> #define DISP_E_TYPEMISMATCH 0x80020005 MCL_IMPORT(int, msvcrt, _wcsicmp, (const short*, const short*)); MCL_IMPORT(HRESULT, OleAut32, VariantChangeType, (VARIANT*, const VARIANT*, USHORT, VARTYPE)); // --- Interface Identifiers --------------------------------------------------- // IID_IUnknown {00000000-0000-0000-C000-000000000046} GUID IID_IUnknown = { 0x00000000, 0x0000, 0x0000, { 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46 } }; // IID_IDispatch {00020400-0000-0000-C000-000000000046} GUID IID_IDispatch = { 0x00020400, 0x0000, 0x0000, { 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46 } }; // --- MyObj Structural Definition --------------------------------------------- // Forward declare the MyObj type so it can be referenced in MyObjVtbl typedef struct MyObj MyObj; // Define the virtual method table for objects of type MyObj typedef struct MyObjVtbl { // Implements IUnknown __stdcall HRESULT(*QueryInterface) (MyObj *this, REFIID riid, void **ppvObject); __stdcall ULONG(*AddRef) (MyObj *this); __stdcall ULONG(*Release) (MyObj *this); // Implements IDispatch __stdcall HRESULT(*GetTypeInfoCount) (MyObj *this, UINT *pctinfo); __stdcall HRESULT(*GetTypeInfo) (MyObj *this, UINT iTInfo, LCID lcid, void **ppTInfo); __stdcall HRESULT(*GetIDsOfNames) ( MyObj *This, REFIID riid, LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId); __stdcall HRESULT(*Invoke) ( MyObj *This, DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pDispParams, VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr); } MyObjVtbl; // Define virtual method DISPIDs #define MY_OBJ_DISPID_FIRST_ADDEND 1 #define MY_OBJ_DISPID_SECOND_ADDEND 2 #define MY_OBJ_DISPID_GET_SUM 3 // Define the basic structure of MyObj instances so it can be referenced in // method implementations struct MyObj { const MyObjVtbl* lpVtbl; int refcount; LONG firstAddend; LONG secondAddend; }; // --- MyObj Method Definitions ------------------------------------------------ // Returns the requested interface of the object when requested // https://learn.microsoft.com/en-us/windows/win32/api/unknwn/nf-unknwn-iunknown-queryinterface(refiid_void) HRESULT __stdcall MyObj_QueryInterface(MyObj *this, REFIID riid, void** ppvObject) { // If the interface requested is implemented by MyObj, return it if (memcmp(riid, &IID_IUnknown, sizeof(GUID)) == 0 || memcmp(riid, &IID_IDispatch, sizeof(GUID)) == 0) { this->lpVtbl->AddRef(this); *ppvObject = this; return S_OK; } // Here are some of the interfaces that are sometimes requested by AutoHotkey // IID_IDispatch {00020400-0000-0000-C000-000000000046} // Implements Microsoft's automation interface, like most COM objects used in AutoHotkey // IID_IProvideClassInfo {B196B283-BAB4-101A-B69C-00AA00341D07} // Implements a "GetClassInfo" method for callers to look up information about the object // IID_IObjectComCompatible {619F7E25-6D89-4EB4-B2Fb-18E7C73C0EA6} // Identifies an AutoHotkey object which was passed to a COM API return E_NOINTERFACE; } // Increments the reference counter // https://learn.microsoft.com/en-us/windows/win32/api/unknwn/nf-unknwn-iunknown-addref ULONG __stdcall MyObj_AddRef(MyObj *this) { ++this->refcount; return this->refcount; } // Decrements the reference counter and frees the object when it hits 0 // https://learn.microsoft.com/en-us/windows/win32/api/unknwn/nf-unknwn-iunknown-release ULONG __stdcall MyObj_Release(MyObj* this) { --this->refcount; if (this->refcount != 0) { return this->refcount; } // Make sure to perform any code needed to free individual fields before // freeing the entire object free(this); return 0; } // Allows debug-style inspection of the COM object // https://learn.microsoft.com/en-us/windows/win32/api/oaidl/nf-oaidl-idispatch-gettypeinfocount HRESULT __stdcall MyObj_GetTypeInfoCount(MyObj *this, UINT* pctinfo) { return E_NOTIMPL; } // Allows debug-style inspection of the COM object // https://learn.microsoft.com/en-us/windows/win32/api/oaidl/nf-oaidl-idispatch-gettypeinfo HRESULT __stdcall MyObj_GetTypeInfo(MyObj *this, UINT iTInfo, LCID lcid, void **ppTInfo) { return E_NOTIMPL; } // Translates script-given method and property names to unique DISPID values. // https://learn.microsoft.com/en-us/windows/win32/api/oaidl/nf-oaidl-idispatch-getidsofnames HRESULT __stdcall MyObj_GetIDsOfNames( MyObj *this, /* [in] */ REFIID riid, /* [in] */ LPOLESTR *rgszNames, /* [in] */ UINT cNames, /* [in] */ LCID lcid, /* [out] */ DISPID *rgDispId) { HRESULT retval = S_OK; for (int i = 0; i < cNames; i++) { if (0 == _wcsicmp(rgszNames[i], L"FirstAddend")) { rgDispId[i] = MY_OBJ_DISPID_FIRST_ADDEND; } else if (0 == _wcsicmp(rgszNames[i], L"SecondAddend")) { rgDispId[i] = MY_OBJ_DISPID_SECOND_ADDEND; } else if (0 == _wcsicmp(rgszNames[i], L"GetSum")) { rgDispId[i] = MY_OBJ_DISPID_GET_SUM; } else { retval = DISP_E_UNKNOWNNAME; rgDispId[i] = DISPID_UNKNOWN; } } return retval; } // Dispatches MyObj's method and property implementations // https://learn.microsoft.com/en-us/windows/win32/api/oaidl/nf-oaidl-idispatch-invoke HRESULT __stdcall MyObj_Invoke( MyObj *this, /* [in] */ DISPID dispIdMember, /* [in] */ REFIID riid, /* [in] */ LCID lcid, /* [in] */ WORD wFlags, /* [out][in] */ DISPPARAMS *pDispParams, /* [out][opt] */ VARIANT *pVarResult, /* [out][opt] */ EXCEPINFO *pExcepInfo, /* [out][opt] */ UINT *puArgErr) { // Set First Addend if (dispIdMember == MY_OBJ_DISPID_FIRST_ADDEND && (wFlags & DISPATCH_PROPERTYPUT)) { // Require a single parameter if (pDispParams->cArgs != 1) { return DISP_E_BADPARAMCOUNT; } // Coerce to 4-byte integer (LONG) if (S_OK != VariantChangeType( &pDispParams->rgvarg[0], &pDispParams->rgvarg[0], 0, VT_I4)) { return DISP_E_TYPEMISMATCH; } this->firstAddend = pDispParams->rgvarg[0].lVal; pVarResult->vt = VT_I4; pVarResult->lVal = this->firstAddend; return S_OK; } // Get First Addend if (dispIdMember == MY_OBJ_DISPID_FIRST_ADDEND && (wFlags & DISPATCH_PROPERTYGET)) { pVarResult->vt = VT_I4; pVarResult->lVal = this->firstAddend; return S_OK; } // Set Second Addend if (dispIdMember == MY_OBJ_DISPID_SECOND_ADDEND && (wFlags & DISPATCH_PROPERTYPUT)) { // Require a single parameter if (pDispParams->cArgs != 1) { return DISP_E_BADPARAMCOUNT; } // Coerce to 4-byte integer (LONG) if (S_OK != VariantChangeType( &pDispParams->rgvarg[0], &pDispParams->rgvarg[0], 0, VT_I4)) { return DISP_E_TYPEMISMATCH; } this->secondAddend = pDispParams->rgvarg[0].lVal; pVarResult->vt = VT_I4; pVarResult->lVal = this->secondAddend; return S_OK; } // Get Second Addend if (dispIdMember == MY_OBJ_DISPID_SECOND_ADDEND && (wFlags & DISPATCH_PROPERTYGET)) { pVarResult->vt = VT_I4; pVarResult->lVal = this->secondAddend; return S_OK; } // Call GetSum if (dispIdMember == MY_OBJ_DISPID_GET_SUM && (wFlags & DISPATCH_METHOD)) { // Require no parameters if (pDispParams->cArgs != 0) { return DISP_E_BADPARAMCOUNT; } pVarResult->vt = VT_I4; pVarResult->lVal = this->firstAddend + this->secondAddend; return S_OK; } return DISP_E_MEMBERNOTFOUND; } // --- Object Factory ---------------------------------------------------------- // Create a constant instance of the virtual method table to be shared // among the instance objects. Similar to AHK's prototype object. static const MyObjVtbl MY_OBJ_VTBL = { .QueryInterface = MyObj_QueryInterface, .AddRef = MyObj_AddRef, .Release = MyObj_Release, .GetTypeInfoCount = MyObj_GetTypeInfoCount, .GetTypeInfo = MyObj_GetTypeInfo, .GetIDsOfNames = MyObj_GetIDsOfNames, .Invoke = MyObj_Invoke }; // Define a factory for MyObj instances that can be called by AutoHotkey MCL_EXPORT(NewMyObj, Cdecl_Ptr); MyObj* NewMyObj() { MyObj* obj = malloc(sizeof(MyObj)); // Initialize all fields obj->lpVtbl = &MY_OBJ_VTBL; obj->refcount = 1; obj->firstAddend = 0; obj->secondAddend = 0; return obj; } )") ; Retrieve an IDispatch COM Object MyObj := ComObjFromPtr(lib.NewMyObj()) MyObj.FirstAddend := 1 MyObj.SecondAddend := 2 MsgBox MyObj.FirstAddend " + " MyObj.SecondAddend " = " MyObj.GetSum() </runner> ===== 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. <runner ahk2> #Requires AutoHotkey v2.0 #include <MCL> lib := MCL.FromC(" ( #include <mcl.h> #include <stdlib.h> #include <oaidl.h> MCL_IMPORT(HRESULT, OleAut32, VariantChangeType, (VARIANT*, const VARIANT*, USHORT, VARTYPE)); static IID IID_NULL = { 0x00000000, 0x0000, 0x0000, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }; MCL_EXPORT(SumValues, Ptr, Cdecl_Int); int SumValues(IDispatch* pObj) { // Get the ID of the "Length" property DISPID dispidLength = DISPID_UNKNOWN; LPOLESTR strLength = L"Length"; pObj->lpVtbl->GetIDsOfNames(pObj, &IID_NULL, &strLength, 1, 0, &dispidLength); if (dispidLength == DISPID_UNKNOWN) { return 0; } // Invoke the "Length" property DISPPARAMS dpLength = { .cArgs = 0, .cNamedArgs = 0 }; VARIANT vLength = { .vt = VT_EMPTY }; if (S_OK != pObj->lpVtbl->Invoke( pObj, dispidLength, NULL, 0, DISPATCH_PROPERTYGET, &dpLength, &vLength, NULL, NULL) || vLength.vt != VT_I4) { return 0; } // Loop from i = 0 to Length int out = 0; for (int i = 0; i < vLength.lVal; i++) { // Invoke the value property, with argument `i+1`. This is equivalent // to `object[i+1]` VARIANT vargGet = { .vt = VT_I4, .lVal = i + 1 }; DISPPARAMS dpGet = { .cArgs = 1, .cNamedArgs = 0, .rgvarg = &vargGet }; VARIANT vGet = { .vt = VT_EMPTY }; if (S_OK != pObj->lpVtbl->Invoke( pObj, DISPID_VALUE, NULL, 0, DISPATCH_PROPERTYGET, &dpGet, &vGet, NULL, NULL)) { return 0; } // Coerce to 4-byte integer (LONG) if (S_OK != VariantChangeType(&vGet, &vGet, 0, VT_I4)) { return 0; } out += vGet.lVal; } return out; } )") input := [1, 2, 3] MsgBox lib.SumValues(ObjPtr(input)) </runner> ==== 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: <runner ahk2> #Requires AutoHotkey v2.0 #include <MCL> lib := MCL.FromC(" ( #include <mcl.h> #include <stdlib.h> #include <oaidl.h> #define E_FAIL 0x80004005 MCL_IMPORT(HRESULT, OleAut32, VarCmp, (VARIANT* left, VARIANT* right, DWORD lcid, ULONG dwFlags)); MCL_IMPORT(HRESULT, OleAut32, VariantClear, (VARIANT* pvarg)); HRESULT GetItem(IDispatch* array, ULONG index, VARIANT* pvarResult) { VARIANT vargGet = { .vt = VT_I4, .lVal = index }; DISPPARAMS dpGet = { .cArgs = 1, .cNamedArgs = 0, .rgvarg = &vargGet }; return array->lpVtbl->Invoke( array, DISPID_VALUE, NULL, 0, DISPATCH_PROPERTYGET, &dpGet, pvarResult, NULL, NULL); } HRESULT PutItem(IDispatch* array, ULONG index, VARIANT* pvarItem) { VARIANT vargIndex = { .vt = VT_I4, .lVal = index }; VARIANT rgvargArgs[] = { *pvarItem, vargIndex }; DISPPARAMS dpPut = { .cArgs = 2, .cNamedArgs = 0, .rgvarg = rgvargArgs }; return array->lpVtbl->Invoke( array, DISPID_VALUE, NULL, 0, DISPATCH_PROPERTYPUT, &dpPut, NULL, NULL, NULL); } HRESULT partition(IDispatch *array, ULONG lo, ULONG hi, ULONG* newPartition) { HRESULT result = E_FAIL; VARIANT varHi = { .vt = VT_EMPTY }; VARIANT varI = { .vt = VT_EMPTY }; VARIANT varJ = { .vt = VT_EMPTY }; // Use highest index value as pivot if (S_OK != GetItem(array, hi, &varHi)) goto fail; ULONG i = lo; for (ULONG j = lo; j < hi; j++) { if (S_OK != GetItem(array, j, &varJ)) goto fail; // lcid = LOCALE_INVARIANT, dwFlags = NORM_IGNORECASE if ( (varJ.vt != varHi.vt) ? (varJ.vt <= varHi.vt) : VarCmp(&varJ, &varHi, 0x7F, 0x1) < 2) { if (S_OK != GetItem(array, i, &varI)) goto fail; if (S_OK != PutItem(array, i, &varJ)) goto fail; if (S_OK != PutItem(array, j, &varI)) goto fail; i = i + 1; VariantClear(&varI); } VariantClear(&varJ); } // Center the pivot value if (S_OK != GetItem(array, i, &varI)) goto fail; if (S_OK != PutItem(array, i, &varHi)) goto fail; if (S_OK != PutItem(array, hi, &varI)) goto fail; *newPartition = i; result = S_OK; fail: VariantClear(&varHi); VariantClear(&varI); VariantClear(&varJ); return result; } MCL_EXPORT(quicksort, Ptr, array, UInt, lo, UInt, hi, CDecl_Int); HRESULT quicksort(IDispatch *array, ULONG lo, ULONG hi) { if (lo >= hi) return S_OK; ULONG p = 0; if (S_OK != partition(array, lo, hi, &p)) return E_FAIL; if (S_OK != quicksort(array, lo, p - 1)) return E_FAIL; if (S_OK != quicksort(array, p + 1, hi)) return E_FAIL; return S_OK; } )") x := ["c", 6, 3.0, 3, 8, "b", 9, 2.0, 4, 5, "a", 1,2, 1.0, 7] MsgBox "Before Sorting: " JSON.Dump(x) lib.quicksort(ObjPtr(x), 1, x.Length) MsgBox "After Sorting: " JSON.Dump(x) </runner>