Introduzione alle API Win32

Capitolo 39: Indies: l'implementazione

Ecco, come promesso, la realizzazione di IndiEs in termini di un interprete esterno che rispetti i protocolli "Active Scripting" (tipicamente, ad esempio, VBScript), per tramite del Microsoft Script Control. Si tratta, in pratica, del file IndiEs.c, utilizzabile con il resto del progetto già illustrato, e può meritare studiarlo attentamente.

 

// // IndiEs.c basato sulla valutazione di una F(X), // delegata a VBScript o altra implementazione di // engine "Active Scripting" (Python, ecc) // #define STRICT #define CINTERFACE #include <windows.h> #include <stdio.h> #include <ctype.h> #include <stdlib.h> #include <string.h> #include "IndiEs.h" // // accesso allo ScriptControl // // non ci serve davvero, ma occorre una definizione // dummy per compilare...: enum ScriptControlStates { }; // questa ci serve eccome...: const GUID IID_IScriptControl = {0x0e59f1d3,0x1fbe,0x11d0,{0x8f,0xf2,0x00,0xa0,0xd1,0x00,0x38,0xbc}}; // questa no, quindi la commentiamo via: // const GUID CLSID_ScriptControl = // {0x0e59f1d5,0x1fbe,0x11d0,{0x8f,0xf2,0x00,0xa0,0xd1,0x00,0x38,0xbc}}; // // per dichiarare in C una interfaccia COM, si fa...: // #undef INTERFACE #define INTERFACE IScriptControl DECLARE_INTERFACE_(IScriptControl, IDispatch) { /* i metodi di IUnknown */ STDMETHOD(QueryInterface)(THIS_ REFIID riid, LPVOID FAR* ppvObj) PURE; STDMETHOD_(ULONG, AddRef)(THIS) PURE; STDMETHOD_(ULONG, Release)(THIS) PURE; /* i metodi che aggiunge IDispatch */ STDMETHOD(GetTypeInfoCount)(THIS_ UINT FAR* pctinfo) PURE; STDMETHOD(GetTypeInfo)(THIS_ UINT itinfo, LCID lcid, ITypeInfo FAR* FAR* pptinfo) PURE; STDMETHOD(GetIDsOfNames)(THIS_ REFIID riid, OLECHAR FAR* FAR* rgszNames, UINT cNames, LCID lcid, DISPID FAR* rgdispid) PURE; STDMETHOD(Invoke)(THIS_ DISPID dispidMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS FAR* pdispparams, VARIANT FAR* pvarResult, EXCEPINFO FAR* pexcepinfo, UINT FAR* puArgErr) PURE; /* i metodi specifici di IScriptControl, con qualche commento aggiuntivo per i piu` basilari */ STDMETHOD( get_Language)(THIS_ BSTR* pbstrLanguage) PURE; // put_Language va chiamata per stabilire il linguaggio da usare STDMETHOD( put_Language)(THIS_ BSTR pbstrLanguage) PURE; STDMETHOD( get_State)(THIS_ enum ScriptControlStates* pssState) PURE; STDMETHOD( put_State)(THIS_ enum ScriptControlStates pssState ) PURE; STDMETHOD( put_SitehWnd)(THIS_ long phwnd) PURE; STDMETHOD( get_SitehWnd)(THIS_ long* phwnd) PURE; STDMETHOD( get_Timeout)(THIS_ long* plMilliseconds) PURE; STDMETHOD( put_Timeout)(THIS_ long plMilliseconds) PURE; STDMETHOD( get_AllowUI)(THIS_ VARIANT_BOOL* pfAllowUI) PURE; STDMETHOD( put_AllowUI)(THIS_ VARIANT_BOOL pfAllowUI) PURE; STDMETHOD( get_UseSafeSubset)(THIS_ VARIANT_BOOL* pfUseSafeSubset) PURE; STDMETHOD( put_UseSafeSubset)(THIS_ VARIANT_BOOL pfUseSafeSubset) PURE; STDMETHOD( get_Modules)(THIS_ interface IScriptModuleCollection** ppmods) PURE; STDMETHOD( get_Error)(THIS_ interface IScriptError** ppse) PURE; STDMETHOD( get_CodeObject)(THIS_ IDispatch** ppdispObject) PURE; STDMETHOD( get_Procedures)(THIS_ interface IScriptProcedureCollection** ppdispProcedures) PURE; STDMETHOD( _AboutBox)(THIS) PURE; // con AddObject si da` accesso al codice di scripting ad un // arbitrario oggetti Automation da noi ottenuto o implementato STDMETHOD( AddObject)(THIS_ BSTR Name, IDispatch* Object, VARIANT_BOOL AddMembers) PURE; STDMETHOD( Reset)(THIS) PURE; // con AddCode si aggiungono tipicamente procedure e funzioni // nel linguaggio interpretativo ("di scripting") in uso STDMETHOD( AddCode)(THIS_ BSTR Code) PURE; // con Eval si valuta una qualsiasi espressione, e se ne // ottiene il risultato STDMETHOD( Eval)(THIS_ BSTR Expression, VARIANT* pvarResult) PURE; // con ExecuteStatement si esegue una istruzione STDMETHOD( ExecuteStatement)(THIS_ BSTR Statement) PURE; // con Run si esegue una qualsiasi procedura o funzione del // linguaggio (aggiunta prima con AddCode), con degli argomenti // ed eventualmente un risultato STDMETHOD( Run)(THIS_ BSTR ProcedureName, SAFEARRAY** Parameters, VARIANT* pvarResult) PURE; }; // puntiamo all'oggetto 'Script Control' static IScriptControl* pSC = 0; // emissione di un messaggio di errore static void say(const char* m) { MessageBox(0,m,"Errore VBS", MB_OK); } // macro per controllare e diagnosticare errori #define IFERR(lab,msg) if(FAILED(hr)) { say(msg); result=FALSE; goto lab; } // inizializzazione di IndiEs; torna TRUE se tutto OK BOOL IndiEs_Init( const char* sOpzioni, // opzioni-stringa: ignorato, per ora unsigned long lOpzioni // opzioni-intero: ignorato, per ora ) { if(pSC) { say("Doppia chiamata ad Init senza Finis"); return FALSE; } BOOL result = TRUE; CoInitialize(0); // inizializziamo il COM GUID clsid; HRESULT hr; // scopriamo il CLSID del Microsoft Script Control hr = CLSIDFromProgID(L"MSScriptControl.ScriptControl",&clsid); IFERR(error1, "MS Script Control non istallato"); // creiamo un'istanza dello Script Control hr = CoCreateInstance(clsid,0,CLSCTX_SERVER, IID_IScriptControl,(void**)&pSC); IFERR(error1, "Fallita creazione MS Script Control"); // inizializzazione del linguaggio (sempre VBScript) BSTR lang; lang = SysAllocString(L"VBScript"); hr = pSC->lpVtbl->put_Language(pSC,lang); SysFreeString(lang); IFERR(error2, "Fallita impostazione linguaggio"); error1: // uscita normale o per errore return result; error2: // uscita per errore se pSC e` gia` stato ottenuto pSC->lpVtbl->Release(pSC); pSC = 0; return result; } // terminazione di IndiEs BOOL IndiEs_Finis(void) { if(pSC) pSC->lpVtbl->Release(pSC); pSC = 0; CoUninitialize(); // rilasciamo il COM return TRUE; } // impostazione di una espressione BOOL IndiEs_Espr(const char* espressione) { // errore se lo ScriptControl non e` settato if(!pSC) return FALSE; // trasformazione dell'espressione in una BSTR // contenente anche header e footer per "F(X)" // definizione dei componenti della BSTR int len = strlen(espressione); static const wchar_t* head = L"Function F(X)\r\nF="; static int headlen = wcslen(head); static const wchar_t* foot = L"\r\nEnd Function\r\n"; static int footlen = wcslen(foot); // spazio totale da allocare (numero di caratteri) int totlen = headlen+footlen+len; // allocazione dello spazio (per stringa normale) wchar_t* buf = (wchar_t*)malloc(2*(totlen+1)); // inserimento dei componenti della stringa memcpy(buf, head, 2*headlen); mbstowcs(buf+headlen, espressione, len); memcpy(buf+headlen+len, foot, 2*footlen); buf[totlen] = 0; // trasformazione in BSTR BSTR bbuf = SysAllocString(buf); free(buf); // aggiunta della funzione allo script-control HRESULT hr = pSC->lpVtbl->AddCode(pSC, bbuf); // terminazione della _Espr SysFreeString(buf); return SUCCEEDED(hr); } // valutazione della funzione per una data X BOOL IndiEs_Valuta(double X, double* pY) { // errore se lo ScriptControl non e` settato if(!pSC) return FALSE; // nome della funzione da chiamare static BSTR bName = SysAllocString(L"F"); // array dei parametri (con un solo elemento) static SAFEARRAY* saArgs = SafeArrayCreateVector(VT_VARIANT,0,1); // le variant di argomento e risultato VARIANT vArg,vResult; vResult.vt = VT_EMPTY; vArg.vt = VT_R8; vArg.dblVal = X; // inserimento dell'argomento nell'array long i=0; SafeArrayPutElement(saArgs,&i,&vArg); // esecuzione della funzione HRESULT hr = pSC->lpVtbl->Run(pSC, bName, &saArgs, &vResult); // il risultato serve come "double" if(SUCCEEDED(hr)) hr = VariantChangeType(&vResult,&vResult,0,VT_R8); *pY = vResult.dblVal; // terminazione della _Valuta return SUCCEEDED(hr); }

Chi ci ha seguito negli ultimi capitoli ha certamente avuto un paio di sorprese da questo codice... esso, infatti, a differenza di quanto avevamo accennato, non permette di usare diversi linguaggi di scripting (bensì soltanto VBScript), e d'altra parte usa il metodo più "avanzato" (efficiente e flessibile) basato su AddCode e Run invece di quello più semplice basato su Eval).

È proprio vero che non si può mai essere sicuri di nulla, eh...?!-)

Naturalmente, la nostra ragione per apportare queste variazioni è quella di convincere il lettore a mettere le mani sul codice, rendendolo più flessibile in termini di linguaggio di scripting da usare, e confrontandolo, in termini di semplicità, potenza, ed efficienza, con l'alternativa basata su Eval...!-)

Finita così questa breve "carrellata" su uno dei tanti, preziosi utilizzi del COM, nel prossimo capitolo torneremo ad occuparci dei controlli standard forniti da Windows, presentando i controlli di classe LISTBOX nel contesto di un problema interessante e di una sua possibile soluzione.


Capitolo 38: Indies: scelte di progetto
Capitolo 40: output per il debug
Elenco dei capitoli