Siehe dazu:
System.CodeDom
System.CodeDom.Compiler
Kurzer Überblick
Die Klasse CSharpCodeProvider bietet die Möglichkeit Code zu compilieren, wofür es drei Möglichkeiten gibt. Der Code kann direkt übergeben werden, eine DOM-Struktur kann zugrunde liegen oder eine Datei wird angegeben.
Hier ein Beispiel:
|
C-/C++-Quelltext
|
1
2
|
CSharpCodeProvider provider = new CSharpCodeProvider();
result = provider.CompileAssemblyFromSource( options, source );
|
Der Compiler benötigt natürlich aber noch weitere Informationen. Zum Beispiel die referenzierten assemblies:
|
C-/C++-Quelltext
|
1
2
3
4
5
6
7
8
9
|
CompilerParameters options = new CompilerParameters();
options.OutputAssembly = "foo";
options.ReferencedAssemblies.Add( "System.dll" );
options.ReferencedAssemblies.Add( "System.Windows.Forms.dll" );
options.CompilerOptions = "/t:library";
options.GenerateInMemory = true;
options.GenerateExecutable = false;
options.IncludeDebugInformation = false;
|
Hier werden angegeben:
Der Name des Kompilats
Eine Liste der benötigten Assembly-Referenzen
Compilieroptionen
Das Kompilat im Speicher erzeugen
Keine Ausführbare Datei des Kompilats
Keine Debuginformationen
Zurück gibt der Compiler ein Objekt vom Typ CompilerResults. Dieses Objekt enthält alle Informationen des Kompiliervorgangs (u.a. auch Übersetzungsfehler):
|
C-/C++-Quelltext
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
result = provider.CompileAssemblyFromSource( options, source );
if ( result.Errors.Count > 0 )
{
foreach ( CompilerError error in result.Errors )
{
listBox2.Items.Add( error.ToString() );
}
}
else
{
listBox2.Items.Add( "Erfolgreich übersetzt" );
asm = result.CompiledAssembly;
}
|
Die Beispielfunktion zum Kompilieren sieht so aus:
|
C-/C++-Quelltext
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
private Assembly Compile( string source )
{
Assembly asm = null;
CompilerResults result = null;
CompilerParameters options = new CompilerParameters();
try
{
options.OutputAssembly = "foo";
options.ReferencedAssemblies.Add( "System.dll" );
options.ReferencedAssemblies.Add( "System.Windows.Forms.dll" );
options.CompilerOptions = "/t:library";
options.GenerateInMemory = true;
options.GenerateExecutable = false;
options.IncludeDebugInformation = false;
CSharpCodeProvider provider = new CSharpCodeProvider();
result = provider.CompileAssemblyFromSource( options, source );
if ( result.Errors.Count > 0 )
{
foreach ( CompilerError error in result.Errors )
{
listBox2.Items.Add( error.ToString() );
}
}
else
{
listBox2.Items.Add( "Erfolgreich übersetzt" );
asm = result.CompiledAssembly;
}
}
catch
{}
return asm;
}
|
Das ist nun natürlich noch extrem unflexibel. Aber für ein Beispiel ausreichend!
Wenn erfolgreich kompiliert wurde kann eine Instanz aus dem zurückgegebenen Assemblyobjekt erzeugt werden:
|
C-/C++-Quelltext
|
1
|
mInstance = asm.CreateInstance( "WindowsApplication1.foo", true, BindingFlags.Instance | BindingFlags.Public, null, new object[] { 100 }, CultureInfo.CurrentCulture, null );
|
Dieses Objekt ist, falls alles korrekt lief, eine Instanz einer Klasse im eben erzeugten Assembly (hier "foo" im Namespace "WindowsApplication1"). Nun ist diese aber nur als Objekt verfügbar, was also tun?
Über die Methode Objekt::GetType() können Typeninformationen gesucht werden. Zum Beispiel alle öffentlichen Methoden:
|
C-/C++-Quelltext
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
Type t = mInstance.GetType();
MethodInfo[] methods = t.GetMethods();
foreach ( MethodInfo method in methods )
{
StringBuilder builder = new StringBuilder();
builder.AppendFormat( "{0} {1}( ", method.ReturnType.ToString(), method.Name );
ParameterInfo[] @params = method.GetParameters();
int i = 0;
foreach ( ParameterInfo parameter in @params )
{
builder.AppendFormat("{0}{1} ", parameter.ToString(), ( i < @params.Length-1 ? "," : "" ) );
i++;
}
builder.Append( ")" );
listBox1.Items.Add( builder.ToString() );
}
|
Also nochmal die Beispielfunktion im Ganzen:
|
C-/C++-Quelltext
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
private void button1_Click( object sender, EventArgs e )
{
listBox2.Items.Clear();
Assembly asm = Compile( textBox1.Text );
if ( asm == null )
return;
mInstance = asm.CreateInstance( "WindowsApplication1.foo", true, BindingFlags.Instance | BindingFlags.Public, null, new object[] { 100 }, CultureInfo.CurrentCulture, null );
Type t = mInstance.GetType();
MethodInfo[] methods = t.GetMethods();
foreach ( MethodInfo method in methods )
{
StringBuilder builder = new StringBuilder();
builder.AppendFormat( "{0} {1}( ", method.ReturnType.ToString(), method.Name );
ParameterInfo[] @params = method.GetParameters();
int i = 0;
foreach ( ParameterInfo parameter in @params )
{
builder.AppendFormat("{0}{1} ", parameter.ToString(), ( i < @params.Length-1 ? "," : "" ) );
i++;
}
builder.Append( ")" );
listBox1.Items.Add( builder.ToString() );
}
}
|
Auch unflexibel ohne Ende. Aber Beispielhaft tauglich!
Zu guter Letzt will man natürlich auch diverse Methoden, Eigenschaften (etcpp) aufrufen können. Das geht über die Klasse "Type" und ihre Diversen Methoden (GetMethod, GetProperty, ...).
|
C-/C++-Quelltext
|
1
|
MethodInfo method = mInstance.GetType().GetMethod( name, BindingFlags.Public | BindingFlags.Instance, null, Type.GetTypeArray( parameters ), null );
|
Hat man die Methoden-Information (wurde nichts gefunden wird "null" zurückgegeben) so kann man über MethodInfo::Invoke die Methode aufrufen. (Equivalent geht das auch mit z.B. PropertyInfo).
|
C-/C++-Quelltext
|
1
2
3
4
|
if ( method != null )
{
method.Invoke( mInstance, parameters );
}
|
Und nochmal in einem Stück:
|
C-/C++-Quelltext
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
private void Execute( string name, params object[] parameters )
{
if ( mInstance == null )
return;
MethodInfo method = mInstance.GetType().GetMethod( name, BindingFlags.Public | BindingFlags.Instance, null, Type.GetTypeArray( parameters ), null );
if ( method != null )
{
method.Invoke( mInstance, parameters );
}
else
listBox2.Items.Add( string.Format( "Methode nicht gefunden: {0}", name ) );
}
|
Aufgerufen wird die Funktion dann z.B. über:
|
C-/C++-Quelltext
|
1
2
3
|
Execute( "MyFunction" ); // kein Parameter
Execute( "MyFunction", 10 ); // ein Parameter (Int32)
Execute( "MyFunction", "Hallo", 150 ); // zwei Parameter (String, Int32)
|
Zum Schluß noch meine Testklasse:
|
C-/C++-Quelltext
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
using System;
using System.Windows.Forms;
namespace WindowsApplication1
{
class foo
{
private int x;
public foo( int value )
{
x = value;
}
public void SayHello()
{
MessageBox.Show( string.Format( "foo::SayHello() -> {0}", x ) );
}
public void HelloAgain( string name )
{
MessageBox.Show( string.Format( "Hello {0}", name ) );
}
public void Add( int a, int b )
{
MessageBox.Show( string.Format( "{0}+{1}={3}", a, b, a+b ) );
}
}
}
|