DLR like hosting

90 views
Skip to first unread message

dave...@gmail.com

unread,
Apr 7, 2012, 2:28:21 PM4/7/12
to cs-s...@googlegroups.com
CS-Script Tribe,

I'm looking to see if the following is possible. In the past I've successfully run the Noesis Javascript DLR inside my VS2010 project and would like to know if the same treatment is possible in CS-Script. If I try the following - the script cannot find the HostContainer namespace... I just need a tiny push to get over this initial hurdle.

Thanks,
Dave Cline

namespace Testpad {

public class Program {
  private static void Main(string[] args) {
   HostContainer host = new HostContainer();
    host.Run();
}
}

public interface IScript {
    void SetHost(HostContainer host);
    void HandleTick();
    void EmitStopRequest();
}

public class HostContainer {
    Timer timer = new Timer();
    IScript script;
    bool isRunning = true;

    public void Run() {
        string scriptCode = GetScript();
        string extraNS = Assembly.GetCallingAssembly().Location;
        string scriptAssembly = CSScript.CompileCode(scriptCode, nulltruenew string[] { extraNS });
        var helper = new AsmHelper(scriptAssembly, nulltrue);

        this.script = helper.CreateAndAlignToInterface<IScript>("*");
        this.script.SetHost(this);

        this.timer.Tick += new EventHandler(timer_Tick);
        this.timer.Interval = 100;
        this.timer.Start();
        while (isRunning) {
            System.Threading.Thread.Sleep(100);
        }
    }

    private void timer_Tick(object sender, EventArgs e) {
        script.HandleTick();
    }

    public void HandleStopRequest() {
        this.isRunning = false;
    }

    public void LogMessage(string msg) {
        // Write msg to text box 
    }

    private string GetScript() {
        return @"
        using System;
        public class Script : MarshalByRefObject {
            HostContainer host{ get; set; }
            long counter = 0;
                
            public void SetHost(Host host){
                this.host = host;
            }

            public void HandleTick(){
                this.host.LogMessage(@""script tick"");
                counter++;
                if (counter > 100)
                    this.host.HandleStopRequest();
            }
        }";
    }
}
}

Oleg Shilo

unread,
Apr 8, 2012, 5:21:05 AM4/8/12
to cs-s...@googlegroups.com
Hi Dave,

There are a few problems with your code.

- In the script "SetHost" parameter declared as Host, which does not exist. It has to be HostContainer.
- Your "using namespaces" in the script code missing Testpad namespace, which is required for the HostContainer type.
- Your script does not implement "EmitStopRequest" so it cannot be aligned to the interface.
- You timer is not firing Tick events. I do not know why.

And there are a few potential improvements.
- You do not need to pass the host assembly with "extraNS". By default the host and the script share the same set of laoded assemblies. Including the host assembly itself.
- You do not need to "align" to interface. This technique is useful when the script has no knowledge about the host types and therefore you cannot use typecasting. However in your case the script is fully aware about the host and it can just inherit from IScript so you can use simple typecasting.
- You instantiate AsmHelper in the way that implies further script unloading. However you do not dispose the AsmHelper instance, what suggests that you are not really concern about the script unloading. If it is indeed the case you do not need use AsmHelper at all and tehre is no need to inherit the script from MarshalByRefObject .

If the proposed improvements are acceptable for your requirements then your script loading code can be as simple as follows:

Host:
this.script = (IScript)CSScript.LoadCode(scriptCode).CreateObject("*");
this.script.SetHost(this);

Script:
using System;
using Testpad;

public class Script : IScript {
    HostContainer host{ get; set; }
    ......

You may also want to have a look at <cs-script>\Samples\Hosting folder containing hips of Hosting samples.

Cheers,
Oleg Shilo

dave...@gmail.com

unread,
Apr 8, 2012, 1:59:59 PM4/8/12
to cs-s...@googlegroups.com, osh...@gmail.com
Oleg,

A thousand thanks upon you kind sir.

I've updated the code (below), and now get results. I'm not quite sure how to handle the threading, of which I think there may be at least 2 maybe 3 threads going on here. Running the below code - I get the timer prints, but the script callback into the HostContainer does not hook into the actual object host it would seem - the timer is null in the HandleStopRequest(); I'm checking out your other hosting examples as we speak... 

Ultimately I want to be able to put this into a winform and perform, Edit/Run, Edit/Run cycles at will so I would guess the need to manage appdomains is necessary. I'll be immersing myself in your fine project to see how far it takes me in my intended direction - as for now, I'm a pink skinned noob.

Thanks for your magnanimous efforts,
Dave Cline


namespace Testpad {

public class ProgramHost {

    private static void Main(string[] args) {
        HostContainer hostContainer = new HostContainer();
        hostContainer.Initialize();
        Thread thread = new Thread(hostContainer.Run);
        thread.Start();

        Console.Out.WriteLine("Press any key to stop the execution ...");
        Console.ReadKey();
        thread.Abort();
        hostContainer.Terminate();

    }
}

public interface IScript {
    void SetHost(HostContainer host);
    void HandleTick(DateTime time);
    void EmitStopRequest();
}

[Serializable]
public class HostContainer {
    System.Threading.TimerCallback callback;
    System.Threading.Timer timer;
    IScript script;
    [NonSerialized]
    AsmHelper asmHelper;

    public void Initialize() {
        string scriptCode = GetScript();
        string scriptAssembly = CSScript.CompileCode(scriptCode, nulltrue);
        this.asmHelper = new AsmHelper(scriptAssembly, nulltrue);
        this.script = this.asmHelper.CreateAndAlignToInterface<IScript>("*");
        this.script.SetHost(this);
    }

    public void Run() {
        this.callback = new TimerCallback(timer_Tick);
        this.timer = new System.Threading.Timer(callback, null, 1000, 1000);
    }

    public void Terminate() {
        this.asmHelper.Dispose();
    }

    private void timer_Tick(object state) {
        script.HandleTick(DateTime.Now);
    }

    public void HandleStopRequest() {
        this.timer = null;
    }

    public void LogMessage(string msg) {
        Console.WriteLine(msg);

    }

    private string GetScript() {
        return @"
    using System;
    using Testpad;

    public class Script : MarshalByRefObject {
        HostContainer host{ get; set; }
        long counter = 0;
                
        public void SetHost(HostContainer host){
            this.host = host;
        }
            
        public void HandleTick(DateTime time){
            this.host.LogMessage(string.Format(@""{0:HH:mm:ss.fff} Script tick"", time));
            counter++;
            if (counter > 3)
                this.EmitStopRequest();
        }
            
        public void EmitStopRequest(){
            this.host.LogMessage(@""Script done!"");
            this.host.HandleStopRequest();
        }
    }";
    }
}
}


On Sunday, April 8, 2012 2:21:05 AM UTC-7, Oleg Shilo wrote:
Hi Dave,

There are a few problems with your code.

- In the script "SetHost" parameter declared as Host, which does not exist. It has to be HostContainer.
- Your "using namespaces" in the script code missing Testpad namespace, which is required for the HostContainer type.
- Your script does not implement "EmitStopRequest" so it cannot be aligned to the interface.
- You timer is not firing Tick events. I do not know why.

And there are a few potential improvements.
- You do not need to pass the host assembly with "extraNS". By default the host and the script share the same set of laoded assemblies. Including the host assembly itself.
- You do not need to "align" to interface. This technique is useful when the script has no knowledge about the host types and therefore you cannot use typecasting. However in your case the script is fully aware about the host and it can just inherit from IScript so you can use simple typecasting.
- You instantiate AsmHelper in the way that implies further script unloading. However you do not dispose the AsmHelper instance, what suggests that you are not really concern about the script unloading. If it is indeed the case you do not need use AsmHelper at all and tehre is no need to inherit the script from MarshalByRefObject .

If the proposed improvements are acceptable for your requirements then your script loading code can be as simple as follows:

Host:
this.script = (IScript)CSScript.LoadCode(scriptCode).CreateObject("*");
this.script.SetHost(this);

Script:
using System;
using Testpad;

public class Script : IScript {
    HostContainer host{ get; set; }
    ......

You may also want to have a look at <cs-script>\Samples\Hosting folder containing hips of Hosting samples.

Cheers,
Oleg Shilo

Oleg Shilo

unread,
Apr 8, 2012, 11:47:36 PM4/8/12
to cs-s...@googlegroups.com
Hi Dave,

I have adjusted your script and you can grab it from DropBox:  http://dl.dropbox.com/u/956512/Dave%20Cline/new%20script%20(7).cs

The changes are: 
1 - Major: replaced "[Serializable]" with ": MarshalByRefObject". This is the correct way of passing the references between AppDomains.
2 - Very minor: replaced thread with ThreadPool. Threadpool is always "IsBackgrount==true" so no need to terminate the thread.
3 - Very minor : replaced "Aligning" with typecasting. In your scenario Aligning offers you no benefits what so ever but it does create the proxy type unnessesary in your case.
  
Cheers,
Oleg

Dave Cline

unread,
Apr 9, 2012, 12:10:25 AM4/9/12
to cs-s...@googlegroups.com, osh...@gmail.com
Oleg,

A thing of beauty is a joy forever.

Thanks for your rapt attention for one who must be a very busy fellow, your time is a seldom and unexpected gift of that I'm sure. 

Now, if I could only figure out why this code does not work. This timer does not seem to be the same instance of the timer the script is using. No matter, I'll dig for a while and if I can't find an answer, I'll move on to the WinForm model. I'll do that anyway.

public void HandleStopRequest() {
this.timer = null;
}

I hope this host referencing model is something that you might be able to add to your examples. If it is already there, in near form, then I apologize for the duplication and my lack of investigative skills.

Kind regards,
Dave Cline


On Sunday, April 8, 2012 8:47:36 PM UTC-7, Oleg Shilo wrote:
Hi Dave,

Oleg Shilo

unread,
Apr 9, 2012, 4:49:31 AM4/9/12
to cs-s...@googlegroups.com
Hi Dave,

>Thanks for your rapt attention for one who must be a very busy fellow, your time is a seldom and unexpected gift of that I'm sure. 
It is not a problem. I am glad to help.

>Now, if I could only figure out why this code does not work. 
The key difference between your code and the code I put on DropBox for you is the way the host instance is passed to the script. In your code it is done through serialization, which  creates a copy of your host object. In my code it is the the reference to the single instance of the HostContainer is passed to the script.


Inter AppDomain communication can often be tricky. That is why you use multiple AppDomains only if you have to. In the case of scripting it is a forced measure due to the well-known CLR limitation - inability to unload loaded assemblies. 

While this limitation has nothing to do with scripting as such CS-Script offers separate AppDomain for the script execution as a work-around. It is consistent with the official MS recommendations and it works... but it comes with the price - one has to be very careful planing the AppDomain type model.

Thai is why I recommended you to consider executing the scripts in the main AppDomain bringing the host and the script together so you do not need to worry about passing the references to the script and back. But of course you need to decide if such a hosting model can meet your requirements.

The sample is actually there: <cs-script>\Samples\Hosting\HostingSimplified. This sample demonstrates how to pass the host reference to the script and how to execute the script in the different AppDomain (ExecuteAndUnloadScript) or in the same one (ExecuteScript). 

Cheers,
Oleg Shilo
--------------------------------------------------------------------------------------------
Internet: http://www.csscript.net
E-Mail: csscript...@gmail.com
Reply all
Reply to author
Forward
0 new messages