C H A P T E R  6

images

DLR Hosting API

The DLR Hosting API is a programming interface that allows one language’s code to execute in another language. For ease of discussion, I’ll refer to the language whose code is executed in another language as the guest language. The language that executes a guest language’s code will be  the host language.  

The biggest source of complexity in executing a guest language’s code is the need for sharing data between the host language and the guest language. For example, when we execute Python code in C#, we often want to pass some objects as input to the Python code. The objects might represent the object model of the software system we are developing. If that software system is a bank application, the objects we pass to the Python code might be customer accounts and the Python code might have functions that can operate on those customer accounts to print out monthly statements. You’ll see in this chapter the mechanism used by the DLR Hosting API for sharing data between host and guest languages.

To demonstrate the value of the DLR Hosting API, we’ll first take a look at how different languages host one another without the DLR Hosting API. You’ll see from these examples that without the DLR Hosting API, developers need to learn each language’s proprietary programming interface for hosting other languages. Given N languages, there can be as many as N x (N-1) ways for them to host one another. That’s quite a heavy burden for us developers to manage even a small portion of the possible combinations. To mitigate that, DLR provides a common API for hosting languages.

In this chapter, you’ll see that when you work with the DLR Hosting API, you always program to the same set of interfaces and classes defined by the API. The details specific to a guest or host language are well-encapsulated, and you don’t need to concern yourself with them. And the DLR Hosting API helps not only consumers, but also producers of programming languages. If you have designed and implemented a new programming language, you can reach out to a broader audience and make your software friendlier to use by plugging your language into the DLR Hosting API. That way, users of your language can use the DLR Hosting API to host your language in C#, IronPython, or any other .NET languages.

The DLR Hosting API consists of two smaller sets of APIs, one for language consumers and the other for language producers. The part for language producers allows new languages to be plugged into the DLR Hosting API. We will focus on the language-consumer side of the DLR Hosting API here and look at the language-producer side in more detail in Chapter 9 when we go through the design and implementation of a domain-specific language called Stitch. In that chapter, you’ll see how to implement the language producer side in order to plug the Stitch language into the DLR Hosting API.

Life Without the DLR Hosting API

First I want to demonstrate the key value of the DLR Hosting API by showing you what life is like without it. You’ll see examples for the following scenarios related to language interoperability. Some of the examples will not use the DLR Hosting API at all. Others will use it only to a limited extent.

  • Using a static language’s code in another static language. We will use VB.NET code in C#.
  • Using a static language’s code in a dynamic language. We will use C# code in IronPython and IronRuby.
  • Using a dynamic language’s code in a static language. We will use IronPython and IronRuby code in C#.
  • Using a dynamic language’s code in another dynamic language. We will use IronPython code in IronRuby.

You’ll find all of the code examples in this section in the LanguageSpecificHosting project in this chapter’s code download. The examples will give you a real feel for how cumbersome it quickly gets using proprietary APIs to host other languages. After that, you’ll see how the DLR Hosting API simplifies language hosting by providing a uniform and language-neutral API.

Using a Static Language’s Code in Another Static Language

As the first step in our exploration, let's begin by reviewing how static languages such as C# and VB.NET interoperate. Imagine there's some VB.NET code we want to use in C# code. To do this, we need to compile the VB.NET code into a .NET assembly, then, we need to (a) reference the assembly in the C# project, and (b) add using statements to import the namespaces and classes defined in the VB.NET code into the C# code. For example, if the VB.NET code defines a class called Customer in the Com.Xyz namespace, to use that class in C#, the C# code needs to have a using statement like this:

using Com.Xyz;

Without the using statement, we would have to use the fully qualified class name Com.xyz.Customer wherever we use the Customer class in the C# code. As previous chapters have mentioned, language interoperability between static .NET languages such as VB.NET and C# is possible because all the static language code is compiled into the same underlying intermediate language code that shares a common type system (CTS).

Using a Static Language’s Code in a Dynamic Language

Now, how about using a static language’s code in a dynamic language? Let's take a look. Listing 6-1 shows what’s needed for IronPython code to access C# code. The C# code, of course, needs to be compiled into a .NET assembly before it can be used by anybody. Let's assume the C# code is compiled into an assembly called Xyz.dll. Line 2 in Listing 6-1 shows that to get a hold of the C# code, the IronPython code needs to reference the Xyz.dll assembly. Referencing that assembly is equivalent to adding a reference to a VB.NET assembly in a C# project when using VB.NET code in C#. Line 3 imports the Customer class into the IronPython code, which is equivalent to importing namespaces and classes defined in VB.NET code into C#.

You can see that the two steps needed for IronPython code to get a hold of C# code are equivalent to those needed for C# code to get a hold of VB.NET code. However, even though the steps are by and large equivalent, they are not exactly the same. C# and IronPython each define their own ways for referencing assemblies and for importing classes.    

Listing 6-1. Using C# Code in IronPython

import clr
clr.AddReference("xyz.dll")
from com.xyz import Customer
# ... code that uses the Customer class.
# You can use fully qualified name com.xyz.Customer too.

Listing 6-2 shows the IronRuby way of accessing C# (or VB.NET) code. This is again a two-step process. First we need to add a reference to the compiled assembly, Xyz.dll. Next we need to import the namespaces or classes we want to use into the IronRuby code space. As Listing 6-2 shows, IronRuby uses the require keyword for adding references to compiled .NET assemblies and the include keyword for importing a namespace. Once again, even though the two steps for using other language’s code in IronRuby are equivalent, they are not exactly the same and, as IronRuby developers, we have to learn IronRuby’s specific keywords to use C# code in IronRuby.

Listing 6-2. Using C# Code in IronRuby

require 'Xyz.dll'
include Com.Xyz
# ... code that uses the Customer class

Using a Dynamic Language’s Code in a Static Language

We just looked at using a static language’s code in a dynamic language, now we’ll look at the reverse—using a dynamic language’s code in a static language. One major difference between this scenario and the previous is that dynamic language code does not need to be compiled until run time. Because dynamic language code is not compiled into an assembly at compile time, we don’t get a hold of the code by adding an assembly reference to our C# project. Instead, the C# code directly references the dynamic language source code, as in Listing 6-3.

The example in Listing 6-3 makes use of the DLR Hosting API only to a limited extent. It calls the CreateEngine static method of the IronPython.Hosting.Python class to create a script engine for running IronPython code. IronPython.Hosting.Python is an IronPython-specific class in the IronPython.dll assembly. In order for the code in Listing 6-3 to compile, we need to add a reference to the IronPython.dll assembly. Had we used the language-neutral DLR Hosting API to create the script engine, we wouldn’t need to add the reference to the C# project. The type of the script engine instance is ScriptEngine, which is a class in the DLR Hosting API. The example in Listing 6-3 uses some features of the DLR Hosting API and some features specific to the IronPython implementation. Later in this chapter, you’ll see examples that use only the DLR Hosting API.

Listing 6-3. Running IronPython Code in C#

private static void CSHostsIronPython()
{
    ScriptEngine engine = Python.CreateEngine();
    engine.Execute("print "hello"");
}

Listing 6-4 shows the same example but for the Ruby language. The code calls the CreateEngine static method of the IronRuby.Ruby class to get a script engine that is capable of running IronRuby code. IronRuby.Ruby is an IronRuby-specific class in the IronRuby.dll assembly.

Listing 6-4. Running IronRuby Code in C#

private static void CSHostsIronRuby()
{
    ScriptEngine engine = Ruby.CreateEngine();
    engine.Execute("puts "hello"");
}

Using a Dynamic Language’s Code in Another Dynamic Language

Now let’s look at an example in which a dynamic language hosts another dynamic language. Listing 6-5 shows how to host IronPython code in IronRuby using IronRuby’s specific mechanism. The IronRuby code in Listing 6-5 calls the require method of the IronRuby module to load the Python code in helloFunc.py. The Python code in helloFunc.py defines a hello function and is shown in Listing 6-6.

The IronRuby module used in Listing 6-5 is a built-in module available to all IronRuby code. If you look at the DLR source code, you can find a class called LibraryInitializer in the IronRuby project. LibraryInitializer is derived by many generated classes that take care of loading various built-in IronRuby modules and functions. All those generated classes are placed in the Initializers.Generated.cs file under the IronRuby.Libraries project. One of those generated classes is BuiltinsLibraryInitializer. If you look into the LoadModules method of BuiltinsLibraryInitializer, you will find that one of the default modules it loads is named IronRuby. The LoadModules method of BuiltinsLibraryInitializer uses the LoadIronRuby_Class method of the same BuiltinsLibraryInitializer class to define the methods belonging to the IronRuby module. One of those methods is require and from the code in the LoadIronRuby_Class method you can see that the require method is actually backed by the Require method in the IronRubyOps class. The use of the IronRuby module in Listing 6-5 is specific to the IronRuby implementation and is not language neutral. You will see that instead of using the IronRuby module to load Python code, we can use the DLR Hosting API to load one dynamic language’s code into another dynamic language.

Listing 6-5. Ruby Code Usings the hello Function Defined in helloFunc.py

pythonHello = IronRuby.require('Python/helloFunc.py')
pythonHello.hello

Listing 6-6. Python Code That Defines a hello Function.

def hello():
    """This function prints Hello."""
    print "Hello"

Overview of the DLR Hosting API

So far we’ve seen the various ways of hosting one language in another in different scenarios. Now I’ll give you an overview of the DLR Hosting API and point out the scenarios for which the DLR Hosting API provides value—only two of the four scenarios described earlier: using a dynamic language’s code in a static language and using a dynamic language’s code in another dynamic language. The DLR Hosting API doesn’t provide any feature for using a static language in another static language or for using static language code in a dynamic language. For those, you need to compile the static language code and add a reference to the compiled assembly file as illustrated in previous sections.

Major Classes in the API

The DLR Hosting API consists of two smaller sets of APIs, one for language consumers and the other for language providers. The classes and interfaces that make up the Hosting API are packaged into the Microsoft.Scripting.dll assembly. Some of the major classes that make up the consumer side of the DLR Hosting API are ScriptRuntime, ScriptScope, ScriptEngine, ScriptSource, and CompiledCode, all of which are in the Microsoft.Scripting.Hosting namespace. Here’s a brief description of each.

ScriptRuntime: This is the class defined by the DLR Hosting API to model the runtime that dynamic code runs on. Just like C# or VB.NET code runs on the CLR runtime, dynamic code also needs a runtime. The CLR runtime can run C#, VB.NET, and other language code. Similarly, a ScriptRuntime instance can run code written in different dynamic languages.

ScriptEngine: This is what ScriptRuntime uses to run code written in different dynamic languages. At run time, for each dynamic language, there needs to be a ScriptEngine instance that knows how to execute code written in that language. A script runtime (i.e., an instance of the ScriptRuntime class) usually holds a reference to a ScriptEngine instance for each dynamic language the script runtime supports. For example, if a script runtime is capable of running IronPython and IronRuby code, the script runtime must internally hold a reference to a ScriptEngine instance that’s capable of running IronPython code and a reference to another ScriptEngine instance that’s capable of running IronRuby code.

ScriptScope: This is the class that allows data sharing between guest and host languages. Earlier in this chapter, I indicated that the biggest source of complexity in executing a guest language’s code is the need for sharing data between the host language and the guest language. The ScriptScope class is defined to facilitate data sharing between different languages. You can think of a ScriptScope instance as a bag that holds named objects that can be passed around from one language to another. Different language code fetches a particular object from the bag by the object’s name and does something useful with the fetched object.

ScriptSource: This is the class defined by the DLR Hosting API to model the source code of a DLR-based language. We can call the Compile method of ScriptSource on a ScriptSource instance to compile the source code the ScriptSource instance represents. The Compile method will return an instance of CompiledCode to represent the compiled source code.

CompiledCode: This is the class defined by the DLR Hosting API to model the compiled source code of a DLR-based language. We can call the Execute method on a CompiledCode instance to execute the compiled source code.

The documentation area of the DLR CodePlex web site has a document called dlr-spec-hosting.doc that contains a wealth of information about the consumer side of the DLR Hosting API. It divides the way you can use the Hosting API into three levels. Level One is the simplest and uses the fewest features of the Hosting API. Level Two is the middle level and Level Three, the most advanced, uses the full power of the API. Although the division seems somewhat artificial, the levels are by and large a very good measure for deciding how much of the Hosting API you’d like to use in your application. To make it easy for readers who have read or will read that document to follow along, this chapter will indicate which level of the DLR Hosting API an example uses when appropriate. As you read through this chapter, please keep in mind that the three levels are just a way to gauge how much of the Hosting API is being used. They are not reflected in any way in the Hosting API itself. There is nothing in the Hosting API that restricts you to a certain level or indicates anything about the levels. If you find yourself unsure at which level you’re using the Hosting API, don’t worry. It really doesn’t matter much because the levels are just a conceptual categorization that does not have any concrete implementation behind it.

All the code examples for these levels are in the HostingExamples project. All Level One examples are in the LevelOneExamples.cs file. Similarly, all Level Two examples are in LevelTwoExamples.cs and Level Three examples in LevelThreeExamples.cs. One indication that the Hosting API is indeed language neutral is the assembly files referenced by the HostingExamples project. The project does not reference any of the IronPython or IronRuby assemblies. Those assemblies are not needed to compile the HostingExamples project because all the examples in the project are language neutral.

The Tale of Two APIs

Previously I mentioned that the DLR Hosting API consists of one API for language consumers and another API for language developers. The provider-side API is for language implementers to plug a new language into the DLR Hosting API so that consumers of the language can run code written in the language via the consumer side of the DLR Hosting API. We saw an example of implementing the provider-side API in Chapter 1 when we plugged in the Hello language into the DLR Hosting API by implementing a class that derives from LanguageContext and a class that derives from ScriptCode. For an API like the DLR Hosting API that allows new providers to be plugged in, it is not uncommon to see the existence of a provider API and a consumer API. For example, the Java Naming and Directory Interface (JNDI) defines a provider API called service provider interface (SPI). There are vendors who have implemented the SPI to provide naming and directory service for LDAP servers, database servers, DNS servers, and so forth. Consumers of JNDI will interact with the various naming and directory services via the consumer-side API and be kept unaware of whether the underlying provider is an LDAP server, database server, or DNS server.  

The classes in the provider side and the consumer side of the DLR Hosting API have a nice correspondence. Table 6-1 shows which consumer-side class corresponds to which provider-side class. The consumer-side classes in the DLR Hosting API are really just a thin wrapper around the provider-side classes. We’ll discuss only the consumer-side API now. You can find examples that implement the provider-side API in Chapters 9 and 11.

Table 6-1. Correspondence Between Provider-Side Classes and Consumer-Side Classes

Consumer-side Class Provider-side Class
ScriptRuntime ScriptDomainManager
ScriptEngine LanguageContext
CompiledCode ScriptCode
ScriptSource SourceUnit
ScriptScope Scope

The DLR Hosting API in Relation to Binders and Dynamic Objects

As part of the overview of the DLR Hosting API, I want to touch on the relation between the DLR Hosting API and language interoperability. We looked at language interoperability in the previous two chapters, where we saw that language interoperability in the DLR is made possible with binders and dynamic objects. Those two chapters and their code examples show that once the binders and dynamic objects are in place, they follow a well-established protocol in their interactions in order to enable language interoperability.

What's not really explained in those chapters is how the binders and dynamic objects come together in the first place. For example, Chapter 4 uses Python objects in C# code and explains how the Python objects interoperate with C# code. (In such scenarios, we call C# the host language and IronPython the source language). Once the C# code obtains a reference to a Python object, the C# code can call methods on the Python object and those methods can return other Python objects for the C# code to use.

But how does the C# code obtain a reference to the first Python object? The first reference doesn’t need to be a reference to a Python object. It can be a reference to a Python class or a Python function. In that case, the C# code can use the Python class to create the first Python object or invoke the Python function to get back a Python object as the return value. Regardless of whether the initial item is a Python object, class, or function, the C# code needs a way to get it.

If Python were like VB.NET, the C# code would get that initial Python object, class, or function by referencing the assembly the VB.NET is compiled into. Because Python code is not compiled at compile time, instead of referencing a compiled assembly, the C# code has to reference the Python source code and be able to compile the source code at run time. That is where the DLR Hosting API comes into the picture.

You’ll learn through this chapter how to use the DLR Hosting API to get a hold of, say Python objects in C# code.  You can combine that knowledge with what you learned in the previous two chapters and get the whole picture of how languages interoperate.

Using Script Runtime to Execute Code

With that high-level overview of the DLR Hosting API under your belt, let’s start to use the DLR Hosting API. This section will show you how to use the script runtime to execute DLR-based language code. The examples in this section will make Level One use of the DLR Hosting API and are in the LevelOneExamples.cs file. At this level, we use ScriptRuntime to execute dynamic language code stored in source files. The code can be IronPython, IronRuby, or other DLR-based language code. As long as the language implements the provider part of the Hosting API, we can use ScriptRuntime to execute its code. Let’s look at some examples. Listing 6-7 shows how to use ScriptRuntime to run an IronPython source file, which is the helloFunc.py in Listing 6-6.

Listing 6-7. Using ScriptRuntime to Load and Interpret Python Code

1)  public static void GetPythonFunctionUsingScriptRuntime()
2)  {
3)      ScriptRuntime scriptRuntime = ScriptRuntime.CreateFromConfiguration();
4)      ScriptScope scope = scriptRuntime.ExecuteFile(@"PythonhelloFunc.py");
5)      Action hello = scope.GetVariable<Action>("hello");
6)      hello();
7)      scriptRuntime.Shutdown();
8)  }  

The code in Listing 6-7 creates a script runtime in line 3 based on the information in the App.config file. We will look at the App.config file in detail shortly. For now, let’s just think of it as a black box that has in it the information needed for creating a script runtime. In Listing 6-7, the code calls the ExecuteFile method on the script runtime and passes in the name of the Python source file as the input parameter. That causes the Python code in the helloFunc.py file to be parsed and evaluated. The result of running the Python code is stored in the return value of the ExecuteFile method call.

The return value of the ExecuteFile method call is a script scope, an instance of the ScriptScope class. As described earlier in this chapter, a script scope is a container that holds objects we want to share across different languages. In this case, it is anticipated that the reason we call the ExecuteFile method is to get the result of the Python code execution. So the ExecuteFile method puts the result of executing the Python code into a script scope and returns that script scope back to the caller. Because the file helloFunc.py contains only the definition of a Python function called hello, the result of executing helloFunc.py is an instance of IronPython.Runtime.PythonFunction stored in the script scope against the name “hello”. That’s why line 5 in Listing 6-7 retrieves the Python function by its name “hello”. Line 5 also converts the Python function of type IronPython.Runtime.PythonFunction to a .NET delegate of type Action. Because of the type conversion, line 6 can invoke the Python function like calling a normal .NET delegate.  

One interesting thing to note about the ScriptScope class is that it implements the IDynamicMetaObjectProvider interface. If you’ve read Chapter 5, you know that means instances of ScriptScope are dynamic objects. The late binding logic for ScriptScope is implemented in such a way that we can fish out a variable as if the variable’s name is a property of the scope. Listing 6-8 shows an example of this use of ScriptScope. If you compare the bolded code in Listing 6-8 to line 5 in Listing 6-7, you’ll see the difference. The code in Listing 6-8 is more succinct and readable and that’s the purpose of having ScriptScope implement the IDynamicMetaObjectProvider interface.

Listing 6-8. Fish Out a Variable as if the Variable’s Name is a Property of the Scope

public static void UseScriptScopeAsDynamicObject()
{
    ScriptRuntime scriptRuntime = ScriptRuntime.CreateFromConfiguration();
    dynamic scope = scriptRuntime.ExecuteFile(@"PythonhelloFunc.py");
    Action hello = scope.hello;
    hello();
    scriptRuntime.Shutdown();
}

Configuring the Languages You Want to Speak

In the previous section, the code examples use the CreateFromConfiguration static method of the ScriptRuntime class to create a script runtime. Behind the scenes, CreateFromConfiguration will read the configurations in the application’s App.config file and use them to create the script runtime. The DLR-related configurations in an App.config file determine which dynamic languages will be supported by the script runtime. There are two ways to configure which languages will be supported by a script runtime. One way is to use App.config and the CreateFromConfiguration static method, as we’ve been discussing. This approach is declarative and does not require us to recompile our application should we want to support a new language in the script runtime. The other approach is to use classes such as ScriptRuntimeSetup and LanguageSetup to programmatically configure which languages will be supported in the script runtime under construction. Let’s look at the two approaches in turn.

Configuring Script Runtime Declaratively

Let’s start with the declarative approach for configuring which languages are supported in a script runtime. Listing 6-9 shows what the DLR-related configurations in App.config look like. App.config is .NET’s mechanism for configuring an application in general. There can be different kinds of configurations in App.config for different parts of an application. For example, an App.config for a distributed application built on .NET Remoting might contain configurations related to .NET Remoting.

Each kind of configurations in an App.config file is specific to a particular aspect of the application and needs to be interpreted accordingly. The way to specify how to interpret a certain kind of configuration is by using a <section> element. In our case, the App.config file shown in Listing 9-6 contains information specific to the DLR Hosting API. So we have a <section> entry under the <configSections> element that specifies that the class Section in the Microsoft.Scripting.Hosting.Configuration namespace should be used to interpret the DLR Hosting API related configurations. The type attribute of the section element specifies the assembly-qualified name of the Section class in the Microsoft.Scripting.Hosting.Configuration namespace. The configurations shown in Listing 6-9 are only valid if you use the version 1.0.0.0 release bits of the Microsoft.Scripting.dll assembly. If you use a debug version of Microsoft.Scripting.dll that you build yourself, you’ll have to adjust the configurations by typing in the correct assembly-qualified name of the Section class.

The configurations the Microsoft.Scripting.Hosting.Configuration.Section class will interpret are all lines enclosed by <microsoft.scripting> and </microsoft.scripting>. Those lines basically specify that we want the script runtime created by the CreateFromConfiguration static method of the ScriptRuntime class to support two languages—IronPython and IronRuby. For each language, we need to tell the DLR Hosting API the language’s name, file extension, display name, and most importantly, the assembly-qualified name of a class that derives from the LanguageContext class in the Microsoft.Scripting.Runtime namespace. For IronPython, the class that derives from the LanguageContext class is PythonContext. For IronRuby, the class that derives from the LanguageContext class is RubyContext. LanguageContext is an important class on the provider side of the DLR Hosting API. A language implementer needs to derive a class from LanguageContext and provide language-specific behaviors in the derived class if he or she wants to plug the language into the DLR Hosting API. Again, the configurations shown in Listing 6-9 for IronPython and IronRuby are valid only if you use the release bits of IronPython 2.6.1 and IronRuby 1.0. If you use different versions or if you build the binaries yourself, you will have to adjust the configurations by typing in the correct assembly-qualified names of the PythonContext and RubyContext classes.

Listing 6-9. DLR-Related Configurations in App.config

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="microsoft.scripting"
type="Microsoft.Scripting.Hosting.Configuration.Section,
        Microsoft.Scripting, Version=1.0.0.0, Culture=neutral,
PublicKeyToken=31bf3856ad364e35" />
  </configSections>

  <microsoft.scripting>
    <languages>
      <language names="IronPython,Python,py"
                extensions=".py"
                displayName="IronPython 2.6.1"
                type="IronPython.Runtime.PythonContext,IronPython, Version=2.6.10920.0,
                Culture=neutral, PublicKeyToken=31bf3856ad364e35" />

      <language names="IronRuby;Ruby,rb"
                extensions=".rb"
                displayName="IronRuby 1.0"
                type="IronRuby.Runtime.RubyContext, IronRuby, Version=1.0.0.0,
                Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
    </languages>
  </microsoft.scripting>
</configuration>

Configuring Script Runtime Programmatically

After seeing the declarative approach for configuring which languages are supported in a script runtime, let’s look at the programmatic approach that achieves the same thing. As a matter of fact, the declarative approach discussed in the previous section is built on top of the programmatic approach. Once you know how the programmatic approach works, you know how the declarative approach works behind the scenes.

Listing 6-10 shows a code example (in LevelThreeExamples.cs) that uses the ScriptRuntimeSetup and LanguageSetup classes to construct a script runtime. The example first creates an instance of LanguageSetup for the IronPython language. The input parameters passed to the constructor of LanguageSetup are essentially the same pieces of information we saw in the App.config file shown in Listing 6-9. Once the LanguageSetup instance is ready, we add it to an instance of ScriptRuntimeSetup and then pass the ScriptRuntimeSetup instance to the constructor of ScriptRuntime. The constructor of ScriptRuntime will use the ScriptRuntimeSetup instance we pass to it to create a script runtime that supports the IronPython language. Therefore, we can use the script runtime to execute the Python code in hello.py as the example in Listing 6-10 shows.

Listing 6-10. An Example That Creates a Script Runtime Programmatically

public static void RunScriptRuntimeSetupExample()
{
    LanguageSetup pythonSetup = new LanguageSetup(
        typeName: "IronPython.Runtime.PythonContext,IronPython, Version=2.6.10920.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35",
        displayName: "IronPython",
        names: new String[] { "IronPython", "Python", "py" },
        fileExtensions: new String[] { ".py" });

    ScriptRuntimeSetup setup = new ScriptRuntimeSetup();
    setup.LanguageSetups.Add(pythonSetup);
    ScriptRuntime scriptRuntime = new ScriptRuntime(setup);
    ScriptScope scope = scriptRuntime.ExecuteFile(@"Pythonhello.py");
}

The code in Listing 6-10 shows the information for creating a LanguageSetup instance for the IronPython 2.6.1 release version. If you want to create a LanguageSetup instance for the IronRuby 1.0 release version, use the following code:

LanguageSetup rubySetup = new LanguageSetup(
    typeName: "IronRuby.Runtime.RubyContext, IronRuby,
        Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35",
    displayName: "IronRuby",
    names: new String[]{"IronRuby", "Ruby", "rb"},
    fileExtensions: new String[]{".rb"});
    

Like Listing 6-9, the code in Listing 6-10 does not require any reference to IronPython assemblies at compile time. Another way to create a LanguageSetup instance for IronPython is to call the CreateLanguageSetup static method of the IronPython.Hosting.Python class like this:

LanguageSetup pythonSetup = Python.CreateLanguageSetup(new Dictionary<string, object>());

As you can see, using the CreateLanguageSetup static method to create a LanguageSetup instance doesn’t require us to find out the assembly-qualified name of the PythonContext class. No matter which version of the IronPython.dll assembly we use, the CreateLanguageSetup static method will return the correct LanguageSetup instance for that IronPython.dll assembly. The empty dictionary object passed to the CreateLanguageSetup method in the code snippet above is supposed to contain IronPython-specific options for controlling the IronPython script engine you create with the pythonSetup variable. Similar to IronPython, IronRuby comes with the IronRuby.Ruby class that provides a static method CreateRubySetup. The CreateRubySetup method returns a LanguageSetup instance for the IronRuby.dll assembly you reference in your project. Using IronRuby.Ruby and IronPython.Hosting.Python to create LanguageSetup instances requires adding references to the IronPython.dll and IronRuby.dll assemblies at compile time. Once you get those LanguageSetup instances, you can call their TypeName property to get the assembly-qualified names of the PythonContext and RubyContext classes.

Scripting an Object Model

So far we have seen examples that use the ScriptRuntime class to execute Python code in a C# application. Those examples are simple and don’t involve the host language passing data to the guest language code. The main usage scenario that the designers of the DLR have for Level One use of the Hosting API is to have the host language pass an object model to the guest language code. The idea is to let the guest language code do something useful with the object model. To show a more realistic example, Listing 6-11 demonstrates this main Level One usage scenario. In this example, I use a simple Customer class to represent the object model. In reality, the object model can be something as complex as the domain model of a banking system containing classes such as Customer, Account, BankStatement, etc.

The example in Listing 6-11 first creates an instance of the Customer class. The Customer class is implemented in C# and its code is shown in Listing 6-12. There are several ways to pass the Customer instance to the guest language code. A script runtime maintains a global script scope. The example in Listing 6-11 uses that to pass objects from a host language to a guest language. Besides using a script runtime’s global script scope, we can use a language-neutral script scope or a language-specific script scope to pass objects from one language to another. You will see examples of these in later sections.

The ScriptRuntime class defines a member property called Globals whose type is ScriptScope and represents the global script scope of a ScriptRuntime instance. The example in Listing 6-11 puts the Customer instance into the script runtime’s global script by calling the SetVariable method of ScriptScope. The Customer instance is associated with the name “customer” in the global script scope. Later, when we need to retrieve the Customer instance out from the global script scope, we need to get it by the name “customer”.

Listing 6-11. Passing an Object Model to Guest Language Code Using the Script Runtime’s Global Scope

public static void PassObjectModelToScript()
{
    Customer customer = new Customer("Bob", 30);
    ScriptRuntime scriptRuntime = ScriptRuntime.CreateFromConfiguration();
    scriptRuntime.Globals.SetVariable("customer", customer);
    ScriptScope scope = scriptRuntime.ExecuteFile(@"PythonsimpleWpf2.py");
}

Listing 6-12. The Customer Class

public class Customer
{
    public int Age { get; set; }
    public String Name { get; set; }

    public Customer(String name, int age)
    {
        this.Name = name;
        this.Age = age;
    }

    public override string ToString()
    {
        return Name + " is " + Age + " years old.";
    }
}

Let’s now see how the guest language code uses the Customer instance that we put into the script runtime’s global scope. The guest language code in this example is the IronPython code stored in simpleWpf2.py and shown in Listing 6-13. The IronPython code basically retrieves the Customer instance from the script runtime’s global scope and displays the customer’s information in a WPF (Windows Presentation Foundation) application. Don’t worry if you aren’t familiar with WPF. My choice of WPF as the UI framework for this example is simply because I want to show something different from the console applications we’ve been using so far.

The key line of code in Listing 6-13 is bolded. The code import customer is how we retrieve an object associated with the name “customer” from a script runtime’s global scope. After this code, the variable customer becomes available in the Python code and we simply display the customer’s information by setting the result of customer.ToString() to a WPF label’s Content property.

Listing 6-13. IronPython Code in simpleWpf2.py

import clr
import customer
clr.AddReference("PresentationFramework")
clr.AddReference("PresentationCore")

from System.Windows import (Application, Window)
from System.Windows.Controls import (Label, StackPanel)
window = Window()
window.Title = "Simple Python WPF Example"
window.Width = 400
window.Height = 300

stackPanel = StackPanel()
window.Content = stackPanel

customerLabel = Label()
customerLabel.Content = customer.ToString()
customerLabel.FontSize = 20
stackPanel.Children.Add(customerLabel)

app = Application()
app.Run(window)

This example passed objects from a host language to a guest language using the script runtime’s global scope. Next we’ll look at script scopes in more detail.

Script Scopes

There are different kinds of script scopes, but they’re all instances of the ScriptScope class. ScriptScope has a property called Engine. If a script scope’s Engine property is set to a particular language’s script engine, the script scope is bound to that particular language and I’ll refer to such script scope as engine scope. On the other hand, if a script scope’s Engine property is not set to a particular language’s script engine, then the script scope is said to be language neutral and I’ll refer to such script scope as language-neutral scope. Why is the distinction between language-neutral scopes and engine scopes important? Let me show you some examples that demonstrate the difference between language-neutral scopes and engine scopes. After the examples, I’ll explain how script scopes retrieve objects by name.

Listing 6-14 shows an example (in LevelTwoExamples.cs) that creates a script scope by calling the CreateScope method on a script runtime. The script scope created this way is language neutral. So when the code in Listing 6-14 asks the script scope whether it contains an object by the name “__doc__”, the script scope will say no. If you run the code in Listing 6-14, you will see the string “scope does not contain __doc__ variable” printed on the screen.

Listing 6-14. Example of a Language-Neutral Scope

public static void RunLanguageNeutralScopeExample()
{
    ScriptRuntime runtime = ScriptRuntime.CreateFromConfiguration();
    ScriptScope scope = runtime.CreateScope();
    if (!scope.ContainsVariable("__doc__"))
        Console.WriteLine("scope does not contain __doc__ variable.");
}

Now, for comparison, let’s see an example of a language-bound scope. Listing 6-15 shows such an example, which you can find in LevelTwoExamples.cs. The code in Listing 6-15 does not use the script runtime to create a script scope. Instead, it uses the script runtime to get the IronPython script engine and then calls the CreateScope method on the script engine to create a script scope bound to the IronPython script engine. Because the script scope is bound to the IronPython script engine, when we asks the script scope whether it contains an object by the name “__doc__”, the script scope will say yes.

Listing 6-15. Example of a Language-Bound Scope

public static void RunPythonEngineScopeExample()
{
    ScriptRuntime runtime = ScriptRuntime.CreateFromConfiguration();
    ScriptEngine pyEngine = runtime.GetEngine("python");
    ScriptScope scope = pyEngine.CreateScope();

    if (scope.ContainsVariable("__doc__"))
        Console.WriteLine("scope contains __doc__ variable.");

    String docString = scope.GetVariable("__doc__");
    Console.WriteLine("doc string is {0}", docString);
}

So why does a script scope behave differently depending on whether and which language’s script engine it is bound to? Recall that the major responsibility of a script scope is to serve as a container that carries objects from one language to another language. When we try to retrieve an object from a script scope by name, the script scope if language-bound will use the language’s binders to bind the name we request to an object in the scope. In the example in Listing 6-15, because the script scope is bound to IronPython’s script engine, when we ask for “__doc__”, the script scope will internally create a call site and use IronPython’s GetMember binder to bind the name “__doc__”. Because the Python language associates the __doc__ attribute with every object, IronPython’s GetMember binder will bind the name “__doc__” to the __doc__ attribute of the script scope.

The global scope of a script runtime we looked at in the previous section is a language-neutral scope. To prove that to yourself, you can run the following code in debug mode:

ScriptRuntime scriptRuntime = ScriptRuntime.CreateFromConfiguration();

            

In the debugger, if you examine scriptRuntime.Globals.Engine, you’ll see that the value of its LanguageContext property is an instance of the Microsoft.Scripting.Runtime.InvariantContext class. That shows that a script runtime’s global scope is not bound to any particular language’s script engine.

So far in this chapter, you have learned the different kinds of script scopes. Next, let’s see what differences it makes when we use those different kinds of script scopes to pass objects by value or by reference from one language to another.

Value and Reference Variables

One important thing to pay attention to when using script scopes to pass objects from one language to another is whether the objects are passed by value or by reference. Passing objects from one language to another is in a way like passing objects to a method. When passing objects to a method, you need to know whether the method has any chance of mutating those objects. If the objects are passed by value to a method, the objects will not be mutated no matter what the method does with those objects inside its method scope. On the other hand, if the objects are passed by reference, they will be mutated if the method assigns new values to the objects’ properties. The distinction between passing an object by value and by reference turns out to be important too in the case of script scopes. And with script scopes, the distinction between passing objects by value and by reference is slightly more complicated because there are different types of script scopes. In this section we will look at the different scopes and see the effects they have on the objects passed from one language to another.

Global Scope and Variable Passing

Let’s first see an example that passes an integer and a Customer instance from C# code to Python code using a global scope. Listing 6-16 shows a C# example that puts a Customer instance and the integer 2 into the script runtime’s global scope (in LevelOneExamples.cs). In the global scope, the Customer instance is associated with the name “customer” and the integer is associated with the name “x”. Those two objects “customer” and “x” are used by the Python code in the simply2.py file, shown in Listing 6-17.

Listing 6-16. Passing a Customer Object and an Integer to Python Code Using a Global Scope

public static void PassObjectsToPythonViaGlobals()
{
    ScriptRuntime scriptRuntime = ScriptRuntime.CreateFromConfiguration();
    Customer customer = new Customer("Bob", 30);
    scriptRuntime.Globals.SetVariable("x", 2);
    scriptRuntime.Globals.SetVariable("customer", customer);

    //Changing the value of x in Python code will not change the x in Globals.
    //This is because x is a value type and also the new x in Python code
    //is not put back into Globals.
    ScriptScope resultScope = scriptRuntime.ExecuteFile(@"Pythonsimple2.py");

    int x = scriptRuntime.Globals.GetVariable("x");
    Console.WriteLine("x is {0}", x);
    Console.WriteLine("Bob's age is {0}", customer.Age);

    Console.WriteLine("Items in global scope: ");
    DumpScriptScope(scriptRuntime.Globals);
            
    Console.WriteLine("Items in the returned scope: ");
    DumpScriptScope(resultScope);
    scriptRuntime.Shutdown();
}

private static void DumpScriptScope(ScriptScope scope)
{
    foreach (var item in scope.GetItems())
        Console.WriteLine("{0} : {1}", item.Key, item.Value);
}

Listing 6-17. Python Code That Uses the Customer Instance and an Integer in the Script Runtime’s Global Sope

import x
import customer

print x
customer.Age = 20
x = 8
print x

As you can see from Listing 6-17, the Python code retrieves the “customer” and “x” objects from the script runtime’s global scope, prints the value of x and then mutates the Age property of the customer as well as the value of x. The original value of x in the Python code is 2. After the mutation, the value of x becomes 8. Now here’s the interesting question. What do you think the value of x and the customer’s age would be in the global scope after the Python code finishes execution? To show you the answer to the question, the code in Listing 6-16 prints out the value of x and the customer’s age property in the global scope after the execution of the Python code. It turns out that the value of x in the global scope is still 2 and the customer’s age is changed to 20. Now if you notice, in Listing 6-16, the method ExecuteFile of ScriptRuntime returns a script scope, which is assigned to the variable resultScope. The code in Listing 6-16 calls the DumpScriptScope method to print out all the objects contained in resultScope. If you run the code in Listing 6-16, you’ll see that resultScope contains an object whose name is “x” and value is 8. You will also see that resultScope contains an object whose name is “customer” and value is the same Customer instance contained in the global scope.

Because the type of x is a .NET value type, the x in the global scope is passed by value to the Python code. So when we mutate the x in Python code, we are mutating a copy of the original x in the global scope. After the Python code finishes execution, the x is not put back into the script runtime’s global scope but into the new script scope returned by the ExecuteFile method. So the global scope still binds the name “x” to the original integer value 2 while the new script scope returned by the ExecuteFile method binds the name “x” to 8. In the next section you’ll see an example where we use a different script scope from the global scope and the x mutated in Python code is automatically put back into the script scope by the DLR Hosting API. If the object we pass to the Python code is an object whose type is a reference type like the customer object in our example, the object is passed by reference. So when we mutate the object in the Python code, we will see those changes made to the original object in the global scope after the Python code finishes execution.

Language Neutral Scope and Variable Passing

The previous example shows what happens when using a global scope to pass variables by value and by reference. Now let’s see what happens when we use a language-neutral scope to pass variables by value and by reference. Listing 6-18 shows the code example of this section (located in LevelTwoExamples.cs). The code example creates a language-neutral scope in line 6 and assigns it to the neutralScope variable. Then in lines 8 and 9, the code example puts a Customer instance and the integer 2 into the language-neutral scope. I purposely kept the code in Listing 6-18 similar to the code in Listing 6-16 so you can easily compare them. In line 11 of Listing 6-18, we create an IronPython script engine. In line 16, we use that script engine to execute the Python code stored in the simple1.py file, shown in Listing 6-19. Notice that in line 16, we tell the script engine to use the language-neutral scope when it executes the Python code. Also note that in line 16, after the execution of Python code finishes, ExecuteFile returns a script scope, which we assign to the resultScope variable. An interesting thing happens in line 19 where we compare the object identities of resultScope and neutralScope. It turns out that resultScope and neutralScope point to the same object in the managed heap. In other words, resultScope and neutralScope are one and the same. This is different from the example we saw in Listing 6-16. In Listing 6-16, the script scope returned by the ExecuteFile method of the script runtime is not the same object as the script runtime’s global scope.

Now if we print out the contents of the language neutral-scope as line 23 and 24 in Listing 6-18 do, we will see that the language-neutral scope’s customer variable has its Age property set to 20 and the x variable in the language-neutral scope is set to 8. This is another place where this example is different from the example in Listing 6-16. In this example, even though the variable x in the language-neutral scope has a value type and is passed to the Python code by value, in the end the variable x in the language-neutral scope is mutated by the Python code because after the Python code finishes execution, the new value of x is put back into the language-neutral scope.

Listing 6-18. Passing a Customer Object and an Integer to Python Code Using a Language-Neutral Scope

1)  public static void PassObjectsToPythonViaLanguageNeutralScope()
2)  {
3)      Customer customer = new Customer("Bob", 30);
4)
   5)      ScriptRuntime scriptRuntime = ScriptRuntime.CreateFromConfiguration();
6)      ScriptScope neutralScope = scriptRuntime.CreateScope();
7)
   8)      neutralScope.SetVariable("x", 2);
9)      neutralScope.SetVariable("customer", customer);
10)
   11)      ScriptEngine scriptEngine = scriptRuntime.GetEngine("python");
12)
   13)      //Changing the value of x in Python code will change the x in the neutral scope
14)      //even if x is a value type object. This is because x is put back into the
15)      //neutral scope.
16)      ScriptScope resultScope = scriptEngine.ExecuteFile(
17)                                     @"Pythonsimple1.py", neutralScope);
18)
   19)      if (resultScope == neutralScope)
20)          Console.WriteLine("Result scope and neutral scope are the same object.");
21)
   22)      Console.WriteLine("Items in the neutral scope: ");
23)      foreach (var item in neutralScope.GetItems())
24)          Console.WriteLine("{0} : {1}", item.Key, item.Value);
25)  }

Listing 6-19. Python Code That Uses the Customer Instance and an Integer in a Language-Neutral Scope

print x
customer.Age = 20

x = 8
print x

There is one more difference between using a global scope and using a language-neutral scope. When we use a global scope to pass objects to Python code, the Python code needs to use import statements like import customer to retrieve those objects. In contrast, when we use a language-neutral scope to pass objects to Python code, the Python code can use those objects directly with no need for the import statements, as Listing 6-19 shows.

You might notice that we discussed the effects of using a global scope and a language-neutral scope on variable passing, but I didn’t say anything about engine scopes. That’s because engine scope is the same as language-neutral scope with regard to variable passing.

In summary, when passing an object using a script scope, it matters whether the object’s type is a value type or a reference type. It also matters whether the script scope is global or language-neutral. Here is a summary of the important facts the code examples in Listing 6-16, Listing 6-17, Listing 6-18 and Listing 6-19 demonstrate:

  • When we call the ExecuteFile method on a script runtime to execute guest language code, the script runtime’s global scope is used to pass objects to the guest language code. The script scope returned by the ExecuteFile method is a different object from the global scope.
  • When we call the ExecuteFile method on a script engine and pass in a language-neutral scope to execute guest language code, we get back a result script scope. The result script scope and the language-neutral scope are one and the same.
  • If the type of an object is a reference type, it does not matter whether we use a global scope or a language-neutral scope to pass the object to the guest language code. Changes made to the object by the guest language code will be available in the script scope.
  • If the type of an object is a value type and we use a global scope to pass the object to the guest language code, changes made to the object by the guest language code will not be put back into the global scope.
  • If the type of an object is a value type and we use a language-neutral scope to pass the object to the guest language code, changes made to the object by the guest language code will be put back into the language-neutral scope.

Level Two Use of the DLR Hosting API

So far in this chapter, we’ve seen several examples relating to script scope, script runtime, and script engine. Only a few of them are making Level Two use of the DLR Hosting API. At Level Two, we can compile the source code of a DLR-based language into an instance of CompiledCode and execute that CompiledCode instance multiple times possibly in different script scopes. We can execute a ScriptSource instance that represents the source code of a DLR-based language without first compiling that source code. We can load assemblies into a script runtime and those assemblies will be available to all the DLR-based language code snippets that run on the script runtime. We can use a class called ObjectOperations to perform various operations, such as object creation and member access on an object. Don’t worry if you’re not clear what those Level Two uses of the DLR Hosting API are. In the next few sections, you’ll see examples and detailed descriptions.

Compiling Code

If you have guest language code that you need to execute multiple times either in the same script scope or in different script scopes, it’s far more efficient if you compile the guest language code once and then execute the compiled code as many times as you like. The ScriptEngine class provides a method called CreateScriptSourceFromFile, which lets you create an instance of ScriptSource that represents the guest language code you want to execute repeatedly. You then compile the guest language code by calling the Compile method on the ScriptSource instance. The Compile method returns an instance of CompiledCode that represents the compiled guest language code. With that instance of CompiledCode, you can call its Execute method to execute the compiled guest language code. The Execute method takes a script scope as its input parameter, which allows you to execute the compiled guest language code repeatedly in different script scopes. Listing 6-20 shows an example that compiles the Python code in simple1.py and executes the compiled code in an engine scope. The Python code in simple1.py was shown in Listing 6-19.

Listing 6-20. An Example that Executes Compiled Python Code

public static void RunCompiledCodeExample()
{
    ScriptEngine pyEngine = ScriptRuntime.CreateFromConfiguration().GetEngine("python");
    ScriptSource source = pyEngine.CreateScriptSourceFromFile(@"Pythonsimple1.py");

    ScriptScope scope = pyEngine.CreateScope();
    scope.SetVariable("x", 2);
    scope.SetVariable("customer", new Customer("Bob", 30));

    CompiledCode compiledCode = source.Compile();
    compiledCode.Execute(scope);
}

Listing 6-20 uses the CreateScriptSourceFromFile method of ScriptEngine to create a ScriptSource instance that represents the code in simple1.py. If the Python code we want to execute is in a string object, we can call the CreateScriptSourceFromString method of ScriptEngine and pass in the string object to create a ScriptSource instance that represents the Python code. Listing 6-21 shows such an example. The Python code in Listing 6-21 is in the pyFunc string object. The Python code defines a function that takes a number and returns true if the number is odd. The example in Listing 6-21 compiles the Python code and executes the compiled code in an engine scope. The result of the execution is fetched from the engine scope and converted to a delegate of type Func<int, bool> called IsOdd. Finally the code prints out the result of invoking IsOdd with the number 3.

Listing 6-21. Compile and Invoke a Python Function in C#

public static void CallPythonFunctionFromCSharpUsingCompiledCode()
{
    ScriptEngine pyEngine = ScriptRuntime.CreateFromConfiguration().GetEngine("python");
    string pyFunc = @"def isodd(n): return 1 == n % 2;";
    ScriptSource source = pyEngine.CreateScriptSourceFromString(pyFunc,
        SourceCodeKind.Statements);
    CompiledCode compiledCode = source.Compile();
    
    ScriptScope scope = pyEngine.CreateScope();
    compiledCode.Execute(scope);
    Func<int, bool> IsOdd = scope.GetVariable<Func<int, bool>>("isodd");
    bool result = IsOdd(3);
    Console.WriteLine("Is 3 an odd number? {0}", result);
}

After we create a ScriptSource instance in Listing 6-21, we don’t necessarily need to call the Compile method on the ScriptSource instance in order to execute the Python code. The ScriptSource class defines an Execute method that we can call to execute the source code represented by a ScriptSource instance without compiling the source code first. The Execute method takes a script scope as input so that we can execute the source code represented by a ScriptSource instance in the script scope we want. Listing 6-22 shows an example that uses the Execute method of ScriptSource to run a Python code snippet.

Listing 6-22. Using the Execute Method of ScriptSource to Run a Python Code Snippet

public static void CallPythonFunctionFromCSharpUsingScriptSource()
{
    ScriptEngine pyEngine = ScriptRuntime.CreateFromConfiguration().GetEngine("python");
    string pyFunc = @"def isodd(n): return 1 == n % 2;";
    ScriptSource source = pyEngine.CreateScriptSourceFromString(pyFunc,
        SourceCodeKind.Statements);
    ScriptScope scope = pyEngine.CreateScope();
    source.Execute(scope);
    Func<int, bool> IsOdd = scope.GetVariable<Func<int, bool>>("isodd");
    bool result = IsOdd(3);
    Console.WriteLine("Is 3 an odd number? {0}", result);
}

Loading Assemblies into Script Runtime

Earlier in this chapter, we saw the code in simpleWpf2.py, in Listing 6-13. The code contains the following two lines in order to reference the PresentationFramework.dll and PresentationCore.dll assemblies.

clr.AddReference("PresentationFramework")
clr.AddReference("PresentationCore")

If we have many source code files that need to reference those two WPF assemblies, instead of having each file add the assembly references, we can load the assemblies into a script runtime and use the script runtime to execute the source code files. The assemblies loaded into the script runtime will be available to all the source code files and, therefore, they won’t need to add the assembly references. Listing 6-23 shows how to load assemblies into a script runtime.

To load the PresentationCore.dll assembly into a script runtime, the example in Listing 6-23 calls the static Load method of the Assembly class to create an Assembly instance that represents the PresentationCore.dll assembly. The example calls the LoadAssembly method on the script runtime with the Assembly instance to load the PresentationCore.dll assembly into the script runtime. The example then repeats the same steps to load the PresentationFramework.dll assembly into the script runtime. Finally, the example uses the script runtime to execute the Python code in simpleWpf.py, which is shown in Listing 6-24. Note that in Listing 6-24, the Python code does not need to have any of the clr.AddReference("PresentationCore") and clr.AddReference("PresentationFramework") statements for adding references to PresentationCore.dll and PresentationFramework.dll.

Listing 6-23. Load WPF Assemblies into a Script Runtime

public static void LoadAssembliesIntoScriptRuntime()
{
    ScriptRuntime scriptRuntime = ScriptRuntime.CreateFromConfiguration();
    scriptRuntime.LoadAssembly(Assembly.Load("PresentationCore, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"));
    scriptRuntime.LoadAssembly(Assembly.Load("PresentationFramework, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"));
    ScriptScope scope = scriptRuntime.ExecuteFile(@"PythonsimpleWpf.py");
}

Listing 6-24. simpleWpf.py

import clr
from System.Windows import (Application, Window)
from System.Windows.Controls import (Label, StackPanel)

window = Window()
window.Title = "Simple Python WPF Example"
window.Width = 400
window.Height = 300

stackPanel = StackPanel()
window.Content = stackPanel

helloLabel = Label()
helloLabel.Content = "Hello"
helloLabel.FontSize = 50
stackPanel.Children.Add(helloLabel)

app = Application()
app.Run(window)

Creating Python Class Instances Using Object Operations

The DLR Hosting API provides a class called ObjectOperations that we can use to perform various operations on an object. For example, we can use the overloaded GetMember methods of ObjectOperations to get a member property of an object. If an object is callable, we can use the overloaded Invoke methods of ObjectOperations to invoke the object. If an object is a class, we can use the CreateInstance method of ObjectOperations to create an instance of the class.  

Listing 6-25 shows an example that uses the CreateInstance method of ObjectOperations to create an instance of a Python class. The Python code in Listing 6-25 is in the pyCode string object. The Python code defines a Python class called ClassA that has no attributes. The example executes the Python code, and the result is stored in the script scope against the variable name “ClassA”. The example fetches that variable from the script scope and assigns it to the ClassA variable, then it shows two ways to use the ClassA variable to create an instance of the ClassA Python class. The first way uses the CreateInstance method of ObjectOperations. The code in Listing 6-25 obtains an instance of ObjectOperations for the Python language from the Operations property of the pyEngine variable, and then calls the CreateInstance method on the ObjectOperations instance to create an instance of the ClassA Python class. The second way of creating an instance of the ClassA Python class is simply invoking the ClassA variable as if it were a callable object.

Listing 6-25. Create an Instance of a Python Class in C#

public static void CreateInstanceOfPythonClassInCSharp()
{
    ScriptEngine pyEngine = ScriptRuntime.CreateFromConfiguration().GetEngine("python");
    string pyCode = @"class ClassA(object): pass";
    ScriptSource source = pyEngine.CreateScriptSourceFromString(pyCode,
        SourceCodeKind.Statements);
    ScriptScope scope = pyEngine.CreateScope();
    source.Execute(scope);
    dynamic ClassA = scope.GetVariable("ClassA");
    object objectA1 = pyEngine.Operations.CreateInstance(ClassA);
    object objectA2 = ClassA();
}

Level Three Use of the DLR Hosting API

We saw a few Level Three uses of the DLR Hosting API in earlier sections. In this section, we will look at some more examples of using the DLR Hosting API at this level. We will see what a script host is and how to implement a custom script host, how to use ObjectOperations to get the documentation of a Python function, and how to create a script runtime in a separate .NET application domain.

Script Host

A script host in the DLR is an instance of the Microsoft.Scripting.Hosting.ScriptHost class and represents the host platform of a script runtime. Because the DLR can run not only on the usual .NET CLR runtime but also in Web environments such as the Silverlight runtime, the DLR Hosting API defines the ScriptHost class to abstract away the specifics of the platforms. For example, given the file path of a Python source code file, the Silverlight runtime and the usual .NET CLR runtime resolve the file path differently. The file path is an example of the platform specifics that are abstracted into the ScriptHost class and a related class called PlatformAdaptationLayer. For good software design, the DLR team isolated those platform specifics into these two classes. The rest of the DLR Hosting API is kept unaware of whether a script runtime runs on the Silverlight platform or the usual .NET CLR platform. If you’re interested in knowing more about running the DLR in the Silverlight runtime, Chapter 11 has an in-depth discussion on that subject.

To show you how ScriptHost and PlatformAdaptationLayer work and how you can use them if you want to run a script runtime on a custom host platform, the example in Listings 6-26 and 6-27 shows how to implement a ScriptHost derived class and a PlatformAdaptationLayer derived class to resolve file paths differently. Listing 6-26 shows the ScriptHost derived class called SimpleHostType. The SimpleHostType overrides the get method of the PlatformAdaptationLayer property it inherits from the base class. The overridden get method returns an instance of the SimplePlatformAdaptationLayer class, whose code is shown in Listing 6-27.

Listing 6-26. SimpleHostType.cs

public class SimpleHostType : ScriptHost
{
    public override Microsoft.Scripting.PlatformAdaptationLayer PlatformAdaptationLayer
    {
        get
        {
            return SimplePlatformAdaptationLayer.INSTANCE;
        }
    }
}

The implementation of SimplePlatformAdaptationLayer overrides the OpenInputFileStream method inherited from the base PlatformAdaptationLayer class. The overridden OpenInputFileStream method resolves a file path by appending “Python” to the path and then passing the modified path the OpenInputFileStream method of the base class. The implication of this file path resolution is that if we want to run the hello.py file in the physical C:ProDLRsrcExamplesChapter6HostingExamplesPython folder, we need only specify “hello.py” instead of “Pythonhello.py” as the file path. Listing 6-28 shows an example of this effect.

Listing 6-27. SimplePlatformAdaptationLayer.cs

public class SimplePlatformAdaptationLayer : PlatformAdaptationLayer
{
    public static PlatformAdaptationLayer INSTANCE = new SimplePlatformAdaptationLayer();

    public override System.IO.Stream OpenInputFileStream(string path)
    {
        return base.OpenInputFileStream(@"Python" + path);
    }
}

The code in Listing 6-28 creates an instance of ScriptRuntimeSetup and sets that instance’s HostType property to typeof(SimpleHostType). Then the code uses the ScriptRuntimeSetup instance to create a script runtime. Because the script host associated with the script runtime is an instance of SimpleHostType, we specify the file path of the Python source code we want to execute on the script runtime as “hello.py” instead of “Pythonhello.py”. Had we not set the HostType property of the ScriptRuntimeSetup instance to typeof(SimpleHostType), the ScriptHost class would be used as the default class for creating a script host for the script runtime and we would need to specify “Pythonhello.py” as the file path.

Listing 6-28. An Example That Uses the SimpleHostType Class to Resolve File Paths

public static void UseCustomScriptHost()
{
    ScriptRuntimeSetup setup = ScriptRuntimeSetup.ReadConfiguration();
    setup.HostType = typeof(SimpleHostType);
    ScriptRuntime scriptRuntime = new ScriptRuntime(setup);
    ScriptScope scope = scriptRuntime.ExecuteFile(@"hello.py");
}

Object Operations

We looked at the ObjectOperations class when we discussed the Level Two usage of the DLR Hosting API. ObjectOperations defines many methods that we can use to perform various operations on an object. Some of the methods belong to Level Two and others belong to Level Three. In this section, I will show you a Level Three use of the ObjectOperations class that obtains the documentation string of a Python function. This can be useful when you’re building a tool that provides intellisense for Python functions, for example.

Listing 6-29 shows our example, in which we create a script runtime and a script engine as usual for running the Python code in helloFunc.py. We already saw the Python code in helloFunc.py earlier in this chapter. For your convenience, the Python code is displayed below with the line of code that’s the focus of our current discussion in bold:

def hello():
    """This function prints Hello."""
    print "Hello"

The bolded line is the documentation string of the hello function. It is the string we want to obtain using ObjectOperations. In Listing 6-29, after the Python code is run, we fetch the object that represents the hello function from the script scope in line 6 and assign it to the helloFunction variable. In lines 8 to 10, we use the GetMemberNames method of ObjectOperations to print out all the members of the helloFunction. One of those members is the __doc__ attribute predefined by the Python language for every Python class, function, and object. The __doc__ attribute contains the documentation string of the Python hello function, and line 12 in Listing 6-29 uses the GetMember method of ObjectOperations to print the documentation string on the screen. If you run the code in Listing 6-29, you’ll see the string “This function prints Hello.” printed as the last line on the screen.

Listing 6-29. Printing the Documentation String of a Python Function to the Screen

1)  public static void GetDocStringOfPythonFunctionUsingObjectOperations()
2)  {
3)      ScriptRuntime scriptRuntime = ScriptRuntime.CreateFromConfiguration();
4)      ScriptEngine pyEngine = scriptRuntime.GetEngine("python");
5)      ScriptScope scope = pyEngine.ExecuteFile(@"PythonhelloFunc.py");
6)      object helloFunction = scope.GetVariable("hello");
7)      
8)      IList<String> helloFuncMembers = pyEngine.Operations.GetMemberNames(helloFunction);
9)      foreach (var item in helloFuncMembers)
10)          Console.WriteLine(item);
11)              
12)     Console.WriteLine(pyEngine.Operations.GetMember(helloFunction, "__doc__"));
13)  }

Remote Script Runtime

A Level Three use of the DLR Hosting API is to run one or more script runtimes remotely. The script runtimes can run in a different process from the host’s process or they can run in the same process but in different application domains. This section will briefly explain what an application domain is and show an example of running a script runtime in the same process as the host’s but in a different application domain. The next sections will give a quick tour of .NET Remoting and show an example of running a script runtime in a process different from the host’s.

A .NET application domain is like a lightweight process that creates a boundary between software components that run in a process. A process can have multiple application domains. Each application domain has its own address space for referencing objects in memory. References to objects in one address space will make no sense in another address space. Therefore, we can’t pass object references in one application domain to another application domain and expect those references to point to the same objects. To share objects between two application domains, those objects need to be value objects that can be serialized in one application domain and deserialized in another. Or the objects need to be instances of classes that inherit directly or indirectly from a .NET Remoting class called MarshalByRefObject. The classes ScriptRuntime, ScriptEngine, ScriptScope, ScriptSource and CompiledCode all inherit from MarshalByRefObject. Therefore, instances of those classes can live in one application domain and be referenced in another application domain. The underlying framework that makes it possible to reference objects that live in a different application domain is .NET Remoting.

One application domain can contain multiple instances of ScriptRuntime. There are many reasons for running a script runtime in a separate application domain. For example, it’s common to use the DLR to add scripting capability to an application. By providing scripting capability, you allow users of your application to extend and automate the application. Users will write code in languages such as IronPython and IronRuby and your application will run those code scripts. When your application runs code scripts written by users, you often want to protect your application from code errors or malicious attacks in those scripts. You will not want your application process to crash when a user script crashes. Sometimes you might want to run user scripts with a different security access level (SAL). For those reasons, you want to execute user scripts in a script runtime that lives in a separate application domain.

Let’s see an example that runs a script runtime in a separate application domain within the same process. Listing 6-30 shows an example that creates a script runtime in a separate application domain. The example creates a new application domain by calling the static CreateDomain method of the AppDomain class in line 3. The newly created application domain is different from the application domain that runs the code in Listing 6-30. To ease the discussion, let’s refer to the application domain created in line 3 as the new application domain and the application domain that runs the code in Listing 6-30 as the old application domain. In line 4, the example calls the static CreateRemote method of ScriptRuntime with the new application domain created as an input parameter. This effectively creates a remote script runtime that lives in the new application domain. The scriptRuntime variable in line 5 points not to the real remote script runtime but to a proxy of the remote script runtime. (The next section about .NET Remoting will explain more about proxies and remote objects.) When the example code calls the GetEngine method on the remote script runtime in line 6, the GetEngine method returns a script engine. The script engine is also a proxy of the real script engine object that lives in the new application domain. In the old application domain, we are able to reference the script runtime and script engine in the new application domain because the ScriptRuntime and ScriptEngine classes inherit from MarshalByRefObject.

Line 7 uses the remote script engine to execute the helloFunc.py file. The result of the execution is an object stored in the script scope against the name “hello”. That object lives in the new application domain. Because of this, when we fetch that object from the script scope in line 8, we call the GetVariableHandle method on the script scope rather than the GetVariable method we saw in previous examples.

Listing 6-30. Executing Python Code in a Remote Script Runtime

1)  public static void CreateRemoteScriptRuntime()
2)  {
3)      AppDomain appDomain = AppDomain.CreateDomain("ScriptDomain");
4)      ScriptRuntimeSetup setup = ScriptRuntimeSetup.ReadConfiguration();
5)      ScriptRuntime scriptRuntime = ScriptRuntime.CreateRemote(appDomain, setup);
6)      ScriptEngine pyEngine = scriptRuntime.GetEngine("python");
7)      ScriptScope scope = pyEngine.ExecuteFile(@"PythonhelloFunc.py");
8)      ObjectHandle helloFunction = scope.GetVariableHandle("hello");
9)      pyEngine.Operations.Invoke(helloFunction, new object[]{});
10)  }

The rest of this chapter will dive deeper into the subject of .NET Remoting and how that relates to the DLR Hosting API. We’ll start with a quick tour of .NET Remoting in general that includes nothing specific to the DLR. After that, I’ll show you an example that runs a script runtime in a different process. If you are already familiar with .NET Remoting, you can skip the next section and jump directly to the example.

.NET Remoting Quick Tour

.NET Remoting is a framework that assists developers in creating applications that are distributed across application domains, processes, or physical machines. Like a number of other technologies, such as DCOM, Java RMI, and CORBA’s IIOP/GIOP, the aim of .NET Remoting is to help in the development of objects or components that can be consumed remotely in an object-oriented way. Figure 6-1 shows the relationship between a .NET Remoting server and client, which might be in a different application domain within the same process, in different processes, or on different physical machines.

image

Figure 6-1. The architecture of a distributed application based on .NET Remoting

As Figure 6-1 shows, the server-side component of a .NET Remoting-based application has some objects that the client component can interact with through proxies. The proxies communicate with the server-side objects they represent through channels. The server-side objects can be remoting objects or non-remoting objects. If they are remoting objects, they can be classified into one of three kinds: client-activated, SingleCall, and Singleton, as follows:

  • A client-activated object is an object that is activated at the client’s request. It can hold state information between several method calls triggered by the same client on the object. A client-activated object is associated with one client only and does not allow you to share state information among several clients.
  • A SingleCall object is a server-activated object. A new SingleCall object is created each time one of its methods is called by a client, and the object is destroyed when the method call is finished. Consequently, a SingleCall object holds no state information between method calls.
  • A Singleton object is also server-activated object. There can be only one instance of a Singleton class. The state of a Singleton object is shared among multiple clients.

Communication channels between a .NET Remoting client and server can use network protocols such as HTTP, SMTP, and TCP. When using an HTTP channel, the request and response messages can be formatted as Simple Object Access Protocol (SOAP) messages. Besides SOAP, there are other formatting choices we can use with the HTTP transfer protocol.

The proxy object in Figure 6-1 is an instance of some type. There are a number of ways for the .NET Remoting client to get that type’s information. One way is to compile the server-side code into assemblies and let the client-side project reference those assemblies. Because the assemblies contain the type information of the server-side classes, by referencing those assemblies, the .NET Remoting client is able to create proxy objects for the server-side remoting objects. Another way to make the server-side type information available to the .NET Remoting client is to use a utility program called soapsuds.exe that ships with the .NET Framework SDK.

Running Script Runtime in a Separate Process

Now let’s look at an example that runs a script runtime in a different process. You can find the server-side code of this example in the ScriptServer project of this chapter’s code download. The client-side code is in the ScriptClient project. The server side of the example will provide a script runtime as a remoting object and the client side will use that script runtime to execute Python code in the process that hosts the server side of the example.  

A server-activated object (SAO) such as a Singleton or SingleCall object needs to have a default constructor that takes no input parameters. Because ScriptRuntime does not have a default constructor that takes no input parameters, its instances can’t be server-activated objects. Therefore, if we want to use the same instance of ScriptRuntime across multiple requests from .NET remoting clients, instead of making the instance of ScriptRuntime a Singleton SAO, we need to use a factory object to act as the Singleton SAO. The factory object will provide clients access to the ScriptRuntime instance. Listing 6-31 shows a factory class called RemotingFactory. The class derives from MarshalByRefObject so that its instances can be remoting objects. RemotingFactory has only one method called GetScriptRuntime, which will return the same ScriptRuntime instance every time it’s called.

Listing 6-31. RemotingFactory.cs

public class RemotingFactory : MarshalByRefObject
{
    private static ScriptRuntime scriptRuntime = ScriptRuntime.CreateFromConfiguration();
            
    public ScriptRuntime GetScriptRuntime()
    {
        return scriptRuntime;
    }
}

Now that we have the RemotingFactory class, we need a process to host and expose an instance of this class as a Singleton object. Listing 6-32 shows the code that, when run, creates a host process and exposes a RemotingFactory instance as a Singleton object. The code in Listing 6-32 creates a channel that uses TCP as the transport protocol and a binary formatter for formatting messages. The channel listens on port 8088. We use the static RegisterWellKnownServiceType method of the RemotingConfiguration class to register RemotingFactory as the type of a Singleton object whose name is “RemotingFactory”. At this point, the server-side implementation of our example is done. Next, we will look at the client-side implementation.

Listing 6-32. Server Code That Hosts a RemotingFactory Instance as a Singleton Object

static void Main(string[] args)
{
    BinaryServerFormatterSinkProvider serverProvider =
                        new BinaryServerFormatterSinkProvider();
    serverProvider.TypeFilterLevel = TypeFilterLevel.Full;

    IDictionary props = new Hashtable();
    props["port"] = 8088;
    
    TcpChannel channel = new TcpChannel(props, clientSinkProvider:null,
                        serverSinkProvider:serverProvider);
    ChannelServices.RegisterChannel(channel, false);
            
    RemotingConfiguration.RegisterWellKnownServiceType(typeof(RemotingFactory),
        "RemotingFactory", WellKnownObjectMode.Singleton);

    Console.WriteLine("Server started. Press enter to shut down.");
    Console.ReadLine();
}

Listing 6-33 shows the code that implements the .NET Remoting client. The code gets a hold of the server-side Singleton object by calling the static GetObject method of Activator with “tcp://localhost:8088/RemotingFactory” as the URI of the server-side Singleton object. We use this URI because (a) the server channel uses TCP as the transport protocol, (b) the server channel listens on port 8088 of localhost, and (c) the server Singleton object is named “RemotingFactory”. Once the client code gets the “RemotingFactory” Singleton object, it calls the Singleton object’s GetScriptRuntime method to get the remote script runtime.

The remote script runtime lives in the server process. What the client code gets from calling the GetScriptRuntime method is a proxy of the remote script runtime. The code in Listing 6-33 calls the GetEngine method on the remote script runtime, which returns a script engine. The script engine is again a proxy of the real script engine object that lives in the server process. The code in Listing 6-33 uses the remote script engine to execute a Python code snippet that defines a Python function called isodd. The result of the execution is an object stored in the script scope against the name “hello”. That object also lives in the server process. Because of that, when we fetch that object from the script scope, we call the GetVariableHandle method instead of the GetVariable method on the script scope.

Listing 6-33. The .NET Remoting Client

static void Main(string[] args)
{
    TcpChannel channel = new TcpChannel();
    ChannelServices.RegisterChannel(channel, false);
    RemotingFactory remotingFactory = (RemotingFactory)Activator.GetObject(
                typeof(RemotingFactory), "tcp://localhost:8088/RemotingFactory");
    
    ScriptRuntime runtime = remotingFactory.GetScriptRuntime();
    ScriptEngine pyEngine = runtime.GetEngine("python");
    string pyFunc = @"def isodd(n): return 1 == n % 2;";
    ScriptSource source = pyEngine.CreateScriptSourceFromString(pyFunc,
        SourceCodeKind.Statements);
    ScriptScope scope = pyEngine.CreateScope();
    source.Execute(scope);
    ObjectHandle IsOddHandle = scope.GetVariableHandle("isodd");

    ObjectHandle result = pyEngine.Operations.Invoke(IsOddHandle, new object[] { 3 });
    bool answer = pyEngine.Operations.Unwrap<bool>(result);
    Console.WriteLine("Is 3 an odd number? {0}", answer);
    Console.ReadLine();
}

To run the example, you need to first compile the ScriptServer and ScriptClient projects in this chapter’s code download. After the compilation, you need to run ScriptServer.exe first and then ScriptClient.exe. Running ServerServer.exe causes a command console to pop up and you’ll see the text “Server started. Press enter to shut down.” displayed in the command console. Running ScriptClient.exe will cause another command console to pop up and display the text “Is 3 an odd number? True”.

Summary

The DLR Hosting API offers a lot of value and functionality. In this chapter, we saw examples that demonstrate how cumbersome it can be to host one language in another without it. The DLR Hosting API consists of a consumer-side API and a provider-side API. We focused here on the consumer side, with many code examples that demonstrate how to use this API at different levels. We discussed in detail how the various kinds of script scopes pass value objects and reference objects around. We then introduced .NET Remoting and looked at an example that runs Python code on a script runtime that lives in a separate process. The DLR Hosting API is an interesting component that has many useful applications. In Chapter 9, we will look at a programming language called Stitch that extends the functionalities of the DLR Hosting API. In Chapter 11, you’ll see how the DLR Hosting API enables DLR-based language code to be embedded in a Silverlight application.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.217.150.123