Link to home
Start Free TrialLog in
Avatar of Bob Learned
Bob LearnedFlag for United States of America

asked on

Dynamically Run Code

Okay, I've struggled with this long enough, and I was hoping that somebody can give me a direction to go in that would provide a better solution.  I am using the following code in VB.NET 2003 to dynamically compile and run some code that is loaded from a text file:

  Public Function Compile(ByVal codeExecute As String, ByVal className As String, _
      ByVal outputName As String, ByRef arrayErrors As ArrayList) As Boolean

    ' Assume success.
    Dim boolValid As Boolean = True

    arrayErrors = New ArrayList

    Dim providerVB As New VBCodeProvider
    Dim compilerVB As ICodeCompiler = providerVB.CreateCompiler

    Dim paramCompiler As New CompilerParameters

    paramCompiler.GenerateInMemory = False

    'note it saves it in a temporary folder "user temp folder"
    'also note you could enhance this by pulling the namespace info
    'and class name from the code instead of having seperate vars for them.
    paramCompiler.OutputAssembly = outputName & ".dll"
    paramCompiler.MainClass = className
    paramCompiler.IncludeDebugInformation = True

    'I believe this is to get all the assemblies of the current
    'application and add them as references to the new assembly
    For Each assemblyVB As [Assembly] In AppDomain.CurrentDomain.GetAssemblies()
      paramCompiler.ReferencedAssemblies.Add(assemblyVB.Location)
    Next

    'here we do the deed
    Dim resultsCompile As CompilerResults = compilerVB.CompileAssemblyFromSource(paramCompiler, _
      codeExecute)

    'this is just to list out all the errors if any
    For Each errorCompile As CompilerError In resultsCompile.Errors
      arrayErrors.Add(errorCompile)
    Next

    If arrayErrors.Count = 0 Then

      'now we create an instance of the assembly newly created
      'and set it to an object notice this is also a good example of late binding

      Dim vArgs() As Object

      Dim nsName As String = Me.GetType.Assembly.GetEntryAssembly().GetName().Name

      Dim fullName = nsName & "." & outputName & "." & className

      Dim objectName As String = nsName & "." & className

      Dim objectRun As Object = resultsCompile.CompiledAssembly.CreateInstance(objectName)

      Dim typeClass As Type = resultsCompile.CompiledAssembly.GetType(objectName)
      Dim methodShowDialog As MethodInfo = typeClass.GetMethod("Test")

      'now we call the sub in the object

      Dim vParams() As Object

      methodShowDialog.Invoke(objectRun, vParams)

      'Dim domainRun As AppDomain = AppDomain.CreateDomain("Run", AppDomain.CurrentDomain.Evidence)
      'domainRun.AppendPrivatePath(Path.GetDirectoryName([Assembly].GetExecutingAssembly.Location.ToString))

      'Dim assemblyRun As [Assembly] = domainRun.Load(domainRun.RelativeSearchPath & "\" & outputName & ".dll")

    Else

      boolValid = False

    End If

    resultsCompile = Nothing
    providerVB = Nothing
    paramCompiler = Nothing
    compilerVB = Nothing

  End Function 'Compile'


It is a small test code that works correctly once:

Imports System
Imports System.Windows.Forms

Namespace AutoBuild.NET
  Public Class TestClass
     Public Sub Test
       MessageBox.Show("test message")
    End Sub
  End Class

End Namespace


The problem comes when I try to make a change to the file and run it again.  I get an error that the assembly cannot be recompiled since it is still in use.  This is because the assembly is still in memory, and cannot be unloaded.  I have tried to overcome this by creating a new AppDomain (commented code towards the bottom), and Loading the compiled assembly in the new AppDomain, and then Unloading the domain when I am through.

When I tried this, I kept getting an error that the DLL or one of its dependencies could not be found (Man, I hate this error!) :(

This may not be the best approach, and I am open to any kind of suggestions, hints, links, etc., that could help me find a solution to this problem.

TIA
Bob "The Learned One"
ASKER CERTIFIED SOLUTION
Avatar of eternal_21
eternal_21

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of Bob Learned

ASKER

That is the way that I had it before, but I was still getting the error.  

I noticed, though, that when it was True, it was still creating a file in the assembly bin folder.  The GenerateInMemory = False was an attempt to get it to load into the AppDomain.

Bob
Avatar of eternal_21
eternal_21

> I noticed, though, that when it was True, it was still creating a file in the assembly bin folder.

The DLL will get created, but after being loaded, there will be no lock on the file.  Can you post some more code?  I was able to repeatly generate a DLL file and execute the Test method with out another AppDomain, and without any errors...
Let me check this again, I'll get back to you.

Thanks,
Bob
Okay, I changed paramCompiler.GenerateInMemory = True, and now I don't get any error.  It must have been one of the 10,000 things that I was experimenting with that was causing my problem.  Now, I think I am back to the original problem that I lost track of, which is if I run the code, I get a message box, but if I change the text of the message box, and then try to recompile it, I get the old message box text.

This is the calling code:

  Private Sub RunScript()

    Dim richtextObject As RichTextBox
    Dim stringFileName As String

    Try

      richtextObject = Me.ActiveScriptControl
      stringFileName = Me.ActiveScriptFileName

      Dim errorMessage As String

      Dim compilerRunTime As New SourceCodeCompiler

      Dim boolValid As Boolean = compilerRunTime.Compile(richtextObject.Text, "TestClass", _
        "Test", errorMessage)

      If Not boolValid Then
        MsgBox(errorMessage)
      End If

      '      m_scriptEngine.SystemFont = Me.Font

      '     m_scriptEngine.RunScript(stringFileName, Me.ActiveScriptControl.Lines)

      buttonRun.Pushed = False

    Catch ex As Exception

      MsgBox(ex.ToString)

    End Try

  End Sub 'RunScript'


Thanks,
Bob
This is the reference that I used to start thinking about creating a new  AppDomain:

http://www.dotnet247.com/247reference/msgs/33/166928.aspx

Bob
Here is another that talks about AppDomains:

http://www.west-wind.com/presentations/dynamicCode/DynamicCode.htm

Bob
I think what I will do is to split the compiler code, and put it into another application, and make the calling program write a temporary file, and then pass the name of the file to the compiler application, which will get unloaded once it is done.

Thanks for your help.
Bob