Checking For Script Syntax Errors, This Time With Code
A number of people asked me to clarify yesterday's entry. Rather than try to talk you through it, I think the code is straightforward enough to speak for itself. Here's a little skeleton that I just whipped up.
#include <stdio.h>
#include <activscp.h>
#include <new>
const GUID CLSID_VBScript = {0xb54f3741, 0x5b07, 0x11cf, {0xa4, 0xb0, 0x00, 0xaa, 0x00, 0x4a, 0x55, 0xe8}};
const GUID CLSID_JScript = {0xf414c260, 0x6ac0, 0x11cf, {0xb6, 0xd1, 0x00, 0xaa, 0x00, 0xbb, 0xbb, 0x58}};
class MySite : public IActiveScriptSite {
private:
ULONG m_cref;
virtual ~MySite() {}
public:
MySite() {
this->m_cref = 1;
}
STDMETHOD_(ULONG, AddRef)() {
return ++this->m_cref;
}
STDMETHOD_(ULONG,Release)() {
--this->m_cref;
if (this->m_cref == 0) {
delete this;
return 0;
}
return this->m_cref;
}
STDMETHOD(QueryInterface)(REFIID iid, void ** ppv) {
if (ppv == NULL)
return E_POINTER;
if (IsEqualIID(iid, IID_IUnknown))
*ppv = (IUnknown*)this;
else if (IsEqualIID(iid, IID_IActiveScriptSite))
*ppv = (IActiveScriptSite*)this;
else {
*ppv = NULL;
return E_NOINTERFACE;
}
this->AddRef();
return S_OK;
}
STDMETHOD(GetLCID)(LCID * plcid) {
return E_NOTIMPL;
}
STDMETHOD(GetItemInfo)(
LPCOLESTR pstrName,
DWORD dwReturnMask,
IUnknown ** ppunkItem,
ITypeInfo ** ppti) {
return E_NOTIMPL;
}
STDMETHOD(GetDocVersionString)(BSTR * pbstrVersion) {
return E_NOTIMPL;
}
STDMETHOD(OnScriptTerminate)(
const VARIANT * pvarResult,
const EXCEPINFO * pexcepinfo) {
return S_OK;
}
STDMETHOD(OnStateChange)(SCRIPTSTATE state) {
return S_OK;
}
STDMETHOD(OnEnterScript)() {
return S_OK;
}
STDMETHOD(OnLeaveScript)() {
return S_OK;
}
STDMETHOD(OnScriptError)(IActiveScriptError * perror) {
EXCEPINFO excepinfo;
LONG column = 0;
ULONG line = 0;
DWORD context = 0;
BSTR bstrLine = NULL;
memset(&excepinfo, 0x00, sizeof excepinfo);
perror->GetExceptionInfo(&excepinfo);
if (excepinfo.pfnDeferredFillIn != NULL)
excepinfo.pfnDeferredFillIn(&excepinfo);
perror->GetSourceLineText(&bstrLine);
perror->GetSourcePosition(&context, &line, &column);
wprintf(L"Error on line %ld column %ld\n", line, column);
if (bstrLine != NULL)
wprintf(L"Line: %s\n", bstrLine);
if (excepinfo.bstrDescription != NULL)
wprintf(L"Description: %s\n", excepinfo.bstrDescription);
if (excepinfo.bstrSource != NULL)
wprintf(L"Source: %s\n", excepinfo.bstrSource);
if (excepinfo.bstrHelpFile != NULL)
wprintf(L"Help: %s\n", excepinfo.bstrHelpFile);
SysFreeString(bstrLine);
SysFreeString(excepinfo.bstrDescription);
SysFreeString(excepinfo.bstrSource);
SysFreeString(excepinfo.bstrHelpFile);
return S_OK;
}
};
void main() {
HRESULT hr = S_OK;
HRESULT hrInit;
IClassFactory * pfactory = NULL;
IActiveScript * pscript = NULL;
IActiveScriptParse * pparse = NULL;
IActiveScriptSite * psite = NULL;
hr = hrInit = OleInitialize(NULL);
if (FAILED(hr))
goto LError;
hr = CoGetClassObject(CLSID_VBScript, CLSCTX_SERVER, NULL,
IID_IClassFactory, (void**)&pfactory);
if (FAILED(hr))
goto LError;
hr = pfactory->CreateInstance(NULL, IID_IActiveScript, (void**)&pscript);
if (FAILED(hr))
goto LError;
psite = new(std::nothrow) MySite();
if (psite == NULL) {
hr = E_OUTOFMEMORY;
goto LError;
}
hr = pscript->SetScriptSite(psite);
if (FAILED(hr))
goto LError;
hr = pscript->QueryInterface(IID_IActiveScriptParse, (void**)&pparse);
if (FAILED(hr))
goto LError;
hr = pparse->ParseScriptText(L"Function Foo \n Foo = 123 \n End Funtcion \n",
NULL, NULL, NULL, 0, 1, 0, NULL, NULL);
if (FAILED(hr))
goto LError;
LError:
if (FAILED(hr))
printf("%0x\n", hr);
if (pparse != NULL)
pparse->Release();
if (psite != NULL)
psite->Release();
if (pscript != NULL) {
pscript->Close();
pscript->Release();
}
if (pfactory != NULL)
pfactory->Release();
if (SUCCEEDED(hrInit))
OleUninitialize();
}
As you would expect, this program prints out the information about the error, and ParseScriptText returns SCRIPT_E_REPORTED to indicate that there was an error but it has already been reported. Had there been no error, the script would not have actually run; the engine is not started, just initialized.
Error on line 3 column 5
Line: End Funtcion
Description: Expected 'Function'
Source: Microsoft VBScript compilation error
80020101
Comments
- Anonymous
October 12, 2005
what are the advantages/disadvantages of calling: OleInitialize() vs CoInitialize().
Similarly, what is the advantage/disadvantage of calling: CoGetClassObject() and then pfactory->CreateInstance() vs just calling CoCreateInstance() ? - Anonymous
October 12, 2005
First off, you should not call CoInitialize in new code. Call CoInitializeEx.
Second, the differences between OleInitialize and CoInitializeEx is clearly described in the OleInitialize documentation.
Technically, since the app above does not do clipboard, drag-n-drop, etc, it should be calling CoInitializeEx instead of OleInitialize. However, given that I might be using this skeleton in the future to develop a more full-fledged script host that might have to do stuff like that, I would rather have the slightly less efficient call in exchange for avoiding the maintanance headache of remembering to change the initialization routine later. - Anonymous
October 12, 2005
The advantage of getting the factory over calling CoCreateInstance is of course that you can use the factory over and over again to create a large number of engines. Behind the scenes CoCreateInstance is doing just that, so why make it get the factory over and over again?
Of course, this program only creates the one engine. But like I said in the last comment, I might want to extend this skeleton to be a script host that hosts multiple engines at once.
Maybe this is premature extensibility. But since it's a grand total of what, six extra lines of code to have the factory object, I'm not that worried about it. - Anonymous
October 12, 2005
And as Francesco Balena pointed out in the July 1999 issue of Microsoft Internet Developer, you can do the same thing in about 7 or so lines of VB6 by using the Microsoft Script Control as your script host. - Anonymous
October 12, 2005
The article is here:
http://www.microsoft.com/mind/0799/script/script.asp
You can trap syntax and runtime errors using the script control, but that is different than the problem that I am solving here. With the script control, if the program is syntactically valid then it always runs. What if you want to just check the program for valid syntax without running it? - Anonymous
October 12, 2005
An excellent point. The Script Control's AddCode() method always immediately runs the code. Free-standing lines will act immediately unless an early syntax error is encountered. I let the presence of a Run() method delude me. - Anonymous
February 09, 2006
The comment has been removed - Anonymous
April 13, 2006
The comment has been removed - Anonymous
April 13, 2006
You can't.
Consider the security consequences if a web page could run "cmd.exe del .".