The Windows API mostly concerns itself with the interaction between the operating system and an application. For communication between the different Windows applications among themselves, Microsoft has developed a series of technologies alongside the main Windows API. This started out with Dynamic Data Exchange (DDE), which was superseded by Object Linking and Embedding (OLE) and later by the Component Object Model (COM), Automation Objects, ActiveX controls, and the .NET Framework.
COM is C++ and it cannot be written directly with js-ctypes. This documentaion explains how to convert the COM C++ code into js-ctypes code.
Basis and reference for this article Bugzilla :: Bug 738501 - Implement ability to create Windows shortcuts from javascript - Comment 4
Relavent topic Bugzilla :: Bug 505907 - Support C++ calling from JSctypes
Converting COM code to C code
To convert COM code to js-ctypes, we need to write C++ VTable pointers in C. After that, we can pass a pointer .
Speech Synthesis Example
Let's start with following C++ code, which invokes Microsoft Speech API and says "Hello, Firefox!" with system default voice, then wait until the speaking done.
#include <sapi.h> int main(void) { if (SUCCEEDED(CoInitialize(NULL))) { ISpVoice* pVoice = NULL; HRESULT hr = CoCreateInstance(CLSID_SpVoice, NULL, CLSCTX_ALL, IID_ISpVoice, (void**)&pVoice); if (SUCCEEDED(hr)) { pVoice->Speak(L"Hello, Firefox!", SPF_DEFAULT, NULL); pVoice->Release(); } } // MSDN documentation says that even if CoInitalize fails, CoUnitialize // must be called CoUninitialize(); return 0; }
To run the code, save it as test.cpp
, and run following command in the directory (needs Visual Studio).
$ cl ole32.lib test.cpp
vtable
Needs vtable description here.
The order of the vtable is very critical. When used from C or js-ctypes, the order of the vtable methods should match the order of the C++ vtable.
__stdcall and __cdecl
Needs __stdcall and __cdecl (CALLBACK_ABI) description here.
Call by reference
Needs C++ reference (&) and C pointer (*) description here.
Converted C code
Now we can translate whole code into C syntax.
#include <sapi.h> int main(void) { if (SUCCEEDED(CoInitialize(NULL))) { struct ISpVoice* pVoice = NULL; HRESULT hr = CoCreateInstance(&CLSID_SpVoice, NULL, CLSCTX_ALL, &IID_ISpVoice, (void**)&pVoice); if (SUCCEEDED(hr)) { pVoice->lpVtbl->Speak(pVoice, L"Hello, Firefox!", 0, NULL); pVoice->lpVtbl->Release(pVoice); } } // MSDN documentation says that even if CoInitalize fails, CoUnitialize // must be called CoUninitialize(); return 0; }
To run the code, save it as test.c
, and run following command in the directory.
$ cl ole32.lib test.c
C code with pseudo struct
Needs pseudo struct description here.
#include <sapi.h> struct MyISpVoiceVtbl; struct MyISpVoice { struct MyISpVoiceVtbl* lpVtbl; }; struct MyISpVoiceVtbl { /* start inherit from IUnknown */ void* QueryInterface; void* AddRef; ULONG (__stdcall *Release)(struct MyISpVoice*); /* end inherit from IUnknown */ /* start inherit from ISpNotifySource */ void* SetNotifySink; void* SetNotifyWindowMessage; void* SetNotifyCallbackFunction; void* SetNotifyCallbackInterface; void* SetNotifyWin32Event; void* WaitForNotifyEvent; void* GetNotifyEventHandle; /* end inherit from ISpNotifySource */ /* start inherit from ISpEventSource */ void* SetInterest; void* GetEvents; void* GetInfo; /* end inherit from ISpEventSource */ /* start ISpVoice */ void* SetOutput; void* GetOutputObjectToken; void* GetOutputStream; void* Pause; void* Resume; void* SetVoice; void* GetVoice; HRESULT (__stdcall *Speak)(struct MyISpVoice*, LPCWSTR pwcs, DWORD dwFlags, ULONG* pulStreamNumber); void* SpeakStream; void* GetStatus; void* Skip; void* SetPriority; void* GetPriority; void* SetAlertBoundary; void* GetAlertBoundary; void* SetRate; void* GetRate; void* SetVolume; void* GetVolume; void* WaitUntilDone; void* SetSyncSpeakTimeout; void* GetSyncSpeakTimeout; void* SpeakCompleteEvent; void* IsUISupported; void* DisplayUI; /* end ISpVoice */ }; int main(void) { if (SUCCEEDED(CoInitialize(NULL))) { struct MyISpVoice* pVoice = NULL; HRESULT hr = CoCreateInstance(&CLSID_SpVoice, NULL, CLSCTX_ALL, &IID_ISpVoice, (void**)&pVoice); if (SUCCEEDED(hr)) { pVoice->lpVtbl->Speak(pVoice, L"Hello, Firefox!", SPF_DEFAULT, NULL); pVoice->lpVtbl->Release(pVoice); } } // MSDN documentation says that even if CoInitalize fails, CoUnitialize // must be called CoUninitialize(); return 0; }
Converting C code to js-ctypes code
Now we have working C code. It could be converted into js-ctypes in almost straightforward way.
CLSID and IID
Needs CLSID and IID of SpVoice and ISpVoice description here.
COM types and functions
Needs COM types (GUID) and functions (CoInitialize etc) description here.
Converted js-ctypes code
Here's converted code, which works with copy-n-paste into Scratchpad, with Browser Environment.
let { ctypes } = Components.utils.import("resource://gre/modules/ctypes.jsm", {}); // SOME GROUNDWORK let is64bit; if (ctypes.voidptr_t.size == 4 /* 32-bit */) { is64bit = false; } else if (ctypes.voidptr_t.size == 8 /* 64-bit */) { is64bit = true; } let WINABI = is64bit ? ctypes.default_abi : ctypes.winapi_abi; let CALLBACK_ABI = is64bit ? ctypes.default_abi : ctypes.stdcall_abi; // LIBRARIES let lib = ctypes.open('ole32.dll'); // TYPES // Simple Types let BYTE = ctypes.unsigned_char; let DWORD = ctypes.unsigned_long; let LONG = ctypes.long; let LPVOID = ctypes.voidptr_t; let VOID = ctypes.void_t; let ULONG = ctypes.unsigned_long; let USHORT = ctypes.unsigned_short; let WCHAR = ctypes.jschar; // Advanced Types - based on simple types let HRESULT = LONG; let LPCWSTR = WCHAR.ptr; // Guess Types - these just work I couldnt find a proper defintion for it let LPUNKNOWN = ctypes.voidptr_t; // STRUCTURES // Simple Structures let GUID = ctypes.StructType('GUID', [ { 'Data1': ULONG }, { 'Data2': USHORT }, { 'Data3': USHORT }, { 'Data4': BYTE.array(8) } ]); // Advanced Structures let CLSID = GUID; let IID = GUID; // Super Advanced Structures let REFIID = IID.ptr; let REFCLSID = CLSID.ptr; // VTABLES let ISpVoiceVtbl = ctypes.StructType('ISpVoiceVtbl'); let ISpVoice = ctypes.StructType('ISpVoice', [{ 'lpVtbl': ISpVoiceVtbl.ptr }]); ISpVoiceVtbl.define([ // start inherit from IUnknown { 'QueryInterface': ctypes.voidptr_t }, { 'AddRef': ctypes.voidptr_t }, { 'Release': ctypes.FunctionType(CALLBACK_ABI, ULONG, // return [ ISpVoice.ptr ]).ptr }, // end inherit from IUnknown // start inherit from ISpNotifySource // can set to ctypes.voidptr_t if arent going to use it { 'SetNotifySink': ctypes.voidptr_t }, { 'SetNotifyWindowMessage': ctypes.voidptr_t }, { 'SetNotifyCallbackFunction': ctypes.voidptr_t }, { 'SetNotifyCallbackInterface': ctypes.voidptr_t }, { 'SetNotifyWin32Event': ctypes.voidptr_t }, { 'WaitForNotifyEvent': ctypes.voidptr_t }, { 'GetNotifyEventHandle': ctypes.voidptr_t }, // end inherit from ISpNotifySource // start inherit from ISpEventSource { 'SetInterest': ctypes.voidptr_t }, { 'GetEvents': ctypes.voidptr_t }, { 'GetInfo': ctypes.voidptr_t }, // end inherit from ISpEventSource // start ISpVoice { 'SetOutput': ctypes.voidptr_t }, { 'GetOutputObjectToken': ctypes.voidptr_t }, { 'GetOutputStream': ctypes.voidptr_t }, { 'Pause': ctypes.voidptr_t }, { 'Resume': ctypes.voidptr_t }, { 'SetVoice': ctypes.voidptr_t }, { 'GetVoice': ctypes.voidptr_t }, { 'Speak': ctypes.FunctionType(CALLBACK_ABI, HRESULT, // return [ ISpVoice.ptr, LPCWSTR, // *pwcs DWORD, // dwFlags ULONG // *pulStreamNumber ]).ptr }, { 'SpeakStream': ctypes.voidptr_t }, { 'GetStatus': ctypes.voidptr_t }, { 'Skip': ctypes.voidptr_t }, { 'SetPriority': ctypes.voidptr_t }, { 'GetPriority': ctypes.voidptr_t }, { 'SetAlertBoundary': ctypes.voidptr_t }, { 'GetAlertBoundary': ctypes.voidptr_t }, { 'SetRate': ctypes.voidptr_t }, { 'GetRate': ctypes.voidptr_t }, { 'SetVolume': ctypes.voidptr_t }, { 'GetVolume': ctypes.voidptr_t }, { 'WaitUntilDone': ctypes.voidptr_t }, { 'SetSyncSpeakTimeout': ctypes.voidptr_t }, { 'GetSyncSpeakTimeout': ctypes.voidptr_t }, { 'SpeakCompleteEvent': ctypes.voidptr_t }, { 'IsUISupported': ctypes.voidptr_t }, { 'DisplayUI': ctypes.voidptr_t } // end ISpVoice ]); // FUNCTIONS // http://msdn.microsoft.com/en-us/library/windows/desktop/ms695279%28v=vs.85%29.aspx let CoInitializeEx = lib.declare('CoInitializeEx', WINABI, HRESULT, // result LPVOID, // pvReserved DWORD // dwCoInit ); // http://msdn.microsoft.com/en-us/library/windows/desktop/ms688715%28v=vs.85%29.aspx let CoUninitialize = lib.declare('CoUninitialize', WINABI, VOID // return ); // http://msdn.microsoft.com/en-us/library/windows/desktop/ms686615%28v=vs.85%29.aspx let CoCreateInstance = lib.declare('CoCreateInstance', WINABI, HRESULT, // return REFCLSID, // rclsid LPUNKNOWN, // pUnkOuter DWORD, // dwClsContext REFIID, // riid LPVOID // *ppv ); // HELPER FUNCTIONS function checkHRESULT(hr /*primative HRESULT*/, funcName /*jsStr*/) { // primative because thats what is returned by declared functions that // return HRESULT hr = hr.toString(); // makes it primative if (hr < 0) { console.error('HRESULT', hr, 'returned from function ', funcName /*, 'getStrOfResult:', getStrOfResult(hr)*/); throw new Error('HRESULT ' + hr + ' returned from function ' + funcName); } } let CLSIDFromArr = IIDFromArr = function(jsArr_pieces) { let guid = GUID(); // CLSID and IID are same they are GUID guid.Data1 = parseInt(jsArr_pieces[0], 16); guid.Data2 = parseInt(jsArr_pieces[1], 16); guid.Data3 = parseInt(jsArr_pieces[2], 16); let j = 2; for (let i=0; i<8; i++) { j++; guid.Data4[i] = parseInt(jsArr_pieces[j], 16); }; return guid; } // CONSTANTS let COINIT_MULTITHREADED = 0; let COINIT_APARTMENTTHREADED = 2; let CLSCTX_ALL = 0x17; let CLSID_SpVoice = CLSIDFromArr(['0x96749377', '0x3391', '0x11D2', '0x9E', '0xE3', '0x00', '0xC0', '0x4F', '0x79', '0x73', '0x96']); let IID_ISpVoice = IIDFromArr(['0x6C44DF74', '0x72B9', '0x4992', '0xA1', '0xEC', '0xEF', '0x99', '0x6E', '0x04', '0x22', '0xD4']); let SPF_DEFAULT = 0; function main() { let spVoice; let spVoicePtr; try { // MSDN Docs tell us ot use CoInitEx instead of CoInit, and default is 0 // which is COINIT_MULTITHREADED but it wouldnt work so I used // COINIT_APARTMENTTHREADED and it worked checkHRESULT would throw a bad // HRESULT of RPC_E_CHANGED_MODE which is 0x80010106 which is // -2147417850. primative_hr = CoInitializeEx(null, COINIT_APARTMENTTHREADED); checkHRESULT(primative_hr, "CoInitializeEx"); spVoicePtr = ISpVoice.ptr(); primative_hr = CoCreateInstance(CLSID_SpVoice.address(), null, CLSCTX_ALL, IID_ISpVoice.address(), spVoicePtr.address()); checkHRESULT(primative_hr, "CoCreateInstance"); spVoice = spVoicePtr.contents.lpVtbl.contents; let aText = 'Hello Firefox!'; let aFlags = SPF_DEFAULT; primative_hr = spVoice.Speak(spVoicePtr, aText, aFlags, 0); checkHRESULT(primative_hr, "CoCreateInstance"); } catch (ex) { console.error('ex occured:', ex); } finally { if (spVoice) { spVoice.Release(spVoicePtr); } CoUninitialize(); } } main(); lib.close();
Other Examples
SHGetPropertyStoreForWindow
- This examples shows that CoInit is not needed, some WinAPI functions return the interface. This example usesIPropertyStore::SetValue
to change the System.UserModelApp.ID of an open Firefox window.IShellItem
IFileOperation
IShellLink
&IPersistFile
- Create shortcut files.- Example A - Based on Bugzilla :: Bug 738501 - Attachement 608538
- Exmaple B - Based on Example A
IShellLink
&IPersistFile
&IPropertyStore
- Uses IPropertyStore to set and get the System.UserModelApp.ID on shortcut files.