Introduction
Quite often application developers tend to find out that thay no longer have time to extend functionality of program by releasing newer and newer executables.Simply because hunger for new but small features and more flexibility by users never ends. And knowing how creative user comunity itself can be, next evolution step is often clear ...
Lets' make the whole thing scriptable
I usually try to use whatever library that is already available as part of operating system. And we are quite lucky here. Ever since windows 95 Windows ships with Internet Explorer which in turn is continuously updated to support Latest version of JavaScript and VBScript. Now what is Interesting is that jscript.dll and vbscript.dll are as mentioned before part of every windows version out there. And also fact that they are just script engines. Meaning that they were written to be completely independent from application that uses them. This smart idea was at that time named Active Scripting. There are many downloadable dll engines writen for it like Ruby Lua Python Perl Haskell... and few of esoteric ones too ;D And the best part is no code changes are needed except changing string from let's say "JavaScript" to "RubyScript" But back to engines. If I oversimplify it then I could say that all that they can do are elementary operations like A+B=C plus Regular Expressions. Three most known applications using them are Microsofts Web server , Windows scripting host, and Internet Explorer. For example. In this Api Internet explorer simply implements Scripting Host interface = just registers his functions and variables(A, B, C= complete DOM model in this case) and from that moment on everything inside him is scriptable. Folks from Microsoft were so impressed with this new flexibility that they said to them self. "Lets register every win32 api and com thing we can find just for fun and forget tedious c++ compilation times" And Windows Scripting Host was born. Now you probably think yourself "Damn that will be another overcomplicated esoteric com api class galore" Well you will be surprised how easy it really is. Just Select script engine with proper string like "JavaScript" and Register your procedures and vars in GetIDsOfNames
The Sample
The sample itself except registering simple test function also registers most of win32 apis so that you have something to play with right from the start. This also can give you an glimpse on how for example Windows Scripting Host internally works. As for now. Sample supports two major calling conventions stdcall and cdecl (put prefix _ in front of api name in script ). But detection of function return type (int or WCHAR*) is just basic and so is error handling so you can focus more on how it works.
#define INITGUID
#include <windows.h>
#include <activscp.h>
#include <stdio.h>
HMODULE dll[1000];
int WINAPI test( char* text ) {
printf("my app proc called from script with param \"%s\" ",text);
return atol(text+strlen(text)-1)+1;
}
struct ScriptInterface : public IDispatch {
DWORD cdcl;
long WINAPI QueryInterface( REFIID riid,void ** object) { *object=IsEqualIID(riid, IID_IDispatch)?this:0; return *object?0:E_NOINTERFACE; }
DWORD WINAPI AddRef () { return 0; }
DWORD WINAPI Release() { return 0; }
long WINAPI GetTypeInfoCount( UINT *) { return 0; }
long WINAPI GetTypeInfo( UINT, LCID, ITypeInfo **) { return 0; }
long WINAPI GetIDsOfNames( REFIID riid, WCHAR** name, UINT cnt ,LCID lcid, DISPID *id) {
for(DWORD j=0; j<cnt;j++) {
char buf[1000]; DWORD k; WideCharToMultiByte(0,0,name[j],-1,buf,sizeof(buf),0,0);
for(k=0; k<2;k++) {
if(strcmp(buf+k,"test")==0) { id[j]=(DISPID)test; break ; } else
for(int i=0; (dll[i]||dll[i-1]);i++) { if((id[j]=(DISPID)GetProcAddress(dll[i],buf+k))) break; } if(id[j]) break;
}
cdcl=k;
if(!id[j]) return E_FAIL;
}
return 0;
}
long WINAPI Invoke( DISPID id, REFIID riid, LCID lcid, WORD flags, DISPPARAMS *arg, VARIANT *ret, EXCEPINFO *excp, UINT *err) {
if(id) {
int i= cdcl?arg->cArgs:-1, result=0, stack=arg->cArgs*4; char* args[100]={0};
while((cdcl?(--i>-1):(++i<arg->cArgs))) {
DWORD param=arg->rgvarg[i].ulVal;
if(arg->rgvarg[i].vt==VT_BSTR) {
WCHAR* w=arg->rgvarg[i].bstrVal;
WideCharToMultiByte(0,0,w,-1,args[i]=new char[wcslen(w)+1],wcslen(w)+1,0,0); param=(DWORD)args[i];
}
_asm push param;
}
_asm call id
_asm mov result, eax
if (cdcl) _asm add esp, stack
i=-1; while(++i<arg->cArgs) if(args[i]) delete args[i];
if(ret) ret->ullVal=result; char*c=(char*)result;
if(ret) ret->vt=VT_UI4; __try { if(!c[1]&&(*c<'0'||*c>'9')) ret->vt=VT_BSTR; ret->bstrVal=SysAllocString((WCHAR*)c); } __except(EXCEPTION_EXECUTE_HANDLER){}
return 0;
}
return E_FAIL;
}
};
struct ScriptHost : public IActiveScriptSite {
ScriptInterface Interface;
long WINAPI QueryInterface( REFIID riid,void ** object) { *object=(IsEqualIID(riid, IID_IActiveScriptSite))?this:0; return *object?0:E_NOINTERFACE; }
DWORD WINAPI AddRef () { return 0; }
DWORD WINAPI Release() { return 0; }
long WINAPI GetLCID( DWORD *lcid ) { *lcid = LOCALE_USER_DEFAULT; return 0; }
long WINAPI GetDocVersionString( BSTR* ver ) { *ver = 0; return 0; }
long WINAPI OnScriptTerminate(const VARIANT *,const EXCEPINFO *) { return 0; }
long WINAPI OnStateChange( SCRIPTSTATE state) { return 0; }
long WINAPI OnEnterScript() { return 0; }
long WINAPI OnLeaveScript() { return 0; }
long WINAPI GetItemInfo(const WCHAR *name,DWORD req, IUnknown ** obj, ITypeInfo ** type) {
if(req&SCRIPTINFO_IUNKNOWN) *obj=&Interface; if(req&SCRIPTINFO_ITYPEINFO) *type=0; return 0;
}
long WINAPI OnScriptError( IActiveScriptError *err ) {
EXCEPINFO e; err->GetExceptionInfo(&e); MessageBoxW(0,e.bstrDescription,e.bstrSource,0);
return 0;
}
};
void main() {
HRESULT hr; CoInitialize(0);
char* name[]={"ntdll","msvcrt","kernel32","user32","advapi32","shell32","wsock32","wininet","<",">",0};
for(int i=0; name[i];i++) dll[i]=LoadLibrary(name[i]);
GUID guid; CLSIDFromProgID( L"JavaScript" , &guid );
IActiveScript *script; hr = CoCreateInstance(guid, 0, CLSCTX_ALL,IID_IActiveScript,(void **)&script); if(!script) return;
IActiveScriptParse *parse; hr = script->QueryInterface(IID_IActiveScriptParse, (void **)&parse);
ScriptHost host;
script->SetScriptSite((IActiveScriptSite *)&host);
script->AddNamedItem(L"app", SCRIPTITEM_ISVISIBLE|SCRIPTITEM_NOCODE );
WCHAR* source = L" number = 'text 1'.match(/\\d/); result = app.test('hello'+' world '+number); "
L" app.MessageBoxA(0,'WIN'+32+' or any dll api called from JavaScript without need to install anything','hello world '+result,0)";
hr = parse->InitNew();
hr = parse->ParseScriptText( source ,0,0,0,0,0,0,0,0);
script->SetScriptState( SCRIPTSTATE_CONNECTED);
script->Close();
};
Points of Interest
Most interesting part is Invoke. Since there you are called from script. Script will tell you via Flags parameter whether he wana Get / Set variable or call procedure.
As I mentioned. If build-in JavaScript or VBscript is not for you and you don't mind installing something. Then for example look here for [other engines mentioned in aticle]
Another think that comes in my mind is support of thiscall calling convention. That is calling C++ members of instanced objects. Well I wanted to keep sample simple. But I explain how call thicall works since it's simple. register it the same way via &member but put instance pointer in register ecx before actuall call
_asm mov ecx, pointer_to_instance
_asm call id
As you can see thiscall is just standard cdecl (therefore use _ prefix in script) but relaing on this pointer being present in ecx.
Also beware that static class functions are normal stdcall.
And with Borland C++ compillers things with calling members are complicated since they love to use nonstandard fastcall (different with every compiller) which is mix of stdcall cdecl and thiscall with first three params passed in registers and on top of it they are in different order then rest of params. But you can usually just remove __fascall from declarations and you will get standardized thiscall again. Sometimes I feel like this thing is just used to mark borland territory ;D
Anyway I personally use just standard ones like stdcall since they work in any compiller plus speedup with others is virtually undetectable.