Serialization problem - c#

We save and read files by (de) serializing a class named "DocumentClass".
All was working well, untill we added 2 more fields to the documentclass. (We think that's the problem)
When we now try to open files that were serialized by the previous edition, we get an error.
System.ArgumentException: Object of type 'System.Int32' cannot be converted to type 'System.String'.
at SoftwareProject.Componenten.Bestand.DocumentClass.d(String A_0)
at de..ctor(String A_0)
at g.a(String A_0)
The method generating the error is the method "Read". (DocumentClass.d() is the obfuscated name )
But things get weirder: when we open the file in VS debug mode, no error is generated, but all fields in the documentclass are 0 or null ???
We are lost here ... please help ...
We've added the [OptionalField] attribute to the new fields, but that doesn't help ..
Why are all values null in debug mode ??
And where is the runtime error coming from ? How can we debug it ?
Thanks in advance!!
public static DocumentClass Read(string fullFilePath)
{
DocumentClass c = new DocumentClass();
Stream s = File.OpenRead(fullFilePath);
BinaryFormatter b = new BinaryFormatter();
//b.AssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Simple;
b.Binder = new MyCustomBinder();
try
{
c = (DocumentClass)b.Deserialize(s);
}
catch( Exception exc )
{
s.Close();
throw exc;
}
finally
{
s.Close();
}
return c;
}
public class MyCustomBinder : SerializationBinder {
public override Type BindToType(string assemblyName, string typeName) {
Type tyType = null;
string sShortAssemblyName = assemblyName.Split(',')[0];
Assembly[] ayAssemblies = AppDomain.CurrentDomain.GetAssemblies();
if (sShortAssemblyName.ToLower() == "debugAssemblyName")
{
sShortAssemblyName = "AppAssemblyName";
}
foreach (Assembly ayAssembly in ayAssemblies) {
if (sShortAssemblyName == ayAssembly.FullName.Split(',')[0]) {
tyType = ayAssembly.GetType(typeName);
break;
}
}
return tyType;
}
}

.Net has something called "Version Tolerant Serialization" which most likely solves this issue ;)
You should check out this easy to understand example on object serialization:
http://programming.flashadventures.com/c-sharp/writing-objects-to-files-serialization/

I assume you are using BinaryFormatter? This serializer is notoriously brittle, since it (by default) includes the field-name in the stream; this impacts obfuscation particularly badly. Presumably the obfuscator is now choosing new named for the fields (perhaps at random, perhaps due to the new fields), and so it can't deserialize correctly.
A few options:
don't obfuscate the DTO
implement ISerializable so the field names don't matter
use a serializer that doesn't care about field names
I'd personally opt for the latter, but I'm a bit biased ;-p I know of people using protobuf-net with obfuscated classes; the data only includes numeric markers, so the meaning isn't really exposed (except of course, by inspection of the data - post codes etc, but that is the job of encryption).

I think you need to use Custom Serialization

Related

C# programmatically edit my .NET assembly at runtime

I have a .NET assembly that is built by me but would like to be able rewrite the .DLL with some minor but arbitrary attribute change file at runtime. Specifically I would like to be able to change a property of an attribute of a class so that I can customize the binary depending on the situation.
To illustrate, I want to achieve the effect of editing the assembly being generated from the code
[SomeAttribute("Name")]
public class MyClass{
...
such that the new assembly is functionally the same as
[SomeAttribute("Custom Name")]
public class MyClass{
...
And this "Custom Name" could be anything (determined at runtime). Is this possible to do at runtime?
The reason why the actual .DLL needs to be modified is because it will get loaded up by a seperate process which cannot determine the runtime information (I do not control this process).
Experimentation so far has shown that it seems to work if the new "Custom Name" is the same length as the original, but not otherwise (even if you edit the preceding byte that specifies the length; presumably there are offsets stored in the file somewhere).
EDIT: Forgot to mention, solution needs to be under the .NET 2 framework as well.
Unclear what you really want to do (XY problem?)
Still, if you want to modify an assembly, you normally use Mono.Cecil that self-describes as: you can load existing managed assemblies, browse all the contained types, modify them on the fly and save back to the disk the modified assembly. .
Note that an attribute can contain extra data on top of the data that is passed as a parameter:
public class MyAttribute : Attribute
{
public MyAttribute(string str)
{
argument = str;
}
private string argument;
public string Argument { get; }
public string AssemblyName
{
get
{
return Assembly.GetEntryAssembly().FullName;
}
}
}
[MyAttribute("Hello")]
class Program
{
static void Main(string[] args)
{
var attr = typeof(Program).GetCustomAttribute<MyAttribute>();
Console.WriteLine(attr.Argument);
Console.WriteLine(attr.AssemblyName);
}
}
Using the extremely helpful suggestion from #xanatos I have made this solution:
Under .NET 2, you can install package Mono.Cecil 0.9.6.1.
The code then is as follows:
AssemblyDefinition assbDef = AssemblyDefinition.ReadAssembly("x.dll");
TypeDefinition type = assbDef.MainModule.GetType("NameSpace.MyClass").Resolve();
foreach (CustomAttribute attr in type.CustomAttributes)
{
TypeReference argTypeRef = null;
int? index = null;
for (int i = 0; i < attr.ConstructorArguments.Count; i++)
{
CustomAttributeArgument arg = attr.ConstructorArguments[i];
string stringValue = arg.Value as string;
if (stringValue == "Name")
{
argTypeRef = arg.Type;
index = i;
}
}
if (index != null)
{
attr.ConstructorArguments[(int)index] = new CustomAttributeArgument(argTypeRef, newName);
}
}
assbDef.Write("y.dll");
Which will search an assembly for any attribute arguments with value "Name" and replace their value with newName.
Rather than modifying the DLL, you can add attributes at run-time using the TypeDescriptor class; e.g.
TypeDescriptor.AddAttributes(typeof(MyClass), new SomeAttribute("Custom Name"));
The only caveat with this approach is that code which relies purely on reflection will not be able to read the added attribute(s) - you must use TypeDescriptor.GetAttributes(). However, most of the built-in .NET Framework methods that operate on attributes are aware of metadata added at run-time.
https://msdn.microsoft.com/en-us/library/system.componentmodel.typedescriptor_methods(v=vs.110).aspx

What mistake am I making when serializing?

This leads to serialization exception at runtime. It's just a demo project to test the best way to do this. I included the main method and the class which im trying to serialize.
Ignore: I really cant add more detail, i've described the problem, attached the code, this "please add more details" thing is the stupidest thing ever. Let me post it already.
Data toSend = new Data();
toSend.Output();
///SERIALIZE
BinaryFormatter formatter = new BinaryFormatter();
Stream streamOut = File.OpenWrite("file");
formatter.Serialize(streamOut, toSend);
streamOut.Close();
Console.WriteLine("----------------------------");
///DESERIALIZE
Stream streamIn = File.OpenRead("file");
Object received = formatter.Deserialize(streamIn);
Data toReceive = (Data)received;
toReceive.Output();
class Data : ISerializable
{
int integerData;
string stringData;
bool booleanData;
int shouldnotbeserialized;
public Data()
{
integerData = 1;
stringData = "Hello";
booleanData = true;
shouldnotbeserialized = 55;
}
//To deserialize
public Data(SerializationInfo info, StreamingContext context)
{
integerData = info.GetInt32("theint");
stringData = info.GetString("thestring");
booleanData = info.GetBoolean("thebool");
}
public void Output()
{
Console.WriteLine(integerData);
Console.WriteLine(stringData);
Console.WriteLine(booleanData);
Console.WriteLine(shouldnotbeserialized);
}
//Implemented method to serialize
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("thestring", stringData);
info.AddValue("theint", integerData);
info.AddValue("thebool", booleanData);
}
}
Exception message:
Type 'SerializationDemo.Data' in Assembly 'SerializationDemo,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' is not marked
as serializable.
The answer is given to you in the exception message:
Type 'SerializationDemo.Data' in Assembly 'SerializationDemo,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' is not marked
as serializable.
You need to mark your class with the Serializable attribute.
[Serializable()]
class Data : ISerializable
From the context it seems like you are going to be transmitting the object down a network (deduced due to the local variable names toSend, toReceive). You need to be aware that, if you where for example, using a client-server model, an object serialized and sent from the server software will not be deserializable from the client software by default.
This is because the fundamental characteristic of binary serialization is that it preserves type system fidelity. Type system fidelity denotes that type information is not lost in the serialization process. This means that when you serialize an object, is is not only the "object's state" written to the data stream, but also the name of the class and the containing assembly. Even if you defined the class Data in the assembly that is going to deserialize the data, the deserialize method will fail (throw an exception) because the deserializer will be looking for the type Program1.Namespace.Data not Program2.Namespace.Data. To remedy this you can define your data messages in a mutual assembly (class library) or by defining a SerializationBinder that will allow you to basically trick the deserialize method into thinking you are still in the same assembly.

MvvmCross ViewTypeResolver doesn't resolve Tag (fragment or custom type)

Actual situation is, that I added to the MvxViewTypeResolver class the "Fragment"-Case, so it does look like this:
#region Copyright
// <copyright file="MvxViewTypeResolver.cs" company="Cirrious">
// (c) Copyright Cirrious. http://www.cirrious.com
// This source is subject to the Microsoft Public License (Ms-PL)
// Please see license.txt on http://opensource.org/licenses/ms-pl.html
// All other rights reserved.
// </copyright>
//
// Project Lead - Stuart Lodge, Cirrious. http://www.cirrious.com
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Android.Views;
using Cirrious.MvvmCross.Binding.Android.Interfaces.Binders;
namespace Cirrious.MvvmCross.Binding.Android.Binders
{
public class MvxViewTypeResolver : IMvxViewTypeResolver
{
private Dictionary<string, Type> _cache = new Dictionary<string, Type>();
public IDictionary<string, string> ViewNamespaceAbbreviations { get; set; }
#region IMvxViewTypeResolver Members
public virtual Type Resolve(string tagName)
{
Type toReturn;
if (_cache.TryGetValue(tagName, out toReturn))
return toReturn;
var unabbreviatedTagName = UnabbreviateTagName(tagName);
var longLowerCaseName = GetLookupName(unabbreviatedTagName);
var viewType = typeof(View);
#warning AppDomain.CurrentDomain.GetAssemblies is only the loaded assemblies - so we might miss controls if not already loaded
var query = from assembly in AppDomain.CurrentDomain.GetAssemblies()
from type in assembly.GetTypes()
where viewType.IsAssignableFrom(type)
where (type.FullName ?? "-").ToLowerInvariant() == longLowerCaseName
select type;
toReturn = query.FirstOrDefault();
_cache[tagName] = toReturn;
return toReturn;
}
private string UnabbreviateTagName(string tagName)
{
var filteredTagName = tagName;
if (ViewNamespaceAbbreviations != null)
{
var split = tagName.Split(new char[] {'.'}, 2, StringSplitOptions.RemoveEmptyEntries);
if (split.Length == 2)
{
var abbreviate = split[0];
string fullName;
if (ViewNamespaceAbbreviations.TryGetValue(abbreviate, out fullName))
{
filteredTagName = fullName + "." + split[1];
}
}
}
return filteredTagName;
}
#endregion
protected string GetLookupName(string tagName)
{
var nameBuilder = new StringBuilder();
switch (tagName)
{
case "View":
case "ViewGroup":
nameBuilder.Append("android.view.");
break;
case "fragment":
nameBuilder.Append("android.app.");
break;
default:
if (!IsFullyQualified(tagName))
nameBuilder.Append("android.widget.");
break;
}
nameBuilder.Append(tagName);
return nameBuilder.ToString().ToLowerInvariant();
}
private static bool IsFullyQualified(string tagName)
{
return tagName.Contains(".");
}
}
}
Now it is submitting the correct longLowerCaseTagName (android.app.fragment) but in the query it isn't able to resolve the type.
My suggestion is, that the fragment-control isn't loaded when the type should be resolved. Maybe there is an other way to get the type resolved?
Also if I add a custom type (giving the tag Mvx.MyCustomType in the axml) it doesn't get resolved. Do I have to add something in the MvxBindingAttributes.xml in this case?
Thanks for the help!
First an explanation of the code:
The custom XML inflater factory used by the MvvmCross Binder tries to load Views in a very similar way to the standard 2.x Android XML inflater.
The default code for the view type resolution is indeed in: https://github.com/slodge/MvvmCross/blob/master/Cirrious/Cirrious.MvvmCross.Binding/Android/Binders/MvxViewTypeResolver.cs
If your xml contains a name such as <MyCompany.MyProject.MyViews.MyFirstView /> then the view type resolver:
first checks for abbreviations and expands these into full namespaces - by default the only known abbreviation is Mvx. which is expanded to: Cirrious.MvvmCross.Binding.Android.Views.. If you want to add more abbrevations then override ViewNamespaceAbbreviations in https://github.com/slodge/MvvmCross/blob/master/Cirrious/Cirrious.MvvmCross.Binding/Android/MvxBaseAndroidBindingSetup.cs
then checks to see if the unabbreviated name is a non-namespaced name. If it is, then it assumes that the class is the Android namespace and prepends it with android.view. or android.widget.
then converts the fully namespaced name to all lowercase as a case-insensitive lookup key
uses that lowercase key to search all Types which derive from View in all loaded assemblies.
caches the result (whether its null or not) in order to speed up subsequent inflations.
All of this behaviour was designed to match the default Android xml view inflation code in http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/2.3.6_r1/android/view/LayoutInflater.java#LayoutInflater.createViewFromTag%28java.lang.String%2Candroid.util.AttributeSet%29
With that explanation out of the way - here's an answer to your questions:
MvvmCross does not yet currently contain any Fragment support. The official MonoDroid fragment support itself was only released last week, and I've not yet had anybody request fragments - Android "fragmentation" seems to have kept most people back on Activity and Dialog based code.
Briefly ;ooking at the documentation, fragment isn't an Android View - it looks like Fragment inherits directly from Java.Lang.Object - see http://developer.android.com/reference/android/app/Fragment.html
Because of this, there's no way that the MvvmCross ViewTypeResolver will currently work with fragments.
I would suggest that if you need both mvvmcross and fragments today, then your best bet is to replace the default resolver (using IoC) with your own resolver - but I can't offer much advice on this as I haven't yet fully read and understood the droid docs on http://developer.android.com/guide/topics/fundamentals/fragments.html
From my experience in creating the current inflation code, then I think you will find the source essential reading when you do this - e.g. see : http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.0.1_r1/android/view/LayoutInflater.java#LayoutInflater.createViewFromTag%28android.view.View%2Cjava.lang.String%2Candroid.util.AttributeSet%29
I can't give you any information on when official mvvmcross fragment support will be available - it's not something that is currently scheduled.
Custom views are supported, but will not normally live in the Mvx. abbreviated namespace.
They are much more likely to live in your UI application namespace, or in some shared library.
To see a custom view in action, see the PullToRefresh example in the tutorial - https://github.com/slodge/MvvmCross/blob/master/Sample%20-%20Tutorial/Tutorial/Tutorial.UI.Droid/Resources/Layout/Page_PullToRefreshView.axml

.NET assemblies: understanding type visibility

I am trying to reproduce something that System.Xml.Serialization already does, but for a different source of data.
For now task is limited to deserialization only.
I.e. given defined source of data that I know how to read. Write a library that takes a random type, learns about it fields/properties via reflection, then generates and compiles "reader" class that can take data source and an instance of that random type and writes from data source into the object's fields/properties.
here is a simplified extract from my ReflectionHelper class
public class ReflectionHelper
{
public abstract class FieldReader<T>
{
public abstract void Fill(T entity, XDataReader reader);
}
public static FieldReader<T> GetFieldReader<T>()
{
Type t = typeof(T);
string className = GetCSharpName(t);
string readerClassName = Regex.Replace(className, #"\W+", "_") + "_FieldReader";
string source = GetFieldReaderCode(t.Namespace, className, readerClassName, fields);
CompilerParameters prms = new CompilerParameters();
prms.GenerateInMemory = true;
prms.ReferencedAssemblies.Add("System.Data.dll");
prms.ReferencedAssemblies.Add(Assembly.GetExecutingAssembly().GetModules(false)[0].FullyQualifiedName);
prms.ReferencedAssemblies.Add(t.Module.FullyQualifiedName);
CompilerResults compiled = new CSharpCodeProvider().CompileAssemblyFromSource(prms, new string[] {source});
if (compiled.Errors.Count > 0)
{
StringWriter w = new StringWriter();
w.WriteLine("Error(s) compiling {0}:", readerClassName);
foreach (CompilerError e in compiled.Errors)
w.WriteLine("{0}: {1}", e.Line, e.ErrorText);
w.WriteLine();
w.WriteLine("Generated code:");
w.WriteLine(source);
throw new Exception(w.GetStringBuilder().ToString());
}
return (FieldReader<T>)compiled.CompiledAssembly.CreateInstance(readerClassName);
}
private static string GetFieldReaderCode(string ns, string className, string readerClassName, IEnumerable<EntityField> fields)
{
StringWriter w = new StringWriter();
// write out field setters here
return #"
using System;
using System.Data;
namespace " + ns + #".Generated
{
public class " + readerClassName + #" : ReflectionHelper.FieldReader<" + className + #">
{
public void Fill(" + className + #" e, XDataReader reader)
{
" + w.GetStringBuilder().ToString() + #"
}
}
}
";
}
}
and the calling code:
class Program
{
static void Main(string[] args)
{
ReflectionHelper.GetFieldReader<Foo>();
Console.ReadKey(true);
}
private class Foo
{
public string Field1 = null;
public int? Field2 = null;
}
}
The dynamic compilation of course fails because Foo class is not visible outside of Program class. But! The .NET XML deserializer somehow works around that - and the question is: How?
After an hour of digging System.Xml.Serialization via Reflector I came to accept that I lack some kind of basic knowledge here and not really sure what am I looking for...
Also it is entirely possible that I am reinventing a wheel and/or digging in a wrong direction, in which case please do speak up!
You don’t need to create a dynamic assembly and dynamically compile code in order to deserialise an object. XmlSerializer does not do that either — it uses the Reflection API, in particular it uses the following simple concepts:
Retrieving the set of fields from any type
Reflection provides the GetFields() method for this purpose:
foreach (var field in myType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
// ...
I’m including the BindingFlags parameter here to ensure that it will include non-public fields, because otherwise it will return only public ones by default.
Setting the value of a field in any type
Reflection provides the function SetValue() for this purpose. You call this on a FieldInfo instance (which is returned from GetFields() above) and give it the instance in which you want to change the value of that field, and the value to set it to:
field.SetValue(myObject, myValue);
This is basically equivalent to myObject.Field = myValue;, except of course that the field is identified at runtime instead of compile-time.
Putting it all together
Here is a simple example. Notice you need to extend this further to work with more complex types such as arrays, for example.
public static T Deserialize<T>(XDataReader dataReader) where T : new()
{
return (T) deserialize(typeof(T), dataReader);
}
private static object deserialize(Type t, XDataReader dataReader)
{
// Handle the basic, built-in types
if (t == typeof(string))
return dataReader.ReadString();
// etc. for int and all the basic types
// Looks like the type t is not built-in, so assume it’s a class.
// Create an instance of the class
object result = Activator.CreateInstance(t);
// Iterate through the fields and recursively deserialize each
foreach (var field in t.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
field.SetValue(result, deserialize(field.FieldType, dataReader));
return result;
}
Notice I had to make some assumptions about XDataReader, most notably that it can just read a string like that. I’m sure you’ll be able to change it so that it works with your particular reader class.
Once you’ve extended this to support all the types you need (including int? in your example class), you can deserialize an object by calling:
Foo myFoo = Deserialize<Foo>(myDataReader);
and you can do this even when Foo is a private type as it is in your example.
If I try to use sgen.exe (the standalone XML serialization assembly compiler), I get the following error message:
Warning: Ignoring 'TestApp.Program'.
- TestApp.Program is inaccessible due to its protection level. Only public types can be processed.
Warning: Ignoring 'TestApp.Program+Foo'.
- TestApp.Program+Foo is inaccessible due to its protection level. Only public types can be processed.
Assembly 'c:\...\TestApp\bin\debug\TestApp.exe' does not contain any types that can be serialized using XmlSerializer.
Calling new XmlSerializer(typeof(Foo)) in your example code results in:
System.InvalidOperationException: TestApp.Program+Foo is inaccessible due to its protection level. Only public types can be processed.
So what gave you the idea that XmlSerializer can handle this?
However, remember that at runtime, there are no such restrictions. Trusted code using reflection is free to ignore access modifiers. This is what .NET binary serialization is doing.
For example, if you generate IL code at runtime using DynamicMethod, then you can pass skipVisibility = true to avoid any checks for visibility of fields/classes.
I've been working a bit on this. I'm not sure if it will help but, anyway I think it could be the way. Recently I worked with Serialization and DeSerealization of a class I had to send over the network. As there were two different programs (the client and the server), at first I implemented the class in both sources and then used serialization. It failed as the .Net told me it had not the same ID (I'm not sure but it was some sort of assembly id).
Well, after googling a bit I found that it was because the serialized class was on different assemblies, so the solution was to put that class in a independent library and then compile both client and server with that library. I've used the same idea with your code, so I put both Foo class and FieldReader class in a independent library, let's say:
namespace FooLibrary
{
public class Foo
{
public string Field1 = null;
public int? Field2 = null;
}
public abstract class FieldReader<T>
{
public abstract void Fill(T entity, IDataReader reader);
}
}
compile it and add it to the other source (using FooLibrary;)
this is the code I've used. It's not exactly the same as yours, as I don't have the code for GetCSharpName (I used t.Name instead) and XDataReader, so I used IDataReader (just for the compiler to accept the code and compile it) and also change EntityField for object
public class ReflectionHelper
{
public static FieldReader<T> GetFieldReader<T>()
{
Type t = typeof(T);
string className = t.Name;
string readerClassName = Regex.Replace(className, #"\W+", "_") + "_FieldReader";
object[] fields = new object[10];
string source = GetFieldReaderCode(t.Namespace, className, readerClassName, fields);
CompilerParameters prms = new CompilerParameters();
prms.GenerateInMemory = true;
prms.ReferencedAssemblies.Add("System.Data.dll");
prms.ReferencedAssemblies.Add(Assembly.GetExecutingAssembly().GetModules(false)[0].FullyQualifiedName);
prms.ReferencedAssemblies.Add(t.Module.FullyQualifiedName);
prms.ReferencedAssemblies.Add("FooLibrary1.dll");
CompilerResults compiled = new CSharpCodeProvider().CompileAssemblyFromSource(prms, new string[] { source });
if (compiled.Errors.Count > 0)
{
StringWriter w = new StringWriter();
w.WriteLine("Error(s) compiling {0}:", readerClassName);
foreach (CompilerError e in compiled.Errors)
w.WriteLine("{0}: {1}", e.Line, e.ErrorText);
w.WriteLine();
w.WriteLine("Generated code:");
w.WriteLine(source);
throw new Exception(w.GetStringBuilder().ToString());
}
return (FieldReader<T>)compiled.CompiledAssembly.CreateInstance(readerClassName);
}
private static string GetFieldReaderCode(string ns, string className, string readerClassName, IEnumerable<object> fields)
{
StringWriter w = new StringWriter();
// write out field setters here
return #"
using System;
using System.Data;
namespace " + ns + ".Generated
{
public class " + readerClassName + #" : FieldReader<" + className + #">
{
public override void Fill(" + className + #" e, IDataReader reader)
" + w.GetStringBuilder().ToString() +
}
}";
}
}
by the way, I found a tiny mistake, you should use new or override with the Fill method, as it is abstract.
Well, I must admit that GetFieldReader returns null, but at least the compiler compiles it.
Hope that this will help you or at least it guides you to the good answer
regards

Is it possible to serialize a C# code block?

I'm using C# with .NET 3.5. Is it possible to serialize a block of code, transmit it somewhere, deserialize it, and then execute it?
An example usage of this would be:
Action<object> pauxPublish = delegate(object o)
{
if (!(o is string))
{
return;
}
Console.WriteLine(o.ToString());
};
Transmitter.Send(pauxPublish);
With some remote program doing:
var action = Transmitter.Recieve();
action("hello world");
My end goal is to be able to execute arbitrary code in a different process (which has no prior knowledge of the code).
YES!!!
We have done this for a very real case of performance. Doing this at runtime or using a DSL was not an option due to performance.
We compile the code into an assembly, and rip the IL out of the method. We then get all the metadata associated with this method and serialize the whole mess via XML, compress it, and put it in our database.
At re-hydration time, we re-constitute the IL with the metadata using the DynamicMethod class, and execute it.
We do this because of speed. We have thousands of little blocks of code. Unfortunately, to compile a block of code and run it on the fly takes at least 250 ms, which is way too slow for us. We took this approach, and it is working REALLY well. At run-time, it takes an unmeasurable amount of time to reconstitute the method and run it.
Only thing to keep an eye on... Signed assemblies and Unsigned assemblies cannot mix the serialized method data.
You could try to use IronPython in your project. It's trivial to do what you are asking in Python. The Python code could call your C# methods. As for security, you could execute the code in a restricted environment of some kind (one example is RestrictedPython).
Generally speaking that sounds like a really bad idea and a big security hole.
You don't want another process to execute any code. Understand what you really need another process to do and build a little DSL around it.
You could also send it as a string then use the CodeDomProvider to compile it, same result. I have an example bit of code thus:
using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using Microsoft.CSharp;
namespace DynamicCodeApplication
{
class azCodeCompiler
{
private List<string> assemblies;
public azCodeCompiler()
{
assemblies = new List<string>();
scanAndCacheAssemblies();
}
public Assembly BuildAssembly(string code)
{
CodeDomProvider prov = CodeDomProvider.CreateProvider("CSharp");
string[] references = new string[] { }; // Intentionally empty, using csc.rsp
CompilerParameters cp = new CompilerParameters(references)
{
GenerateExecutable = false,
GenerateInMemory = true
};
string path = System.Runtime.InteropServices.RuntimeEnvironment.GetRuntimeDirectory();
cp.CompilerOptions = "#" + path + #"\csc.rsp";
CompilerResults cr = prov.CompileAssemblyFromSource(cp, code);
foreach (CompilerError err in cr.Errors)
{
Console.WriteLine(err.ToString());
}
return cr.CompiledAssembly;
}
public object ExecuteCode(string code,
string namespacename, string classname,
string functionname, bool isstatic, params object[] args)
{
object returnval = null;
Assembly asm = BuildAssembly(code);
object instance = null;
Type type = null;
if (isstatic)
{
type = asm.GetType(namespacename + "." + classname);
}
else
{
instance = asm.CreateInstance(namespacename + "." + classname);
type = instance.GetType();
}
MethodInfo method = type.GetMethod(functionname);
returnval = method.Invoke(instance, args);
return returnval;
}
private void scanAndCacheAssemblies()
{
/*
foreach (string str in Directory.GetFiles(#"C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727"))
{
if (str.Contains(".dll"))
{
foreach (string st in str.Split(new char[] { '\\' }))
{
if (st.Contains(".dll"))
{
assemblies.Add(st);
}
}
}
}
* */
assemblies.Add("Accessibility.dll");
assemblies.Add("AspNetMMCExt.dll");
assemblies.Add("cscompmgd.dll");
assemblies.Add("CustomMarshalers.dll");
assemblies.Add("IEExecRemote.dll");
assemblies.Add("IEHost.dll");
assemblies.Add("IIEHost.dll");
assemblies.Add("Microsoft.Build.Conversion.dll");
assemblies.Add("Microsoft.Build.Engine.dll");
assemblies.Add("Microsoft.Build.Framework.dll");
assemblies.Add("Microsoft.Build.Tasks.dll");
assemblies.Add("Microsoft.Build.Utilities.dll");
assemblies.Add("Microsoft.Build.VisualJSharp.dll");
assemblies.Add("Microsoft.CompactFramework.Build.Tasks.dll");
assemblies.Add("Microsoft.JScript.dll");
assemblies.Add("Microsoft.VisualBasic.Compatibility.Data.dll");
assemblies.Add("Microsoft.VisualBasic.Compatibility.dll");
assemblies.Add("Microsoft.VisualBasic.dll");
assemblies.Add("Microsoft.VisualBasic.Vsa.dll");
assemblies.Add("Microsoft.Vsa.dll");
assemblies.Add("Microsoft.Vsa.Vb.CodeDOMProcessor.dll");
assemblies.Add("Microsoft_VsaVb.dll");
assemblies.Add("mscorlib.dll");
assemblies.Add("sysglobl.dll");
assemblies.Add("System.configuration.dll");
assemblies.Add("System.Configuration.Install.dll");
assemblies.Add("System.Data.dll");
assemblies.Add("System.Data.OracleClient.dll");
assemblies.Add("System.Data.SqlXml.dll");
assemblies.Add("System.Deployment.dll");
assemblies.Add("System.Design.dll");
assemblies.Add("System.DirectoryServices.dll");
assemblies.Add("System.DirectoryServices.Protocols.dll");
assemblies.Add("System.dll");
assemblies.Add("System.Drawing.Design.dll");
assemblies.Add("System.Drawing.dll");
assemblies.Add("System.EnterpriseServices.dll");
assemblies.Add("System.Management.dll");
assemblies.Add("System.Messaging.dll");
assemblies.Add("System.Runtime.Remoting.dll");
assemblies.Add("System.Runtime.Serialization.Formatters.Soap.dll");
assemblies.Add("System.Security.dll");
assemblies.Add("System.ServiceProcess.dll");
assemblies.Add("System.Transactions.dll");
assemblies.Add("System.Web.dll");
assemblies.Add("System.Web.Mobile.dll");
assemblies.Add("System.Web.RegularExpressions.dll");
assemblies.Add("System.Web.Services.dll");
assemblies.Add("System.Windows.Forms.dll");
assemblies.Add("System.XML.dll");
assemblies.Add("vjscor.dll");
assemblies.Add("vjsjbc.dll");
assemblies.Add("vjslib.dll");
assemblies.Add("vjslibcw.dll");
assemblies.Add("vjssupuilib.dll");
assemblies.Add("vjsvwaux.dll");
assemblies.Add("vjswfc.dll");
assemblies.Add("VJSWfcBrowserStubLib.dll");
assemblies.Add("vjswfccw.dll");
assemblies.Add("vjswfchtml.dll");
assemblies.Add("Accessibility.dll");
assemblies.Add("AspNetMMCExt.dll");
assemblies.Add("cscompmgd.dll");
assemblies.Add("CustomMarshalers.dll");
assemblies.Add("IEExecRemote.dll");
assemblies.Add("IEHost.dll");
assemblies.Add("IIEHost.dll");
assemblies.Add("Microsoft.Build.Conversion.dll");
assemblies.Add("Microsoft.Build.Engine.dll");
assemblies.Add("Microsoft.Build.Framework.dll");
assemblies.Add("Microsoft.Build.Tasks.dll");
assemblies.Add("Microsoft.Build.Utilities.dll");
assemblies.Add("Microsoft.Build.VisualJSharp.dll");
assemblies.Add("Microsoft.CompactFramework.Build.Tasks.dll");
assemblies.Add("Microsoft.JScript.dll");
assemblies.Add("Microsoft.VisualBasic.Compatibility.Data.dll");
assemblies.Add("Microsoft.VisualBasic.Compatibility.dll");
assemblies.Add("Microsoft.VisualBasic.dll");
assemblies.Add("Microsoft.VisualBasic.Vsa.dll");
assemblies.Add("Microsoft.Vsa.dll");
assemblies.Add("Microsoft.Vsa.Vb.CodeDOMProcessor.dll");
assemblies.Add("Microsoft_VsaVb.dll");
assemblies.Add("mscorlib.dll");
assemblies.Add("sysglobl.dll");
assemblies.Add("System.configuration.dll");
assemblies.Add("System.Configuration.Install.dll");
assemblies.Add("System.Data.dll");
assemblies.Add("System.Data.OracleClient.dll");
assemblies.Add("System.Data.SqlXml.dll");
assemblies.Add("System.Deployment.dll");
assemblies.Add("System.Design.dll");
assemblies.Add("System.DirectoryServices.dll");
assemblies.Add("System.DirectoryServices.Protocols.dll");
assemblies.Add("System.dll");
assemblies.Add("System.Drawing.Design.dll");
assemblies.Add("System.Drawing.dll");
assemblies.Add("System.EnterpriseServices.dll");
assemblies.Add("System.Management.dll");
assemblies.Add("System.Messaging.dll");
assemblies.Add("System.Runtime.Remoting.dll");
assemblies.Add("System.Runtime.Serialization.Formatters.Soap.dll");
assemblies.Add("System.Security.dll");
assemblies.Add("System.ServiceProcess.dll");
assemblies.Add("System.Transactions.dll");
assemblies.Add("System.Web.dll");
assemblies.Add("System.Web.Mobile.dll");
assemblies.Add("System.Web.RegularExpressions.dll");
assemblies.Add("System.Web.Services.dll");
assemblies.Add("System.Windows.Forms.dll");
assemblies.Add("System.XML.dll");
assemblies.Add("vjscor.dll");
assemblies.Add("vjsjbc.dll");
assemblies.Add("vjslib.dll");
assemblies.Add("vjslibcw.dll");
assemblies.Add("vjssupuilib.dll");
assemblies.Add("vjsvwaux.dll");
assemblies.Add("vjswfc.dll");
assemblies.Add("VJSWfcBrowserStubLib.dll");
assemblies.Add("vjswfccw.dll");
assemblies.Add("vjswfchtml.dll");
return;
}
}
}
Compile it into a separate assembly, send the assembly, have the other process load it.
You might want to consider security implications.
Update: another idea would be to generate an expression tree and use this library to serialize it:
http://www.codeplex.com/metalinq/
It is an interesting challenge, but you should probably describe why you want to do this, since there is a lot of different approaches depending on your objective. As humpohl points out, there is also some pretty serious security issues.
"Serialized code" could just be source code or a compiled assembly, depending on your requirements. You probably don't need to use a seperate code serialization format.
If you want to generate code dynamically and pass that on, you could generate code using CodeDOM and compile it. However, you most likely dont need to generate completely arbitrary code.
Another option is using the DLR, and constraining the code to execute...

Categories