471,325 Members | 1,438 Online
Bytes | Software Development & Data Engineering Community
Post +

Home Posts Topics Members FAQ

Join Bytes to post your question to a community of 471,325 software developers and data experts.

Cross AppDomain Communication, Scripting

Howdy All,

I am having difficulty with two-way communication across AppDomains in
an attempt to dynamically script applications. Everything works as
expected, except when using ByRef parameters.

The below explanation is lengthy, but well worth the read. If you can
help me, I'd gladly share this code which has greatly helped my
development of extensible applications.

I have developed an easy to use scripting engine for .NET that handles
loading and unloading of a secondary AppDomain automatically. I call
the object "SafeScriptingEngine", and it has properties for the source
language, source code, and any needed references and imports. When its
compile method is called, the code is compiled by the appropriate
compiler, then it creates a new AppDomain, loads the compiled code into
the AppDomain, and uses a Class Factory approach to return a well-known
interface.

This simple interface contains two functions called "MethodExists",
which accepts a function name and a list of parameters then return a
Boolean of whether the function exists, and "CallMethod", which again
accepts a function name and a list of parameters then actually calls
the function.
Public Interface IRemoteLoader
Function Invoke(ByVal methodName As String, ByVal parameters() As
Object) As Object
Function MethodExists(ByVal methodName As String, ByVal parameters()
As Object) As Boolean
End Interface
So, an example of using this engine looks like:
Dim sse As New SafeScriptingEngine()
sse.Language = Languages.VisualBasic
sse.SourceCode = "" & _
"Public Function UpperString(ByVal s As String) As String" & _
" Return s.ToUpper" & _
"End Function"
If sse.Compile() Then
If sse.MethodExists("UpperString", New Object() { "" }) Then
MsgBox(sse.CallMethod("UpperString", New Object() { "this
string will be converted to uppercase" }))
End If
End If
All of this works great. There are no memory leaks, and all compilers,
AppDomains, and assemblies are unloaded as desired and expected. In
fact, I designed the engine to be inheritable such that functions can
be created that are strongly typed. As you can see, the default engine
expects a non-strongly-typed array of Objects.

To prove the usability of the engine, I created a "SimpleStringScript"
which inherits from the "SafeScriptEngine". The "SimpleStringScript"
accepts only the part of code that does the manipulation, such as
"s.ToUpper" in the example above. In the background, it adds function,
parameters, and Return statement. Also, with the "SimpleStringScript",
there is no need for "MethodExists" that accepts parameters. Instead,
it has a "StringMethodExists" function that accepts no parameters,
which then calls "MethodExists" on the base "SimpleStringScript" with a
String Object as the parameter. Likewise, there is no need to call
"CallMethod" with a function name and Object array. Instead, it has a
"CallStringMethod" function that take a String as a parameter, which in
turn calls the base "CallMethod" with the appropriate function name and
the String parameter encased in an Object array.
Public Class SimpleStringScript
Inherits SafeScriptingEngine

Public Function CallStringScript(ByVal s As String) As String
Public Function StringMethodExists() As Boolean
...
End Class
Again, all of this works great, just as desired and expected.

The problem arises when you create a script that uses ByRef parameters.
I would like a function that looks like:
Public Sub MyCustomFunction(ByRef e as MyEventArgs)
e.MyObject.MyProperty = "some value"
End Sub
I quickly discovered that any parameter being passed into another
AppDomain must be Serializable, as do any objects that are referenced
in its hierarchy. Otherwise, it will throw a SerializationException
when you try to pass in the parameter. So, I changed all objects that
are referenced in the EventArgs to be Serializable, and that solved the
problem when calling. However, upon return to the calling AppDomain,
the objects and values in "e" are still the same as before the call.
None of the manipulations taking place in the script from the secondary
AppDomain are carried back to the calling AppDomain.

I figure this is because, as expected with the Serializable objects,
the "e" is being serialized, sent across the AppDomain boundary, then
deserialized. When it is deserialized, an entirely new object is
created. Changes made are not sent back across the boundary simply by
using ByRef arguments. Unfortunately, I'm not sure exactly how to solve
this problem.

I think I should be sending an Interface as my argument, rather than an
object of MyEventArgs. As long as the Interface is well-known in both
AppDomains, I think the scripting AppDomain should know what to call,
and when it does, the call should be marshaled back to the primary
AppDomain via the Interface. Basically, the Interface is just a pointer
back to the actual object. Can anyone confirm this to be True or False?

If False, please HELP!

If True, then is it possible to simply wrap the MyEventArgs object into
an Interface, such as "IMyEventArgs", then change the scripted function
to accept the Interface rather than the object, then pass an Interface
rather than an object into the method? Would that suffice, so long as
both AppDomains are aware of the Interface and anything it references?
Obviously, if the Interface referenced a type that is not loaded in the
secondary AppDomain, then there would be problems because the script
would not know how to use it.

Or, do I have to wrap the EventArgs into an Interface, as well as wrap
every object that it references into an Interface. Meaning, again using
the example above, would I also need to create an "IMyObject"
Interface?

My workaround for the time being is to return the object being
manipulated as the return value, that way, the new object (created from
deserialization) is again serialized upon return. However, this
requires the scripter to know that they need to return it. Using C#,
that is no big deal because the compiler will tell you when there is
nothing returned. However, in Visual Basic, if you fail to supply a
return value, you simply get Nothing. But, since I will be writing the
scripts, I'll deal with the workaround for now. But, I sure would like
ByRef to work across AppDomains.

Any suggestions?

Thanks in advance,
Matthew

___________________________________
Matthew Roberts
Framework Architect
Business Process Solutions
SOURCECORP
Dallas, TX 75204
matthewroberts (at) srcp (dot) com

Jul 21 '05 #1
1 3865
I understand your dillema.

By inheriting from MarshalByRefObject you are indeed marshaling using a proxy so that the "service" instance will reflect any
changes made on the client, however, any members of your class must also be serialized.

The members must also be serializable in order to serialize the container, so they can be marshalled by value or by reference. This
means that they too must inherit from MarshalByRefObject to be marshaled by reference.

Yes, it's a limitation because you can only inherit from one object. You can use the ISerializeable interface to provide your own
serialization implementation, but this does not allow the Marshaller to create a proxy for your object. This is a by-value
implementation only, if I understand it correctly.

I'm sorry I can't offer much more on remoting. You may want to start posting directly in the remoting forums to see if there are
other solutions which still reside within those realms, although the built-in remoting framework is not your only option.

You can use sockets on a lower level to provide "custom" marshalling. Maybe create your own ScriptingServiceMarshaler that can
handle sending data to another ScriptingServiceMarshaler instance and block the thread until a response is received with the
appropriate call-back values.

I'd like to hear from other forum readers/writers on this topic. I may have skipped information that can help you, so don't
consider my response the be-all and end-all of your project. Maybe remoting is more robust then I give it credit for?

I must say, that "eval" type-functionality, as can be used in scripting languages, has probably not been implemented inside the
framework due to performance and security issues. Although ASP.NET performs runtime-compilation, it's expected that the application
has been built by developers, not end-user code. If your using this for any purpose other than serializing code at runtime that
will NOT be know before hand and for some reason cannot be "plugged" in to the main application through runtime-loaded assemblies,
then I would consider this as an option, otherwise explore a different solution. If the issue is simply cross-AppDomain execution,
the remoting framework can solve your problem without the use of "dynamic" code.

Security is a big factor to consider. "dynamic" code may be subject to injection attacks and other misuses of the framework if you
let an end-user enter the code. You'll have to consider a potentially large amount of CAS and probably impersonation on your
dynamic assemblies in order to prevent end-users from entering malisious code that can be executed with the trust of the main
assembly. Just running them in their own AppDomain may not be enough. That may prevent your main app from crashing, but not
securing the system.

I don't mean to lecture you, I'm probably out of place... but I've mentioned it for the benefit of other forum readers so they are
aware of the potential side-effects.

--
Dave Sexton
dave@www..jwaonline..com
-----------------------------------------------------------------------
"MatthewRoberts" <mr*********@hotmail.com> wrote in message news:11**********************@o13g2000cwo.googlegr oups.com...
Thanks for the reply. Is that the only way to accomplish what I want?
Will use of interfaces not do the trick for two-way communication?

There are many objects referenced in my chain, so would all of them
have to inherit from MarshalByRefObject, or only the top parent object?
If so, that is going to be impossible, and therefore I will not be able
to do what I want. Some of the objects already inherit from other
objects, which means they would lose functionality if inheriting from
anywhere else.

Just to make sure we are on the same page, I do inherit from
MarshalByRefObject for by RemoteLoaderFactory, which is basically the
scripting "engine" that is CREATED in the secondary AppDomain, but that
is USED in the primary AppDomain. A reference to my IRemoteLoader
interface is returned by the factory to the primary AppDomain so that
it can then control the scripting AppDomain. But, what you are saying
is that even my parameters that are sent to my script in the secondary
AppDomain should inherit from MarshalByRefObject?

There must be an easier way to accomplish this type of scripting
functionality. I wish ByRef would just do the trick. Any other
suggestions?

Jul 21 '05 #2

This discussion thread is closed

Replies have been disabled for this discussion.

Similar topics

3 posts views Thread by Andy Fish | last post: by
7 posts views Thread by Venkat | last post: by
1 post views Thread by cs | last post: by
7 posts views Thread by Scott M. | last post: by
3 posts views Thread by MatthewRoberts | last post: by
4 posts views Thread by Jacob JKW | last post: by

By using Bytes.com and it's services, you agree to our Privacy Policy and Terms of Use.

To disable or enable advertisements and analytics tracking please visit the manage ads & tracking page.