Scripting
Chat
This month I received an e-mail from a developer regarding a problem
he was having with IActiveScriptParse::ParseScriptText blocking
and not returning when a script was parsed. I thought it would be useful
to cover this method in some more detail since it is highly used in Active
Scripting, but not necessarily well understood.
First off, some background on the method for those newer to Active Scripting.
When constructing an Active Scripting "host" (the code module that interacts
with the scripting "engine" and manages the process of feeding script
to the engine, handling events from the engine, etc.), one of the first
steps after constructing the appropriate engine (VBScript, JScript, etc)
is to feed script text into the engine itself. This is done by using IActiveScriptParse::ParseScriptText.
The IActiveScriptParse interface is obtained from the engine via
a normal COM IUnknown::QueryInterface call.
The ParseScriptText method itself has a number of parameters that
must be either used or defaulted depending upon the style of use. Let's
take a look at these parameters in more detail:
HRESULT ParseScriptText(
LPCOLESTR pstrCode,
LPCOLESTR pstrItemName,
IUnknown *punkContext,
LPCOLESTR pstrDelimiter,
DWORD dwSourceContextCookie,
ULONG ulStartingLineNumber,
DWORD dwFlags,
VARIANT *pvarResult,
EXCEPINFO *pexcepinfo );
pstrCode
[in] A pointer to the script text to be parsed by the engine. This
text can be a group of one or more functions, global script code such
as variables, etc.
pstrItemName
[in] A pointer to the name of a context (aka named context) in which
the script should be evaluated. If NULL, the context is the global context.
A named context can be thought of as similar to a C++ namespace in that
it wraps a cluster of code with a name. This named context can then
later be accessed with IActiveScript::GetScriptDispatch() to
fetch only those functions within a particular namespace.
punkContext
[in] A pointer to a context normally used in a debugging environment.
This context would be the IDebugDocumentContext and would represent
a subsection of a running script. Under most circumstances, this parm
would be set to NULL.
pstrDelimiter
[in] A end-of-document delimiter. This could be used to parse only
a subset of the entire script passed with pstrCode and could
be any character. However, under most circumstances, this parm would
be NULL, indicating that parsing should continue until a NULL character
is reached in the passed string (end of the string).
dwSourceContextCookie
[in] A value used later in a debugging host (smart host) to interact
with the script document to set breakpoints, locate subsections of the
document, etc. This value is obtained through a prior call to IDebugDocumentHelper::DefineScriptBlock.
If creating a basic host without the more advanced debugging support,
this value can be 0.
ulStartingLineNumber
[in] The line of script at which parsing will begin. Lines are defined
by carriage return/line feed pairs (\r\n). Normally this would be set
to 0.
dwFlags
[in] There are 3 choices here: SCRIPTTEXT_ISEXPRESSION, SCRIPTTEXT_ISVISIBLE,
SCRIPTTEXT_ISPERSISTENT.
SCRIPTTEXT_ISEXPRESSION is used to evaluate a small block of script
as an expression and have the result returned immediately. For example,
if you needed the result from a
script that performed a numeric calculation. This could be used in a
web page that provides a simple calculator, or for adding up numbers
in an on-screen form. This flag is infrequently used.
SCRIPTTEXT_ISVISIBLE is used when the script text is to be callable
within the global namespace. This flag is normally included; however,
it could be excluded in a case where the host wishes to add multiple
sets of script text, but where one or more of the sets is not to be
visible outside the script itself. In essence, this allows a form of
code hiding.
SCRIPTTEXT_ISPERSISTENT is used to ensure that any parsed code
is retained when moving back to the initialized state or when cloning
the engine. Cloning is often done to restart an engine that was interrupted.
By adding this flag, you ensure that you don't have to recall ParseScriptText
to re-add the script text. However, you might find special circumstances
where script text is meant to be run once and not again. This flag ensures
that the script text is not stored in the engine unless you wish it
to be.
pvarResult
[out] A pointer to a VARIANT that contains the result of an expression
evaluation. Passing in a non-NULL value only makes sense if you also
included the flag SCRIPTTEXT_ISEXPRESSION.
pexcepInfo
[out] A pointer to a EXCEPINFO structure that will contain exceptions
encountered while trying to process the script text. It's important
to understand that the EXCEPINFO structure should be initialized to
NULL (cleared out) when passed, and checked for any non-NULL members
when ParseScriptText returns. Any BSTRs that appear in the structure
must be released by calling SysFreeString(). An easy way to ensure
you don't forget to do this is by creating a C++ class called CExcepInfo
that derives from EXCEPINFO. In the constructor, you'd call ZeroMemory(this,sizeof(EXCEPINFO)).
In the destructor, you'd check each BSTR member and call SysFreeString
as appropriate. When calling ParseScriptText, you'd pass in a
pointer to the CExcepInfo class rather than a EXCEPINFO structure.
The CExcepInfo class will then make sure cleanup and initialization
are done when needed. The EXCEPINFO structure will contain useful information
about possible parsing problems that can be relayed back to the user,
particularly if the user was the one who wrote the script.
It's important to understand that ParseScriptText blocks when
evaluating an expression, and doesn't block when evaluating normal script
text. The developer controls this by passing in the appropriate flags
to the method. In the case of non-blocking script text, the script is
actually executed when a call is made to IActiveScript::SetScriptState.
The problem I think developers have with this method is its multiple use
configuration. It is trying to serve two different roles, and it is often
unclear which flags to pass in, when to pass them in, or which flags/values
conflict with others. Ideally, Microsoft would have broken out the method
into two or more methods that were narrow and easier to understand. But
the interfaces are here to stay and it's important to understand the operation
of this critical method in Active Scripting.
Side Dish
Microsoft's Andrew Clinick has been writing a superb series of articles
regarding scripting in the .NET world. I highly recommend reading the
Scripting Clinick column section on http://msdn.microsoft.com
when you have time.
The Active
Scripting FAQ
Here's a new entry in the upcoming FAQ release:
Q: How can I safely interrupt a running script?
A: To interrupt a running script, you call IActiveScript::InterruptScriptThread().
This is not guaranteed to immediately interrupt the script. Rather, it
sets a flag that the script should stop, and the script engine will stop
at the next opportunity. InterruptScriptThread is one of the only
two methods you can call on a script engine from outside its base thread
(the other being SetScriptSite). Since the thread where the script
engine is running is obviously blocked while the script is in progress,
you must call InterruptScriptThread from a different thread. This
would also apply for reacting to the ESC key to call InterruptScriptThread.
Since the thread isn't pumping messages while it's waiting for the script
to complete, it won't detect that the ESC key has been pressed. This sample
code assumes you've marshalled an IActiveScript interface pointer
to the second thread and named it pScriptEngine.
//Interrupt the script engine. You can either pass NULL
for the second
//parameter, or you can pass an initialized EXCEPINFO structure. Passing
//an uninitialized EXCEPINFO structure may result in a crash.
EXCEPINFO ei;
memset( &ei, 0, sizeof(EXCEPINFO) );
ei.scode = 0x800a3328;
ei.bstrDescription = SysAllocString( L"Joel's custom error" );
ei.bstrSource = SysAllocString(L"InterruptScriptThread" );
HRESULT hr = pActiveScript->InterruptScriptThread( SCRIPTTHREADID_BASE,
&ei, 0 );
Source: Joel Alley, Microsoft Developer Support
Do You Have
a Passion for Scripting?
Check out the Dr. Dobb's Journal Alternative & Scripting Languages
web site (http://www.ddj.com/topics/altlang/)
for more information on a variety of scripting and alternative languages.
This site changes regularly, so check back often.
Be sure to bookmark Microsoft Program Manager Andrew Clinick's Scripting
Clinick monthly column on the in's-and-out's of Active Scripting
and the new Visual Studio for Applications (VSA) .NET technology.
Looking for
back issues of the Active Scripting newsletter?
Head over to the Dr. Dobb's Journal site
(http://www.ddj.com/maillists/active/) to read any of the back issues
online. The Active Scripting newsletter has now been in publication for
over a year and you will find a wealth of information, commentary and
insight that will make using and understanding Active Scripting easier
and more productive.
Final Thoughts
I hope you will find this newsletter a timely and helpful addition to
your knowledge base about Active Scripting. Please feel free to email
me at [email protected]
and let me know what you think of it, ways to improve on content, format,
etc., and how this newsletter is helping you.
Until next month,
Cheers!
Mark
|