| 

.NET C# Java Javascript Exception

3
Hallo,
ich habe Anwendung, mit der ich zur Laufzeit eine Datei laden kann.
In dieser Datei stehen zeilenweise Befehle und Anweisungen, die ausgeführt werden.
Ähnlich wie mit Makros in Execl. Alle diese Anweisungen sind in meiner Anwendung als SUB's oder Function's definiert. Die Ergebnise werden in meiner Anwendung ausgegeben (z.B. in Textboxen usw.).

Nun habe ich ein VB.NET Beispielprogramm gefunden, mit dem man mittels CodeDOM Quellcode programmgesteuert compilieren und anschließend starten kann. Wenn man das tut wird ein eigenständiges Programm daraus. Soweit will ich nicht gehen. Meine Frage:
Ist es möglich daß das dynamisch compilierte Programm bzw. deren Funktionen anschließend direkt von meiner Anwendung eingebunden und benutzt werden können. Geht das eventuell über eine DLL ? Nur wie ?

Wenn das funktionieren würde, könnte ich auf die VB.NET-Syntax für meine Makros umsteigen und auch das aufwändige Verwalten meiner selbst erstellten Makro-Syntax verzichten bzw. vereinfachen. Ein weiterer Vorteil für mich wäre, das ich Variable definieren könnte und mehrfach verschachtelte For Netxt-Schleifen anwenden könnte. Das habe ich bis dato noch nicht.

Für in einfaches Beispiel wäre ich sehr dankbar.

Gruß
ckflei
28.02.2011
ckflei 73 1 6
5 Antworten
2
Da Du nach einem einfachen Beispiel gefragt hast (und CodeDOM als ganzes wirklich sehr mächtig, aber deshalb zumindest am Anfang auch etwas unübersichtlich ist): ich hab zwar gerade kein komplettes Beispiel parat, aber es gibt einen Einstieg bei der MSDN-Beschreibung des VBCodeProvider. Wenn Deine Sourcen als String statt als File vorliegen, nimm CompileAssemblyFromSource statt CompileAssemblyFromFile.

Das Beispiel zeigt, wie man die erstellte Assembly auf die Platte bekommt. Wenn Du sie aber ad-hoc im laufenden Programm verwenden willst, mach folgendes: Über die CompilerParameters kannst Du mit GenerateInMemory festlegen, dass die Assembly nur im Speicher erzeugt wird. Den Zugriff darauf erhältst Du über das CompilerResults-Objekt in der Property CompiledAssembly vom Typ Assembly. Über dessen Methode CreateInstance kannst Du Dir dann schließlich einen Typen aus Deinem Quellcode erzeugen.

Edit, nachdem Du das Beispiel gepostet hast:

Zu Deinen in Deiner "Antwort" formulierten Fragen (kleiner Tip: es wäre besser gewesen, Du hättest Deine Frage per Edit-Funktion erweitert):

1. ab dem zweiten Klick auf Kompilieren lädst Du die im Speicher kompilierte Assembly mit, die hat aber naturgemäß keine Location, was der VB-Compiler übelnimmt. Siehe auch hier

Du mußt also schreiben
Dim Asm As Assembly
For Each Asm In AppDomain.CurrentDomain.GetAssemblies()

If Not String.IsNullOrEmpty(Asm.Location) Then
Param1.ReferencedAssemblies.Add(Asm.Location)
End If

Next


2. Du setzt den Text auf einem neu erzeugten Form1 statt auf dem, das angezeigt wird. Das kannst Du so korrigieren
...
aMethod.Invoke(obj, New Object() {Me})
...
Public Class BLPClass

Public Sub ProgrammSub(tf As Form1)
...


3. Es ist schon ganz ok so, finde ich. Drei Sachen solltest Du Dir aber vielleicht noch überlegen:
a) Ist es aus Sicherheitssicht für Dich vertretbar, auf diese Weise eine Schnittstelle aufzumachen, die alles darf, was Dein Programm auch darf? Evtl. läßt sich das Problem ein wenig dadurch eindämmen, dass Du nur eine wohlüberlegte Teilmenge der geladenen Assemblies referenzierst. Ganz entschärfen wirst Du das Problem damit aber nicht.
b) Statt einer Referenz auf Dein Form könntest Du auch einen Callback an die kompilierte Methode übergeben, damit hättest Du das Aktualisieren des Forms besser unter Kontrolle.
c) Bei jedem Klick entsteht eine neue Assembly, die im Speicher verbleibt. Das kann u.U. zum Problem werden, läßt sich aber nur dadurch umgehen, dass Du die kompilierte Assembly in eine eigene AppDomain lädst, was dann aber auch das Aufrufen und den Callback schwieriger macht.
01.03.2011
Matthias Hlawatsch 13,2k 4 9
Hallo ich habe ein Programmbeispiel erstellt und gesendet.
Ich weiß nicht ob das jeder der mir bisher geantwortet hat sieht. Darin habe ich meine beiden Probleme geschildert die noch zu lösen sind.
ckflei 04.03.2011
Hallo Matthias, danke für den Tip zu Punkt 1. Der funktioniert prima.
Ich habe versucht mit den Callback's was zu machen aber drin bin ich nicht fit. Meine Versuche habe am Ende des Programmbeispiels erläutert. Eeventuell fällt dir noch was dazu ein.
ckflei 05.03.2011
1
Also die CodeDOM-Schnittstelle ist die möchtigste Variante. Um genau zu sein wird aber aus dem Quellcode kein eigenes Programm sondern erstmal nur ein Assembly welches du direkt im Speicher ausführen oder als DLL oder EXE ablegen und ausführen / einbinden kannst.

Alterantive kannst du auch dir mal diesen Artikel anschauen:

http://osherove.com/blog/2004/2/17/make-your-net-application-support-scripting-a-practical-appr.html
http://www.csscript.net/

Es gibt auch eine LUA-Scripting-Host-Implementierung für .Net. LUA ist eine gern verwendete Sprache für solche anwendungen.

http://www.gamedev.net/page/resources/_/reference/programming/sweet-snippets/using-lua-with-c-r2275
28.02.2011
Floyd 14,6k 3 9
Floyd 14,6k 3 9
Hallo ich habe ein Programmbeispiel erstellt und gesendet.
Ich weiß nicht ob das jeder der mir bisher geantwortet hat sieht. Darin habe ich meine beiden Probleme geschildert die noch zu lösen sind.
ckflei 04.03.2011
1
Da kann man mehrere Wege gehen.

  • Abstraktionen herstellen (Interfaces / Klassen), die gewisse Grundfunktionalitäten bieten.
    Diese mittels Reflection ausführen lassen.
    Dürfte nicht sehr aufwendig werden für grundlegende Aktionen.
    Könnte man mit dem Command Pattern designen.

  • Du kannst es über IL Codegenerierung machen: ILGenerator-Klasse
    Nicht mal eben so gemacht, denk ich.

  • Von Dir angesprochene CodeDOM

  • Einiges, wenn nicht sehr viel geht auch über ExpressionTree

    Wenn Du schon bereits Methoden hast, die über diese Macros angesteuert werden, dann würde ich auf jeden Fall eine kleine Architektur für die Methoden aufbauen.

    Editier Deinen Post am Besten mal und beschreib ein wenig mehr, an was für Befehle Du gedacht hast (vlt. mit Bsp.)?
  • 28.02.2011
    KHoffmann 939 7
    Hallo, also ich habe mir jetzt ein Versuchsprogramm geschrieben und ich muß sagen das compilieren funtioniert. Das Ausführen eines dieser Methoden geht auch. Es kamen aber 2 neue unverhoffte Probleme hinzu:
    1. Wenn ich ein zweitesmal compiliere kommt ein Compilerfehler. Ich muß momentan mein ganzes Programm immer wieder neu starten.

    2. Solange ich keine von meinen compilierten Methoden keine UI-Controls verändere funktionieert das alles. Aber wenn ich z.B. in eine eine Textbox etwas schreiben will wird das nicht ausgeführt. Da hänge ich momentan fest.

    ckflei 04.03.2011
    1
  • ' Das Programm simuliert mein Vorhaben, das ein VB-Code während der Laufzeit des meines Programms geändert werden kann, dann compiliert wird  
    ' und ausgeführt werden kann. Wobei im compilierten Code Subs oder Funktionen ausgeführt werden sollen die z.B. in der Class Form1 definiert sind.
    ' Hier die die TestSub stellvertretend.
    ' Das funktioniert bereits beispielhaft.

    ' Folgende Fragen tun sich auf:

    '1. Wie kann ich nach dem Ausführen des compilierten Programms bzw. Sub das System wieder soweit zurücksetzen, das bei einem erneuten Start
    ' (Compiler-Button drücken) keine Fehlermeldung kommt.
    ' Mit Nothing ist es nicht getan.

    '2. Die Textbox1.Text wird bei der Ausführung der TestSub nicht aktualisiert (v1 und v2 werden nicht dargestellt.
    ' Ich vermute, das es mit den unterschiedlichen Treads zusammenhängt mit dem die compilierte Methode ausgeführt wird und mit dem UI-Thread.
    ' Es ist aber wichtig, das die UI-Controls aktualisiert werden können.

    '3. Eventuell hat ja jemand eine viel bessere Idee wie mann da zum Erfolg kommt.

    'Für ein paar Ratschläge wäre ich sehr dankbar.

    Imports System
    Imports System.Windows.Forms
    Imports Microsoft.VisualBasic
    Imports System.CodeDom
    Imports System.CodeDom.Compiler
    Imports System.Reflection




    Public Class Form1
    Inherits System.Windows.Forms.Form
    Private error1 As String = String.Empty
    Private Shared ass As Assembly
    Private Shared aClass As Type
    Private Shared aMethod As MethodInfo
    Private Shared obj As Object
    Friend WithEvents TextBox1 As System.Windows.Forms.TextBox
    Friend WithEvents RichTextBox1 As System.Windows.Forms.RichTextBox


    #Region " Windows Form Designer generated code "

    Public Sub New()
    MyBase.New()

    'This call is required by the Windows Form Designer.
    InitializeComponent()

    'Add any initialization after the InitializeComponent() call

    End Sub

    'Form overrides dispose to clean up the component list.
    Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
    If disposing Then
    If Not (components Is Nothing) Then
    components.Dispose()
    End If
    End If
    MyBase.Dispose(disposing)
    End Sub

    'Required by the Windows Form Designer
    Private components As System.ComponentModel.IContainer

    'NOTE: The following procedure is required by the Windows Form Designer
    'It can be modified using the Windows Form Designer.
    'Do not modify it using the code editor.
    Friend WithEvents cmdCompile As System.Windows.Forms.Button
    Friend WithEvents Button1 As System.Windows.Forms.Button
    <System.Diagnostics.DebuggerStepThrough()> _
    Private Sub InitializeComponent()
    Me.cmdCompile = New System.Windows.Forms.Button
    Me.Button1 = New System.Windows.Forms.Button
    Me.TextBox1 = New System.Windows.Forms.TextBox
    Me.RichTextBox1 = New System.Windows.Forms.RichTextBox
    Me.SuspendLayout()
    '
    'cmdCompile
    '
    Me.cmdCompile.Anchor = CType((System.Windows.Forms.AnchorStyles.Bottom Or System.Windows.Forms.AnchorStyles.Right), System.Windows.Forms.AnchorStyles)
    Me.cmdCompile.FlatStyle = System.Windows.Forms.FlatStyle.System
    Me.cmdCompile.Location = New System.Drawing.Point(157, 413)
    Me.cmdCompile.Name = "cmdCompile"
    Me.cmdCompile.Size = New System.Drawing.Size(80, 24)
    Me.cmdCompile.TabIndex = 1
    Me.cmdCompile.Text = "Kompilieren"
    '
    'Button1
    '
    Me.Button1.Anchor = CType((System.Windows.Forms.AnchorStyles.Bottom Or System.Windows.Forms.AnchorStyles.Right), System.Windows.Forms.AnchorStyles)
    Me.Button1.Location = New System.Drawing.Point(487, 413)
    Me.Button1.Name = "Button1"
    Me.Button1.Size = New System.Drawing.Size(75, 24)
    Me.Button1.TabIndex = 2
    Me.Button1.Text = "Ende"
    Me.Button1.UseVisualStyleBackColor = True
    '
    'TextBox1
    '
    Me.TextBox1.Anchor = System.Windows.Forms.AnchorStyles.Bottom
    Me.TextBox1.Location = New System.Drawing.Point(321, 415)
    Me.TextBox1.Name = "TextBox1"
    Me.TextBox1.Size = New System.Drawing.Size(94, 21)
    Me.TextBox1.TabIndex = 3
    '
    'RichTextBox1
    '
    Me.RichTextBox1.Location = New System.Drawing.Point(13, 14)
    Me.RichTextBox1.Name = "RichTextBox1"
    Me.RichTextBox1.Size = New System.Drawing.Size(548, 375)
    Me.RichTextBox1.TabIndex = 4
    Me.RichTextBox1.Text = ""
    '
    'Form1
    '
    Me.AutoScaleBaseSize = New System.Drawing.Size(5, 14)
    Me.ClientSize = New System.Drawing.Size(570, 448)
    Me.Controls.Add(Me.RichTextBox1)
    Me.Controls.Add(Me.TextBox1)
    Me.Controls.Add(Me.Button1)
    Me.Controls.Add(Me.cmdCompile)
    Me.Font = New System.Drawing.Font("Tahoma", 8.25!, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, CType(0, Byte))
    Me.Name = "Form1"
    Me.Text = "Dynamic Compilation"
    Me.ResumeLayout(False)
    Me.PerformLayout()

    End Sub

    #End Region

    Private Function LoadBLPClass() As String
    Dim pfaddat1 As String = My.Application.Info.DirectoryPath
    Dim pfaddat2 As String = pfaddat1.Substring(0, pfaddat1.Length - 3) & "\BLPClass.vb"
    Dim fileContents As String = My.Computer.FileSystem.ReadAllText(pfaddat2)
    Return fileContents
    End Function

    Private Sub cmdCompile_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdCompile.Click
    'Einige Compiler-Parameter definieren
    Dim Param1 As New Compiler.CompilerParameters(Nothing, String.Empty, False)
    Param1.GenerateExecutable = False
    Param1.GenerateInMemory = True
    Param1.IncludeDebugInformation = False

    'Einige gebräuchliche Assembly-Verweise hinzufügen (basierend auf der gegenwärtig ausgeführten Anwendung).
    Dim Asm As Assembly
    For Each Asm In AppDomain.CurrentDomain.GetAssemblies()
    Param1.ReferencedAssemblies.Add(Asm.Location)
    Next

    'Code aus der Datei BLPClass.vb laden und darstellen
    Dim code As String = LoadBLPClass()
    RichTextBox1.Text = code

    'Den Code kompilieren.
    Dim res As CompilerResults = New VBCodeProvider().CompileAssemblyFromSource(Param1, code)

    'Compiler-Fehlerüberprüfung
    If res.Errors.Count > 0 Then
    Dim error1 As String = String.Empty
    For Each cerr As CompilerError In res.Errors
    error1 &= cerr.ToString() & vbCrLf & vbCrLf
    Next
    ass = Nothing
    MessageBox.Show(error1)
    Exit Sub
    End If

    'Auswerten der Assembly
    ass = res.CompiledAssembly
    aClass = ass.GetType("BLPClass")
    aMethod = aClass.GetMethod("ProgrammSub")
    obj = Activator.CreateInstance(aClass)
    aMethod.Invoke(obj, Nothing)

    'funtioniert nicht
    ass = Nothing
    res = Nothing
    Param1 = Nothing

    End Sub

    Public Sub TestSub(ByVal v1 As Double, ByVal v2 As Double)
    MsgBox("test bestanden " & v1.ToString & " " & v2.ToString)
    TextBox1.Text = v1.ToString & " " & v2.ToString
    End Sub

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
    Me.Close()
    End Sub

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load

    End Sub
    End Class

    '##############################################
    'hier beginnt die compilierte Classe
    '##############################################
    Imports System
    Imports System.Drawing
    Imports System.Threading.Thread
    Imports System.Windows.Forms
    Imports System.Math
    Imports Versuch

    Public Class BLPClass
    Public tf As New Form1

    Public Sub ProgrammSub()
    Dim d1 As Double = 0.8
    Dim d2 As Double = 1.9

    For I As Integer = 0 To 2
    tf.TestSub(d1, d2)
    Next

    End Sub

    End Class




    Hallo Matthias, als erstes mal vielen Dank für deinen Tip zu Punkt 1. Der funktioniert prima.

    Bei Punkt 2 kommt leider eine Fehlermeldung "Parameteranzahlkonflikt".

    Da werde ich mich doch noch mit Callback's beschäfftigen müssen. Ich hab zwar schon einiges darüber gelesen aber selbst damit gearbeitet habe ich noch nicht. Ich habe ein wenig rumprobiert wie folgt. Aber ich dreh mich im Kreis.

    Habe folgendes versucht:

    Ich habe in der Class BLPClass einen Delegaten für die TestSub in Form1 definiert:

    Public Delegate Sub Form1Delegate(ByVal index As Integer, ByVal v1 As Double, ByVal v2 As Double)

    Und einen Delegaten für die ProgrammSub definiert:

    Public Delegate Sub BLPClassDelegate()

    Dann die beiden Adressen ermittelt mit:

    Public Form1Method As Form1Delegate = AddressOf Form1.TestSub <= **
    Public BLPClassMethod As BLPClassDelegate = AddressOf ProgrammSub

    ** Hier wird ein Objektverweis benötigt, aber dann bin ich genausoweit wie vorher

    In der Methode ProgrammSub hätte ich dann Form1Method(1,d1,d2) aufrufen wollen.
    Aber wahrscheinlich bin ich da auf einen Holzweg.
  • 04.03.2011
    ckflei 73 1 6
    Danke für das Beispiel. Habe meine Antwort entsprechend erweitert.
    Matthias Hlawatsch 05.03.2011
    Strange - bei mir hat der wie angegeben modifizierte Code funktioniert, auch in Bezug auf Punkt 2. Hast Du sowohl den Aufruf bei aMethod.Invoke als auch die Signatur von ProgramSub wie angegeben angepasst? Es wird ein Parameter (das Form) übergeben und erwartet, das sollte eigentlich passen.
    Ein Callback könnte so aussehen, dass Du in Program statt des Forms ein Action<double, double> als Parameter erwartest und anstelle von TestSub aufrufst und bei aMethod.Invoke statt Me AddressOf Form1.TestSub übergibst.
    Matthias Hlawatsch 05.03.2011
    "in ProgrammSub" sollte es heißen - Kommentare kann man (leider) nicht nachträglich editieren
    Matthias Hlawatsch 05.03.2011
    Entschuldige es war mein Fehler, ich hatte
    die Zeile Public Sub ProgrammSub(tf As Form1)
    nicht aktualisiert. Super ! Jetzt funktioniert es.



    Vielen Dank
    ckflei 06.03.2011
    0
    RunSharp (siehe http://code.google.com/p/runsharp/) wäre eventuell was für Dich.
    01.03.2011
    Golo Roden 2,7k 3 9
    Hallo ich habe ein Programmbeispiel erstellt und gesendet.
    Ich weiß nicht ob das jeder der mir bisher geantwortet hat sieht. Darin habe ich meine beiden Probleme geschildert die noch zu lösen sind.
    ckflei 04.03.2011

    Stelle deine .net-Frage jetzt!
    TOP TECHNOLOGIES CONSULTING GmbH