Click here to Skip to main content
15,880,392 members
Articles / Programming Languages / C#
Article

LiveCode.NET

Rate me:
Please Sign up or sign in to vote.
4.86/5 (55 votes)
6 May 2002Public Domain3 min read 231.5K   2.5K   170   37
Compile C# code on-the-fly. Usage in a plug-in / plug-out component model.

Sample Image - livecodedotnet.jpg

Disclaimer: The information in this article & source code is published in accordance with the final (V1) bits of the .NET Framework

Contents

Abstract

This sample shows how to compile C# source-code on-the-fly, and how to use this in a plug-in / plug-out component model.

Compilation

The namespace Microsoft.CSharp located in the .NET component "Managed C# Compiler" (cscompmgd.dll) has a Compiler class with the Compile() method to parse C# code in-process:

C#
public static CompilerError[] Compile(
   string[] sourceTexts,
   string[] sourceTextNames,
   string target,
   string[] imports,
   IDictionary options
);

The LiveApp Windows Forms sample shows a TextBox control where the user can enter the C# source. Later we simply pass the TextBox.Text string to Compile().

Plug-in

To make the plug-in as flexible as possible, we only use a predefined interface ILiveInterface for any communications between the host and the plug-in. Note we don't use any late-binding, reflection or Invoke mechanics, but true interfaces!

This interface is compiled into a separate .NET library assembly, LiveInterface.dll. To keep our example as simple as possible, this interface has just one method:

C#
public interface ILiveInterface
{
  string ModifyString( string inpString );
}

With ModifyString() we pass in a string and get the modified string back.

But note, the interface can have any number of methods with any kind of parameters and return values. Although there may be some limitations for cross app-domain communications.

Plug-out

Once loaded in an application, a .NET assembly is kept in memory. There is no possibility to release just one single assembly. The only way to get this effect is to unload the complete AppDomain.

This limitation forces us to use some techniques:

  • We have to create a separate (secondary) AppDomain to be unloaded later.
  • Unfortunately, there is no activation method to create a class instance in another AppDomain and to only return just a specific interface to this instance. We always get back an object reference, and this would force the plug-in-assembly to be attached to our primary AppDomain! If this happens, there is no way to "plug-out" the plug-in-assembly later.
  • To workaround this, I used a class-factory approach.

The class-factory is just a simple class also located in the same assembly LiveInterface.dll like ILiveInterface is. It provides one Create() method as a wrapper for the .NET Activator.CreateInstanceFrom() call:

C#
public class LiveInterfaceFactory : MarshalByRefObject
{
  ...
  public ILiveInterface Create( string assemblyFile, string typeName, 
      object[] constructArgs )
  {
    return (ILiveInterface) Activator.CreateInstanceFrom(
         assemblyFile, typeName, false, bfi, null, constructArgs,
         null, null, null ).Unwrap();
  }

The most important feature of the Create() method is to only return our interface, but NOT the object! This way, the only real connection between the two app-domains is our well-known interface.

Finally, this lets us to unload an app-domain like:

C#
AppDomain.Unload( secDom );

Sample App

The LiveCode.NET example solution contains this two projects:

  • LiveInterface: interface & class-factory only assembly
  • LiveApp: sample Windows Forms application for hosting plug-ins

You can see the LiveApp GUI on the screenshot at the top of this page.

The initial plug-in source-code is loaded from file InitialLiveClass.cs into the TextBox. The user can now modify this code, as long as the interface and class-name is unchanged.

Another TextBox lets the user type the string for the class constructor parameter, and one more is for the ModifyString() method parameter.

If the user hits the Run! button, this executes these two steps:

C#
private void btnRun_Click(object sender, System.EventArgs e)
{
  try
  {
    DoCompile();        // compile the C# source
    DoRun();            // execute live!
  }
  catch( Exception )
  ...
}

DoCompile() just compiles passing the C# source, the referenced assemblies and compiler options:

C#
private void DoCompile()
{
  string[] srcCodes = new string[] { textSourceCode.Text };    
      // the source code
  string[] referAsm = new string[] { "LiveInterface.dll" };    
      // referencing assembly
  string outFile    = "LiveAssembly.dll";                      
      // the assembly to create
  ListDictionary cplOpts = new ListDictionary();               
      // compiler parameters
  cplOpts.Add( "target", "library" );
  CompilerError[] ces = Microsoft.CSharp.Compiler.Compile( srcCodes,
                                  srcNames, outFile, referAsm, cplOpts );
  ...
}

DoRun() loads the new assembly into a secondary app-domain and executes the method:

C#
private void DoRun()
{
  // create a secondary app-domain
  AppDomain secDom = AppDomain.CreateDomain( "SecondaryDomain" );

  // create the factory class in the secondary app-domain
  LiveInterfaceFactory factory = (LiveInterfaceFactory) secDom.CreateInstance(
      "LiveInterface", "LiveCode.LiveInterface.LiveInterfaceFactory" ).Unwrap();

  // with the help of this factory, we can now create a real 'LiveClass' instance
  object[] constructArgs = new object[] { textConstruct.Text };
  ILiveInterface ilive = factory.Create( "LiveAssembly.dll",
      "LiveCode.LiveSample.LiveClass", constructArgs );

  // finally, we call the 'LiveClass' instance method using our ILiveInterface
  textReturned.Text = ilive.ModifyString( textParam.Text );

  // release all references to the factory and ILiveInterface
  factory = null;
  ilive = null;

  // unload the complete secondary app-domain
  AppDomain.Unload( secDom );
}

The string returned by ModifyString() is made visible in the bottommost TextBox. All source code is included in the download. With VS.NET, just load the solution \LiveApp.sln

MultiPlug sample

Another sample is included to show a generic use of plug-ins. It is just a visualisation of the presented component concept. You can interactively create application domains, load assemblies and instantiate plug-in classes. Finally, you could release the instances and unload a complete AppDomain.

MultiPlug Sample Screenshot

An important hint if you extend the component model: Please be sure to specify the class interface inheritance list by placing the plug-in interfaces at the beginning:

C#
public class YourPluginClass : MarshalByRefObject, IPlugInterface,
			   IYourLocalInterface

If you write the wrong order:

C#
public class YourPluginClass : MarshalByRefObject, IYourLocalInterface,
			   IPlugInterface

your plugin-assembly will be attached to the primary domain and thus be locked.

Limitations

  • Microsoft.CSharp.Compiler.Compile needs the multithreaded apartment => [MTAThread]
  • plugin namespace- and class names have to be fixed or e.g. 'registered' in any way.

License

This article, along with any associated source code and files, is licensed under A Public Domain dedication


Written By
Web Developer
Switzerland Switzerland
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralMy vote of 5 Pin
Manoj Kumar Choubey26-Feb-12 18:22
professionalManoj Kumar Choubey26-Feb-12 18:22 
GeneralMy vote of 5 Pin
Matt Esterak17-Mar-11 1:47
Matt Esterak17-Mar-11 1:47 
GeneralQuestion to NETMaster Pin
salieri118-Jul-06 23:12
salieri118-Jul-06 23:12 
Hi Smile | :)
First of all I have to say that it's very usefull article!
But I have one question about this:

I want to build web site with option to load new controls == dll's.

I want to implement interface approach you used, but the problem is:
On my web site (client who will use DLL's) i created the same interface that component will implement - in order to have an reference to interface.
I have to create this interface on my web site - in order to compile the project.

But when I am casting my dynamically created object to Interface, i have either null (when using AS) or InvalidCastException (when using old style casting).

I have tryed so much ways... My interface both on client and in component are outside namespaces...But still i can't obtain reference to interface using your approach. In your example Interface was a part of the project, so maybe it's impossible to do this in Web???

If you have some ideas i will be glad to hear it
thanks,

regards

QuestionCan't compile two times Pin
fluminis25-Nov-05 23:08
fluminis25-Nov-05 23:08 
AnswerRe: Can't compile two times Pin
nuki0713-Jun-06 2:42
nuki0713-Jun-06 2:42 
GeneralRe: Can't compile two times Pin
Rafael Nicoletti25-Jul-06 3:27
Rafael Nicoletti25-Jul-06 3:27 
GeneralThreading Pin
Jonas Beckeman4-Aug-05 10:56
Jonas Beckeman4-Aug-05 10:56 
GeneralRe: Threading Pin
Jonas Beckeman4-Aug-05 11:57
Jonas Beckeman4-Aug-05 11:57 
GeneralObsolete DLL Pin
NETMaster4-Jun-05 21:26
NETMaster4-Jun-05 21:26 
Generalquestion of globalization Pin
lein0012-Feb-05 9:41
susslein0012-Feb-05 9:41 
GeneralWhy the Factory Pin
mikeperetz11-Jan-05 12:06
mikeperetz11-Jan-05 12:06 
GeneralRe: Why the Factory Pin
NETMaster12-Jan-05 9:03
NETMaster12-Jan-05 9:03 
GeneralRe: Why the Factory Pin
Tomerland18-Apr-07 3:49
Tomerland18-Apr-07 3:49 
Generalproblem using this idea in a plugin Pin
whatever1192613-Jan-04 6:06
whatever1192613-Jan-04 6:06 
GeneralRe: problem using this idea in a plugin Pin
Anonymous14-Jan-04 9:20
Anonymous14-Jan-04 9:20 
GeneralUsing in ASP.Net Pin
LPLMG8-Dec-03 14:22
LPLMG8-Dec-03 14:22 
Generalvs process Pin
osto5-Dec-03 18:51
osto5-Dec-03 18:51 
GeneralRe: vs process Pin
Nahor22-Dec-03 10:43
Nahor22-Dec-03 10:43 
GeneralRe: vs process Pin
chriskoiak16-Feb-04 4:39
chriskoiak16-Feb-04 4:39 
GeneralIs there another way Pin
neutral7-Nov-03 12:25
neutral7-Nov-03 12:25 
GeneralGlobalization Pin
Member 52106017-Aug-03 21:34
Member 52106017-Aug-03 21:34 
GeneralTranslation to VB.NET Pin
Luharhar15-Jul-03 13:02
Luharhar15-Jul-03 13:02 
GeneralRe: Translation to VB.NET Pin
aaava15-Nov-07 11:02
aaava15-Nov-07 11:02 
GeneralExcellent article but... what about Windows Forms? II Pin
Anonymous11-Jun-03 1:46
Anonymous11-Jun-03 1:46 
GeneralEmulate a Mouse event Pin
Amauw Scritz7-Apr-03 8:25
Amauw Scritz7-Apr-03 8:25 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.