Finally on to the meat of the article. As I said already, compiler services are built in to the .NET framework. These live in the System.CodeDom
namespace. I say compiler services and not compilers because CodeDom
encompasses a lot more than that. However, the bits we're interested in live in the System.CodeDom.Compiler
namespace.
To start off with we need an instance of a class that inherits from CodeDomProvider
. This class provides methods to create instances of other helper classes specific to that language. Derivatives of CodeDomProvider
are shipped with the framework for all four .NET languages, however the only two we're interested in are VB and C#, the most popular. They are Microsoft.VisualBasic.VBCodeProvider
and Microsoft.CSharp.CSharpCodeProvider
. The design of this system is so good that after choosing which of these to use, the steps are exactly the same.
The first thing we use is the CodeDomProvider
's CreateCompiler
method to get an instance of a class implementing ICodeCompiler
. Once we have this that's the last we need from our CodeDomProvider
. Our helper class will have a CompileScript
function, which will accept script source, the path to an assembly to reference and a language to use. We'll overload it so the user can use their own codeprovider if they want to support scripting in a language other than VB or C#.
The next step once we have our ICodeCompiler
is to configure the compiler. This is done using the CompilerParameters
class. We create an instance of it using the parameterless constructor and configure the following things:
- We don't want an executable, so set
GenerateExecutable
to false. - We don't want a file on disk, so set
GenerateInMemory
to true. - We don't want debugging symbols in it, so set
IncludeDebugInformation
to false. - We add a reference to the assembly passed as a parameter.
- We add a reference to
System.dll
andSystem.Windows.Forms.dll
to make the TextBox usable.
As a side note, to provide information for compilers specific to the language you are using (such as specifying Option Strict
for VB) you use the CompilerOptions
property to add extra command-line switches.
Once we have our CompilerParameters
set up we use our compiler's CompileAssemblyFromSource
method passing only our parameters and a string containing the script source. This method returns an instance of the CompilerResults
class. This includes everything we need to know about whether the compile succeeded - if it did, the location of the assembly produced and if it didn't, what errors occured.
That is all this helper function will do. It will return the CompilerResults
instance to the application for further processing.
[VB]
Public Shared Function CompileScript(ByVal Source As String, ByVal Reference As String, _
ByVal Provider As CodeDomProvider) As CompilerResults
Dim compiler As ICodeCompiler = Provider.CreateCompiler()
Dim params As New CompilerParameters()
Dim results As CompilerResults
'Configure parameters
With params
.GenerateExecutable = False
.GenerateInMemory = True
.IncludeDebugInformation = False
If Not Reference Is Nothing AndAlso Reference.Length <> 0 Then _
.ReferencedAssemblies.Add(Reference)
.ReferencedAssemblies.Add("System.Windows.Forms.dll")
.ReferencedAssemblies.Add("System.dll")
End With
'Compile
results = compiler.CompileAssemblyFromSource(params, Source)
Return results
End Function
[C#]
public static CompilerResults CompileScript(string Source, string Reference,
CodeDomProvider Provider)
{
ICodeCompiler compiler = Provider.CreateCompiler();
CompilerParameters parms = new CompilerParameters();
CompilerResults results;
// Configure parameters
parms.GenerateExecutable = false;
parms.GenerateInMemory = true;
parms.IncludeDebugInformation = false;
if (Reference != null && Reference.Length != 0)
parms.ReferencedAssemblies.Add(Reference);
parms.ReferencedAssemblies.Add("System.Windows.Forms.dll");
parms.ReferencedAssemblies.Add("System.dll");
// Compile
results = compiler.CompileAssemblyFromSource(parms, Source);
return results;
}
We also need one more helper function, which we will pretty much completely take straight from the plugin services we developed in my previous tutorial. This is the function that examines a loaded assembly for a type implementing a given interface and returns an instance of that class.
[VB]
Public Shared Function FindInterface(ByVal DLL As Reflection.Assembly, _
ByVal InterfaceName As String) As Object
Dim t As Type
'Loop through types looking for one that implements the given interface
For Each t In DLL.GetTypes()
If Not (t.GetInterface(InterfaceName, True) Is Nothing) Then
Return DLL.CreateInstance(t.FullName)
End If
Next
Return Nothing
End Function
[C#]
public static object FindInterface(System.Reflection.Assembly DLL, string InterfaceName)
{
// Loop through types looking for one that implements the given interface
foreach(Type t in DLL.GetTypes())
{
if (t.GetInterface(InterfaceName, true) != null)
return DLL.CreateInstance(t.FullName);
}
return null;
}
Comments