Checking For Script Syntax Errors, This Time With Code

Checking For Script Syntax Errors, This Time With Code

  • Comments 9

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

  • 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() ?
  • 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.




  • 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.
  • 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.
  • 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?

  • 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.
  • Hello, i'm implementing a windows script host in delphi and your posts have been of great help. Thanks for sharing your thoughts and knowlegde with the "world".

    Regarding this post in particular, I've done what you describe (in delphi) but if I have a msgbox statement before the error, the msgbox dialog always appears. Is this right? When you say "the script would not have actually run" it's suppose not to appear no message box, right?

    I do eveything as you mention (at least, I suppose I do), getting the interfaces ActiveScriptSite, ActiveScriptParse, set the ActiveScriptSite, and don't do the ActiveScriptParse.InitNew nor either the ActiveScript.SetScriptState(SCRIPTSTATE_CONNECTED). So what I'm doing here wrong?

    Thanks a lot,

    Ricardo
  • How can I pass arguments to an exe that needs to be executed from browser. From this what I mean is that I want to execute an exe and also pass commandline arguments to it; all this to be done in browser
  • You can't.

    Consider the security consequences if a web page could run "cmd.exe del *.*".


Page 1 of 1 (9 items)