Sign in to follow this  
Shinkage

[.net] Easy Scripting in .NET

Recommended Posts

Shinkage    595
For all those who haven't experimented with it, the Microsoft.CSharp namespace in the .NET BCL provides access to a fully functioning C# compiler. Using this it only takes a few simple steps to add powerful scripting support to any project. For those interested, I'll demonstrate the simplest way to get such a system up and running with only a few lines of code. First we'll want access to the namespaces where all this functionality resides.
using Microsoft.CSharp;
using System.CodeDom;
using System.CodeDom.Compiler;
Next up we need to create the objects that will actually do the work of compiling our scripts for us.
CSharpCodeProvider	codeProvider = new CSharpCodeProvider();
ICodeCompiler		codeCompiler = codeProvider->CreateCompiler();
With that ready, the compilation parameters need to be set up so the compile knows exactly what we want made. Most of these options are pretty straightforward and obvious. Pay close attention to the ReferencedAssemblies property of the compiler parameters however. This is where we tell the compiler which assemblies we want our script to have access to. For example, if the script is going to be doing any works with forms, it'll need to reference "System.Windows.Forms.dll."
CompilerParameters	params = new CompilerParameters();
params.GenerateExecutable = false;
params.GenerateInMemory = true;
params.IncludeDebugInformation = false;
params.TreatWarningsAsErrors = false;
params.ReferencedAssemblies.Add( "System.dll" );
params.ReferencedAssemblies.Add( "System.Windows.Forms.dll" );
CompilerResults		results = codeCompiler.CompileAssemblyFromFile( params, "MyScript.cs" );
At this point if the compiler met with any errors they can be found in the results.Errors member. Otherwise, we now have a new assembly loaded into memory and ready for use. Using the System.Reflection namespace, any types or methods declared in the source file can be discovered and used. Let's assume that the source file looks like the following:
// MyScript.cs

using System;
using System.Windows.Forms;

class Script
{
	public static void Main()
	{
		MessageBox.Show( "This is a script!" );
	}
}
Given that source after compilation by the preceding steps, the following will be sufficient to call the static Main method.
results.CompiledAssembly.GetType( "Script").GetMethod( "Main" ).Invoke( null, null );
Of course that only covers the basics, but there you have it.

Share this post


Link to post
Share on other sites
Washu    7829
An excellent example.

For those who are going to implement such a setup however, I would sugest using another AppDomain and giving it restricted security settings, so that it's harder for malicious scripts to be used.

Share this post


Link to post
Share on other sites
VizOne    598
The managed DX scripting example uses exactly this technique.


Here a code snippet how to setup a "sandbox"-AppDomain

public class ScriptRunner : MarshalByRefObject // make class remotable
{
ICodeCompiler compiler;
CompilerParameters param;

public static ScriptRunner CreateInSeparateDomain()
{
AppDomain dom = AppDomain.CreateDomain( "sandbox" );
return dom.CreateInstanceAndUnwrap( typeof( ScriptRunner ).Assembly.GetName().Name,
typeof( ScriptRunner ).FullName ) as ScriptRunner;
}

public ScriptRunner()
{
string parameters = "";
// C# compiler
compiler = new CSharpCodeProvider().CreateCompiler();
// parameters = "/debug+"; // uncomment for debugging

// uncomment for JScript.NET
//compiler = new JScriptCodeProvider().CreateCompiler();
//parameters ="/debug+ /versionsafe+ ";

param = new CompilerParameters();
param.CompilerOptions = parameters;
param.GenerateExecutable = false;
param.GenerateInMemory = true;
param.IncludeDebugInformation = true;
param.ReferencedAssemblies.Add( "Scripting.dll" ); // whatever you need here

// set policy
PolicyLevel level = PolicyLevel.CreateAppDomainLevel();
PermissionSet permissions = new PermissionSet( PermissionState.None );
// uncomment all permissions you need
// (never allow "Assertion"...)
// which flags are required minimally also depends
// on .NET runtime Version
SecurityPermissionFlag permissionFlags =
// SecurityPermissionFlag.Assertion |
// SecurityPermissionFlag.BindingRedirects |
// SecurityPermissionFlag.ControlAppDomain |
// SecurityPermissionFlag.ControlDomainPolicy |
// SecurityPermissionFlag.ControlEvidence |
// SecurityPermissionFlag.ControlPolicy |
// SecurityPermissionFlag.ControlThread |
// SecurityPermissionFlag.ControlPrincipal |
// SecurityPermissionFlag.Infrastructure |
// SecurityPermissionFlag.RemotingConfiguration |
// SecurityPermissionFlag.SerializationFormatter |
// SecurityPermissionFlag.Infrastructure |
SecurityPermissionFlag.SkipVerification |
SecurityPermissionFlag.UnmanagedCode|
SecurityPermissionFlag.Execution;

permissions.AddPermission( new SecurityPermission( permissionFlags ) );

// allow reflection
permissions.AddPermission( new ReflectionPermission( ReflectionPermissionFlag.AllFlags ) );

PolicyStatement policy = new PolicyStatement( permissions, PolicyStatementAttribute.Exclusive );
CodeGroup group = new UnionCodeGroup( new AllMembershipCondition(), policy );
level.RootCodeGroup = group;
AppDomain.CurrentDomain.SetAppDomainPolicy( level );
}
}

// add code for compiling and running scripts
}





Regards
Andre

Share this post


Link to post
Share on other sites
VizOne    598
A note: it is important to create the compiler in the sandbox domain. Else the assembly created would automatically bleed into the main domain. This also means that you must set the permissions AFTER the compiler was created, as the compiler requires some special permissions.

Regards,
Andre

Share this post


Link to post
Share on other sites
this is interesting...

I'm just wondering tho...

how might you use this within an html browser?


<html>
<head>
<title>c#</title>
</head>
<body>
<script language="c#">
// MyScript.cs

using System;
using System.Windows.Forms;

class Script
{
public static void Main()
{
MessageBox.Show( "This is a script!" );
}
}
</script>
</body>
</html>




or


<html>
<head>
<title>c#</title>
</head>
<body>
<script language="c#" src="MyScript.cs">
</script>
</body>
</html>



I find this cool as a matter of fact...
I might like to create an enhanced version of vbscript using visual basic .net that easily implements directx coponents.

of course you could achieve directx by using com in a script refering to directAnimation, but that part of directx is dead and why use the simple scripting languages when you can be proud of making your own? Who doesn't want to feel accomplished?

Share this post


Link to post
Share on other sites
wait a sec...

your not talking a bout the scripting I was thinking of...

dang... that c# code is not meant for the "script" box I guess...

you could of course go make .net apps by means of asp pages, but it adds some overhead if youre trying to create one from scratch

ie: without visual studio.net

any possible way to have c# go into the script tag with simplicity... aka the standard html+script approach.

thanx...

Share this post


Link to post
Share on other sites
Influenza    162
C# scripts in web pages are parsed by the web server and execute through ASP.NET before the web page data is sent to the user. They don't act the same as javascript blocks, which are interpreted and run on the user's web client. So no, you can't use C# inside a web page without using ASP.NET.

This is a very good introductory walktrough. I'd read about the CSharp namespace, but never looked into using it. I'd better look again!

Share this post


Link to post
Share on other sites
Etnu    880
Yeah, the sample that comes with the DX9c SDK shows how to use C# scripts within native C++ code by using an intermediate managed C++ layer. It works amazingly well.

Share this post


Link to post
Share on other sites
JesseT    402
Quote:
Original post by deathtrap
So... exactly what are the uses of this? All i see is that you're compiling source code inside your source code.


LOL

Share this post


Link to post
Share on other sites
Nypyren    12074
It's good to note that you can use a string in memory rather than a file on disk for your compile source.

FWIW - I'm using this type of scripting in a binary file editor. I allow the "scripts" to use the public classes/members of my own editor .exe by adding my .exe itself to the reference list.

I use the scripts mainly for loading/saving different file formats, but I'm thinking about letting them do some more advanced stuff adding on to the user interface.

Share this post


Link to post
Share on other sites
dug    160
Here is how to dynamically compile a boo script ( http://boo.codehaus.org/ ), you can sandbox the resulting assembly before running it too if need be.


// (compile with references to Boo.dll and Boo.Lang.Compiler.dll)
using System;
using System.IO;
using System.Threading;
using System.Reflection;
using Boo.Lang.Compiler;
using Boo.Lang.Compiler.IO;
using Boo.Lang.Compiler.Pipelines;


namespace BooRunner
{
class App
{
[STAThread]
static int Main(string[] args)
{
string booscript = "print \"hello\"";

return RunScript(booscript);
}

static int RunScript(string script)
{
BooCompiler compiler = new BooCompiler();
compiler.Parameters.Input.Add(new StringInput("<stdin>", script));
//or to run files:
//compiler.Parameters.Input.Add(new FileInput(Path.GetFullPath(filepath)));

compiler.Parameters.Pipeline = new CompileToMemory();

//or to compile to a dll:
//compiler.Parameters.Pipeline = new CompileToFile();
//compiler.Parameters.OutputAssembly = "Script.dll";

CompilerContext result = compiler.Run();

if (result.Errors.Count > 0)
{
foreach (CompilerError error in result.Errors)
{
Console.WriteLine(error.ToString(true));
}
return -1;
}
try
{
result.GeneratedAssemblyEntryPoint.Invoke(null, new object[1]);
}
catch (TargetInvocationException x)
{
Console.WriteLine(x.InnerException.ToString());
return -1;
}
return 0;

}
}
}

Share this post


Link to post
Share on other sites
posti    122
You can also embed mono in your project and use C# scripting via mono. Making your code little more platform independant.

http://www.mono-project.com/Embedding_Mono
http://www.go-mono.com/embedded-api.html

Probably possible to run mono on game consoles too. So if you plan on porting your game to consoles or other os than windows you should use it.

Share this post


Link to post
Share on other sites
Guest Anonymous Poster   
Guest Anonymous Poster
Quote:
Original post by VizOne
The managed DX scripting example uses exactly this technique.


Here a code snippet how to setup a "sandbox"-AppDomain
*** Source Snippet Removed ***

Regards
Andre



Hello Andre,
Im using VB.NET 2005. Ive been unable to get the sandboxed version to work, although I have no problems with compiling and invoking scripts from the current appdomain.

With the sandboxed version, the call to myprovider.CompileAssemblyFromSource throws an exception that it cannot find the temporary binary file it's created. I even tried specifying an output file and i see it being created but it still complains with filenotfoundexception.

I'm thinking maybe it has something to do with how i'm trying to load the assembly. I'll keep trying, but if you have any more tips, it'd be much appreciated.
-Hypnotron

Share this post


Link to post
Share on other sites
Rob Loach    1504
Washu wrote up an excellent article on Code Access Permissions which I suggest you should most definately read.

Also, a nice tutorial was just recently published here on Gamedev: Using Lua with C#. It goes over using the LuaInterface in .NET. Lua should be mentioned as it's been in the game industry for years.

I'd also like to make a self plug of my GakScript Scripting Engine which nicely wraps the CodeDom and adds multiple language plugin interfaces. Very easy to use and comes with a few simple examples of its use.

[Edited by - Rob Loach on June 4, 2006 5:11:48 PM]

Share this post


Link to post
Share on other sites
Guest Anonymous Poster   
Guest Anonymous Poster
Quote:
Original post by Rob Loach
Washu wrote up an excellent article on Code Access Permissions which I suggest you should most definately read.

Also, a nice tutorial was just recently published here on Gamedev: Using Lua with C#. It goes over using the LuaInterface in .NET. Lua should be mentioned as it's been in the game industry for years.

I'd also like to make a self plug of my GakScript Scripting Engine which nicely wraps the CodeDom and adds multiple language plugin interfaces. Very easy to use and comes with a few simple examples of its use.


Hello Rob. Thanks for the reply.

I understand the importance of code security, but with regards to loading scripts within the same appDomain (as I believe the article you link to suggests) it doesnt deal with the issue of script bloat. That is, you cannot unload assemblies from an AppDomain. However,you _can_ unload entire AppDomains so if you load scripts in an AppDomain you can free memory by unloading that domain.

Am I off base here?
-Hypnotron

Share this post


Link to post
Share on other sites
gwihlidal    1004
My book shows how to use the CodeDom compiler to automate an MVC object model; essentially allowing the ability to write scripts that automate your applications (macros, things like that).

You can also use CodeDom to spit out assemblies you can link to at a later date. For example, all my business objects are represented by XSD and WSDL files, and I wrote a custom compiler that takes my specs and builds business entity objects with them. This allows me to take specifications, build a schema, and auto generate all the code with the special constraints, assert checks, logging, etc..

~Graham

Share this post


Link to post
Share on other sites
foubar    122
I was just wondering how i would link in the use of a static variable within the program.

For example...

public class Test
{ public static string[] TestStringArray; }

I would need this to access and modify client information based on location and other attributes from the origonal application, acessed by the script.

Share this post


Link to post
Share on other sites
backtrace    122
After some playing with this (very useful) code, I still haven't figured out how to specify arguments for the called function. All my attempts have lead me to NullReferenceExceptions. Any idea how I would do this? Using temporary files doesn't look like a good solution to me, but it's the only thing I've been able to come up with for now.

Share this post


Link to post
Share on other sites
Nukleon    122
Hello everyone!

I am trying to get a script engine running from my main program.
The problem i encountered is, that i have to access a member of the main class from the script.
I think i would be better if i explain my program a bit:

I have a main winform, which gathers data from a serial port (the data is provided by a microcontroller).
i would like the script to access the gathered data (which is stored in a variable of the CControl class) and access the main form's "Send()" method, so the script could read data and send commands to the microcontroller.

Is it possible to access the member variables of the Main Thread from a script? How could it be done?
Thanks
Nukleon

Share this post


Link to post
Share on other sites
backtrace    122
Quote:
Original post by Nukleon
... I am trying to get a script engine running from my main program.
The problem i encountered is, that i have to access a member of the main class from the script.
I think i would be better if i explain my program a bit:
...
Is it possible to access the member variables of the Main Thread from a script? How could it be done? ...


Assuming that what you mean is what I think it is ;), I think you can do this by creating a library (at least that's what I'm doing) that contains the information from that class. Like this:

Script:
using MyLib;

class X
{
void Y()
{
MyLib.ClassName.Yatta;
}
}


MyLib:
class ClassName
{
// define your stuff here
}


And then make the main program Invoke() the 'script', using MyLib as a reference. In order to do that correctly, you need to make sure it's in the same path, or specify the correct path of the DLL. Of course the above is only pseudo-code, but it demonstrates what I mean. Combined with the information above, I think you'll be able to set up something useful.

I hope to have been of some help to you.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this