Channels ▼
RSS

.NET Scripting Hosts


Dr. Dobb's Active Scripting Newsletter

The three essential interfaces for developing an Active Scripting host -- whether in C++ or C# -- are IActiveScript, IActiveScriptParse (both implemented by the scripting engine), and IActiveScriptSite (implemented by the host). Last month we looked at IActiveScript and how to declare the interface in .NET. This month we'll look at the interface declarations for the other two essential interfaces.

The IActiveScriptParse interface is exposed by the scripting engine and provides support for adding script text to the engine for processing. It has a somewhat limited use in a host compared to IActiveScript, but you can't get along without it. Laying out the .NET declaration for IActiveScriptParse is similar to the steps for IActiveScript. First, let's look at how the interface appears in Visual Studio's Object Browser (basically, how COM interop sees the interface without any tweaking):


Figure: IActiveScriptParse interface declaration as seen by Object Browser

Notice that the declaration is for IActiveScriptParse32 not IActiveScriptParse. The interface name is actually IActiveScriptParse32, but the header files created by MIDL.EXE for C++ developers remap the name for Win32 development to IActiveScriptParse. For those brave souls building under 64-bit Windows, the interface would be IActiveScriptParse64 but would still map in the end to IActiveScriptParse. For my purposes, I'll just refer to it as IActiveScriptParse.

Now, let's look at the interface again but this time in C#:

    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
    Guid("BB1A2AE2-A4F9-11cf-8F20-00805F2CD064")]
    interface IActiveScriptParse
    {
        void InitNew();
        void AddScriptlet( string defaultName,    
                    string code,
                    string itemName,
                    string subItemName,
                    string eventName,
                    string delimiter,
                    uint sourceContextCookie,
                    uint startingLineNumber,
                    uint flags,
                    out string name,
                    out stdole.EXCEPINFO info);

        void ParseScriptText( 
                string code,
                string itemName,
                IntPtr context,
                string delimiter,
                uint sourceContextCookie,
                uint startingLineNumber,
                uint flags,
                IntPtr result,
                out stdole.EXCEPINFO info);
    }

As with IActiveScript, this interface derives from IUnknown, not IDispatch. The UUID specified with the Guid attribute is the actual UUID of the interface. The string parameters will get automatically converted to BSTR data types by COM interop when a call is made to the method. Finally, the methods return "void" not HRESULT since exceptions will get thrown if errors occur.

Careful readers will notice that the 3rd and 8th parameters differ from the others -- they are defined as generic System.IntPtr objects. The advantage of the IntPtr object is that we can give it a zero value as in:

    System.IntPtr p = System.IntPtr.Zero;

We can then use this zeroed value as a proxy for a classic 'C' style NULL pointer. Since some parameters for the IActiveScriptParse interface (as well as others) take NULL as a potential value, the IntPtr typed parameter makes this easy. It's also handy if we need to back pointers to non-standard objects such as structures that aren't defined (or re-defined) in C#, but I digress.

So far so good.

Next up is IActiveScriptSite which is the first and often the only interface that developers bother to implement when creating a basic, simple Active Scripting host in C++. It differs from IActiveScript and IActiveScriptParse because this interface acts as a callback site for the scripting engine to inquire how the host wishes to handle various things. This interface must be implemented by the host in order to create a valid host -- no exceptions (and no pun intended).

Here is how the IActiveScriptSite interface looks in C#:

    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
    Guid("DB01A1E3-A42B-11cf-8F20-00805F2CD064")]
    interface IActiveScriptSite
    {
        void GetLCID(out uint id);
        void GetItemInfo(    string pstrName,
                    uint dwReturnMask,
                    [Out,MarshalAs(UnmanagedType.IUnknown)] 
                        out object item,
                    IntPtr ppti);
        void GetDocVersionString(out string v);
        void OnScriptTerminate(    ref object result,
                    ref stdole.EXCEPINFO info);
        void OnStateChange(uint state);
        void OnScriptError(
            [In,MarshalAs(UnmanagedType.IUnknown)] object err);
        void OnEnterScript();
        void OnLeaveScript();
    }

Looking at this interface declaration, at first glance it seems similar -- the base type is assumed to be a COM IUnknown interface and the UUID is the value of the IActiveScriptSite interface as defined by Microsoft. The methods looks pretty much the same as before also. What gets interesting and in this case, really, really important are the two places where we are working with a COM interface that has to be marshaled -- one is in GetItemInfo and one is in OnScriptError.

Of the two, GetItemInfo is the far more interesting one.

This is the workhorse method for the host whereby the engine requests access to the interfaces for objects (COM Automation) that were previously added to the engine's namespace via a call to IActiveScript::AddNamedItem (or IActiveScript.AddNamedItem if you like the C# style better for illustrations like this). When the engine encounters a name in script that is being executed that matches up with a name that was added, it asks the host (via IActiveScriptSite.GetItemInfo) for an interface to the object (or its type library if IActiveScript.AddTypeLib was called instead).

Looking at the definition of IActiveScriptSite in the Object Browser (as seen by COM Interop), here is what we see in Visual Studio:


Figure: IActiveScriptSite interface declaration as seen by Object Browser

Notice that the object browser is showing GetItemInfo as having a parameter "out object" as the 3rd parameter. This is the parm that returns back a COM interface to the engine. If you declare the IActiveScriptSite interface in your code and try to write a host around it, here is what you'll likely see once it starts running:


Figure: Error message when using a badly defined IActiveScriptSite interface

In my case, this happened when trying to start the engine up via IActiveScript.SetScriptState(SCRIPTSTATE_CONNECTED). Basically what my GetItemInfo method returned was non-usable by the engine.

So much for the hyped "it just works" philosophy.

It works, but it needs help. If you recall from the earlier articles in this series, I mentioned that COM interop is cool, indispensable, and tricky to work with.

Next time, we'll look at the IActiveScriptParse and IActiveScriptSite interfaces as well as the initial code to constructing a .NET scripting host object.

Do you have a passion for Scripting?

If you want to access the extensive exchanges between other developers about Active Scripting, head over to Google's archive of Usenet newsgroups and read through the microsoft.public.scripting.hosting group. If you're working with the newer .NET version of scripting, you might look through microsoft.public.dotnet.scripting.

Also, if you have questions about how to use Active Scripting, I'd encourage you to read through my Active Scripting FAQ document that covers a wide range of problems other developers have had and many useful answers from their experiences. You can find it here.

Finally, a rich source of material on Active Scripting, JScript, and VBScript from a true Microsoft insider is Eric Lippert's blog.

Looking for back issues of the Active Scripting newsletter?

Head over to the Dr. Dobb's Journal site to read any of the back issues on-line. The Active Scripting newsletter has now been in publication for over 5 years 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 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, and how this newsletter is helping you.

Until next month...

Cheers!
Mark


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.