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

Re: Cross AppDomain Communication, Scripting by Dave

Dave
Wed Mar 23 20:26:02 CST 2005

The serializable attribute will marshal classes across app domains by value, i.e. a copy will be made. To pass an object byref, it
must inherit from MarshalByRefObject to let the marshaler know that you want a proxy made to marshal the return values back to the
calling instance.

See if that works :)

--
Dave Sexton
dave@www..jwaonline..com
-----------------------------------------------------------------------
"MatthewRoberts" <mroberts_hm@hotmail.com> wrote in message news:1111622687.919382.302660@o13g2000cwo.googlegroups.com...
> 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
>



Re: Cross AppDomain Communication, Scripting by MatthewRoberts

MatthewRoberts
Thu Mar 24 13:15:04 CST 2005

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?


Re: Cross AppDomain Communication, Scripting by Dave

Dave
Thu Mar 24 19:00:32 CST 2005

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" <mroberts_hm@hotmail.com> wrote in message news:1111691704.049708.213900@o13g2000cwo.googlegroups.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?
>