Monday, November 20, 2006

DynamicClassFactory - The Heart

(Continuing my previous post)

The heart of DCF is the Reflection-based bit that generates a helper class for each of the supported classes. That's all done in the init() method hereunder:


/// <summary>
///
Initializes the ClassFactory.
/// Creates the code for the ClassFactory and compiles it.
/// If for any reason the ClassFactory could not be properly created, an exception is fired.
/// </summary>
private static void init()
{
try
{
lock (items.SyncRoot)
{
CSharpCodeProvider cp = new CSharpCodeProvider();
//ICodeCompiler ic = cp.CreateCompiler();
CodeDomProvider cdp = new CSharpCodeProvider();
CompilerParameters cpar = new CompilerParameters();
cpar.GenerateInMemory =
true;
cpar.GenerateExecutable =
false;

Hashtable assemblies = new Hashtable();

// Add default references
cpar.ReferencedAssemblies.Add("system.dll");
cpar.ReferencedAssemblies.Add(
"DynamicClassFactory.dll");

foreach (DynamicClassFactoryItem item in items)
{
// Add the reference libraries to the compiler options
// This was added to support cases that referenced libraries are not located
// in the current directory. It became necessary when using the class factory
// from within a Service, in which case the current directory is not the
// directory of the currently running assembly (but System32)
// IMPORTANT: I you want to use this class from within a service, you must have
// entered the folders where to look for assemblies in the config file as so:
// <ReferenceLibs>
// <ReferenceLib>T:\Src\TSSrv\bin\Debug</ReferenceLib>
// </ReferenceLibs>

foreach (string refLib in item.ReferenceLibs)
{
cpar.CompilerOptions +=
" /lib:\"" + refLib + "\"";
}

// Load the assemblies - will enable us to get type-information
Assembly currentAssembly = (Assembly)assemblies[item.AssemblyName];

if (currentAssembly == null)
{
currentAssembly =
Assembly.LoadFrom(item.AssemblyName);

assemblies[item.AssemblyName] = currentAssembly;
if (currentAssembly == null)
{
throw new Exception(string.Format("Assembly with name {0} could not be loaded by DynamicClassFactory", item.AssemblyName));
}

// Set references:
// 1. First we must add the current assembly to the references
// 2. Then we must add all referenced assemblies as well
// NOTE: We explicitely do NOT use cpar.ReferencedAssemblies because
// it does not handle well cases that the assembly name has blanks in it
cpar.CompilerOptions += " /reference:\"" + item.AssemblyName + "\"";

Debug.WriteLine("Adding " + currentAssembly.GetReferencedAssemblies().Length.ToString() + " referenced assemblies for " + item.AssemblyName);

foreach (AssemblyName assemblyName in currentAssembly.GetReferencedAssemblies())
{
if (!cpar.ReferencedAssemblies.Contains("\"" + assemblyName.Name + ".dll\""))
{
Debug.WriteLine("Adding reference assembly - \"" + assemblyName.Name + ".dll\"");
cpar.CompilerOptions +=
" /reference:\"" + assemblyName.Name + ".dll\"";
}
}

Debug.WriteLine("All referenced assemblies for " + item.AssemblyName + " were added successfully");
}
}

string newLine = Environment.NewLine;
string src =
"using System;" + newLine +
"using DynamicClassFactory;" + newLine +
"class ClassFactoryImpl : DynamicClassFactory.IClassFactory" + newLine +
"{" + newLine +
"public ClassFactoryImpl(){}" + newLine +
"public object CreateInstance(int id, params object[] args)" + newLine +
"{" + newLine +
"switch (id)" + newLine +
"{" + newLine;
foreach (DynamicClassFactoryItem item in items)
{
src +=
"case " + Convert.ToInt32(item.Key) + ":" + newLine +
"return " + item.ClassName.Replace(".", "_") + "ClassCreator.Create(args);" + newLine;
}

src +=
"default:" + newLine +
"return null;" + newLine +
"}" + newLine +
"}" + newLine;

// Create an internal class for each supported item in the factory.
foreach (DynamicClassFactoryItem item in items)
{
src +=
"class " + item.ClassName.Replace(".", "_") + "ClassCreator " + newLine +
"{" + newLine +
"public static object Create(params object[] args)" + newLine +
"{" + newLine;
Assembly a = (Assembly)assemblies[item.AssemblyName];
Type currentType = null;
foreach (Type t in a.GetTypes())
{
if (t.FullName == item.ClassName)
{
currentType = t;
break;
}
}

HybridDictionary parametersByLength = new HybridDictionary(currentType.GetConstructors().Length);

// First we organize the various ctor parameters according to the number of parameters in it
foreach (ConstructorInfo cInfo in currentType.GetConstructors())
{
ParameterInfo[] parameters = cInfo.GetParameters();
ArrayList parametersList = parametersByLength[parameters.Length] as ArrayList;
if (parametersList == null)
{
parametersList =
new ArrayList();
parametersByLength[parameters.Length] = parametersList;
}
parametersList.Add(parameters);
}

// Now, for each size of ctor, we build simple if-predicates.
// This may not be the best-performing solution (e.g. if there are many ctors with
// 10 parameters and they differ only by the type of the last parameter),
// but in most cases it's more than enough.
// Another approach would be to build a decision tree, such that each possible ctor
// would end in one of the tree's leaves. However, it would significantly increase
// the implementation complexity, and would probably do little to improve performance.
foreach (int parametersCount in parametersByLength.Keys)
{
ArrayList parametersList = parametersByLength[parametersCount] as ArrayList;
src +=
"if (args.Length == " + parametersCount.ToString() + ")" + newLine +
"{" + newLine;

if (parametersCount == 0)
{
src +=
"return new " + item.ClassName + "();" + newLine;
}
else
{
foreach (ParameterInfo[] parameters in parametersList)
{
string argsList = "";
src +=
"if (";
for (int i = 0; i < parameters.Length; i++)
{
ParameterInfo pInfo = parameters[i];
src +=
"(args[" + i.ToString() + "] is " + pInfo.ParameterType.ToString() + ")";
if (i != parameters.Length-1)
{
src +=
" && ";
}

if (i != 0)
{
argsList +=
", ";
}
argsList +=
"(" + pInfo.ParameterType.ToString() + ")args[" + i.ToString() + "]";
}
src +=
")" + newLine +
"{" + newLine +
"return new " + item.ClassName + "(" + argsList + ");" + newLine +
"}" + newLine;
}
}

src +=
"}" + newLine;
}

src +=
"return null;" + newLine;
src +=
"}" + newLine +
"}" + newLine;
}


src +=
"}" + newLine;

Debug.WriteLine(src); // DO NOT DELETE THIS LINE - it prints the actual Class Factory to the Debug output

// Compile the actual implementation of the class factory
//CompilerResults cr = ic.CompileAssemblyFromSource(cpar,src);
CompilerResults cr = cdp.CompileAssemblyFromSource(cpar, src);
foreach (CompilerError ce in cr.Errors)
{
string errMsg = "DynamicClassFactory Compiler Error (nr. " + ce.ErrorNumber.ToString() +
") on line " + ce.Line.ToString() + ": " + ce.ErrorText;
Debug.WriteLine(errMsg);
EventLogWrapper.WriteEntry(EventType.Warning, errMsg);
}

if (cr.Errors.Count == 0 && cr.CompiledAssembly != null)
{
Type ObjType = cr.CompiledAssembly.GetType("ClassFactoryImpl");
try
{
if (ObjType != null)
{
instance =
Activator.CreateInstance(ObjType) as IClassFactory;
}
}
catch (Exception ex)
{
logException(
"DynamicClassFactory exception when creating internal instance: ", ex);
}
}
}
}
catch (Exception e)
{
// If any exception occurred, send it to Debug and propagate it on - because the application
// won't be able to continue
logException("ClassFactory.init() generated an exception: ", e);
throw;
}

// If there is any error - throw an exception. The application won't be
// able to continue from here anyway...
if (instance == null)
{
throw new Exception("The dynamic ClassFactory could not be created.");
}
}

No comments: