Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

.NET Scripting Hosts, Part Four


Scripting Chat

Of these three interfaces, the host needs to worry most about IActiveScriptSite as that is the only one of these three that the host implements itself -- the other two are handled by the scripting engine. Interestingly since JScript and VBScript are native (non-managed) blocks of code, they essentially go through COM Interop themselves when calling back into the host via IActiveScriptSite. This is handled automatically by COM Interop when the engine makes calls on methods on the interface it received when the host called IActiveScript::SetScriptSite and passed in its IActiveScriptSite interface. Something to keep in mind for a bit later.

Let's look at a basic implementation of IActiveScriptSite in C# for our host:

using System;
using System.Runtime.InteropServices;

namespace dotnet_scriptinghost
{
    [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();

    }

    public class ScriptHost : IActiveScriptSite
    {
        public ScriptHost()
        {
        }

        #region IActiveScriptSite

        public void GetDocVersionString(out string v)
        {
            throw new NotImplementedException();
        }

        public void GetItemInfo(
[In,MarshalAs(UnmanagedType.BStr)] string pstrName,
            [In,MarshalAs(UnmanagedType.U4)] uint dwReturnMask,
[Out,MarshalAs(UnmanagedType.IUnknown)] out object item,
            IntPtr ppti)
        {
            item = null;
        }

        public void GetLCID(out uint id)
        {
            throw new NotImplementedException();
        }

        public void OnEnterScript()
        {
        }

        public void OnLeaveScript()
        {
        }

        public void OnScriptError([In,MarshalAs(UnmanagedType.IUnknown)]
 object err)
        {
        }

        public void OnScriptTerminate(ref object result,
ref stdole.EXCEPINFO info)
        {
        }

        public void OnStateChange(uint state)
        {
        }

        #endregion

}
}

The definition of the IActiveScriptSite interface is the same as we looked at previously -- most the parameters are just normal .NET types but we have to take care with those typed as object to make sure they are attributed to be COM IUnknown interfaces so the marshalling layer will know what to do with them. If you neglect this crucial step, you'll see message boxes like this when you run your host:

In fact, if you see this message box while testing and debugging your .NET scripting host, it's likely you forgot some attributes somewhere on an interface. The message isn't too helpful in diagnosing this problem, but now that you know what to look for and what to do about it this part should go more smoothly -- yeah, right.

You'll notice that some of the methods throw an exception named System.NotImplementedException. This takes the place of a C++ COM method implementation returning the HRESULT E_NOTIMPL which indicates the method is not implemented to the scripting engine. The methods that throw these exceptions follow the normal Active Scripting conventions defined for this interface as to when the exception should be thrown and when it shouldn't.

The class defining the scripting host derives from the C# interface IActiveScriptSite just as a normal C++ class would derive from the IDL defined IActiveScriptSite interface. Implementing the host this way will make it easy to pass the script host IActiveScriptSite interface to the scripting engine. This brings us to the next step -- creating an instance of a scripting engine and beginning to interact with it via IActiveScript and IActiveScriptParse.

Let's create a method in the host that will be called to fire up the host in the first place:

public void Run()
{
    try
    {
        JScript engine = new JScript();
        IActiveScriptParse iap = engine as IActiveScriptParse;
        iap.InitNew();

        IActiveScript ias = engine as IActiveScript;
        ias.SetScriptSite( this );

        ias.Close();
    }
    catch( ExecutionEngineException e )
    {
        MessageBox.Show( e.Message,"dotnet_scriptinghost" );
    }
}

In the first block of code in this method, an instance of the JScript engine is created. Question is: where does the JScript engine class come from? Well, it's not defined in the .NET framework anywhere. We're going to create it ourselves using another nifty COM Interop attribute -- COM Import.

The COM Import attribute tells .NET that I want to define a class that gets its actual definition from an external COM class -- this includes any interfaces it might define. It allows us to create instances of the .NET class and interact with them as .NET objects but which are in fact COM objects. Pretty sweet. Only trick is knowing how to define the class using COM Import because it's not the most intuitive:


    [ComImport, Guid("F414C260-6AC0-11CF-B6D1-00AA00BBBB58")] 
    public class JScript  
    { 
    }

The class name itself -- in this case .JScript. -- can be anything you like. I just happened to choose the name JScript since that is the Active Scripting engine I'm wrapping with .NET. The Guid attribute is also very important. It defines the COM CLSID of the COM object that I want to wrap/create. You can get the CLSID for engines by converting COM ProgIDs to CLSIDs, or by using a tool like OleView from the Win32 SDK to inspect the CLSIDs of the engines. Either way, you'll need the GUID value to create the scripting engine just like you would in C++.

In the next couple of lines of code, I cast the newly created engine as an IActiveScriptParse interface using the C# keyword .as.. Then I call the standard IActiveScriptParse::InitNew method to initialize the engine. With the .NET wrapping of Active Scripting at a basic level now in this example, I can begin to interact with Active Scripting just as I would have in a typical C++ scripting host.

Near the end of the method, I indicate I want to catch exceptions of type System.ExecutionEngineException. These are thrown by the .NET runtime when it incurs a catastrophic error such as when problems happen with badly defined C# interfaces that mimic COM ones. It's a good general practice to handle exceptions as close to where they occur as possible since it's unlikely a caller will know what to make of this kind of exception or why it happened. For now, I'm just showing the error message in a message box -- the result of the exception getting thrown and displayed can be seen in the message box graphic earlier in this article.

The last bit of code is where I tell the engine about my IActiveScriptSite interface and how to talk to my host. Notice that since I derived my host from IActiveScriptSite in the first place, I can cast my host's this pointer to the interface and just pass it on through to the engine. Finally, I close down the scripting engine via IActiveScript::Close as usual.

If you paste this code into a new C# WinForms project in Visual Studio .NET 2003 and compile it, it should run just fine although at this point it doesn't do anything really interesting. For that, you'll have to wait until next month.

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. < / p > < p > U n t i l n e x t 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.