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
#include
#include
#include
#include
#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