Bob Learned
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.GenerateInMe mory = 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.OutputAssemb ly = outputName & ".dll"
paramCompiler.MainClass = className
paramCompiler.IncludeDebug Informatio n = 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.Ge tAssemblie s()
paramCompiler.ReferencedAs semblies.A dd(assembl yVB.Locati on)
Next
'here we do the deed
Dim resultsCompile As CompilerResults = compilerVB.CompileAssembly FromSource (paramComp iler, _
codeExecute)
'this is just to list out all the errors if any
For Each errorCompile As CompilerError In resultsCompile.Errors
arrayErrors.Add(errorCompi le)
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.GetEnt ryAssembly ().GetName ().Name
Dim fullName = nsName & "." & outputName & "." & className
Dim objectName As String = nsName & "." & className
Dim objectRun As Object = resultsCompile.CompiledAss embly.Crea teInstance (objectNam e)
Dim typeClass As Type = resultsCompile.CompiledAss embly.GetT ype(object Name)
Dim methodShowDialog As MethodInfo = typeClass.GetMethod("Test" )
'now we call the sub in the object
Dim vParams() As Object
methodShowDialog.Invoke(ob jectRun, vParams)
'Dim domainRun As AppDomain = AppDomain.CreateDomain("Ru n", AppDomain.CurrentDomain.Ev idence)
'domainRun.AppendPrivatePa th(Path.Ge tDirectory Name([Asse mbly].GetE xecutingAs sembly.Loc ation.ToSt ring))
'Dim assemblyRun As [Assembly] = domainRun.Load(domainRun.R elativeSea rchPath & "\" & 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"
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.GenerateInMe
'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.OutputAssemb
paramCompiler.MainClass = className
paramCompiler.IncludeDebug
'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.Ge
paramCompiler.ReferencedAs
Next
'here we do the deed
Dim resultsCompile As CompilerResults = compilerVB.CompileAssembly
codeExecute)
'this is just to list out all the errors if any
For Each errorCompile As CompilerError In resultsCompile.Errors
arrayErrors.Add(errorCompi
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.GetEnt
Dim fullName = nsName & "." & outputName & "." & className
Dim objectName As String = nsName & "." & className
Dim objectRun As Object = resultsCompile.CompiledAss
Dim typeClass As Type = resultsCompile.CompiledAss
Dim methodShowDialog As MethodInfo = typeClass.GetMethod("Test"
'now we call the sub in the object
Dim vParams() As Object
methodShowDialog.Invoke(ob
'Dim domainRun As AppDomain = AppDomain.CreateDomain("Ru
'domainRun.AppendPrivatePa
'Dim assemblyRun As [Assembly] = domainRun.Load(domainRun.R
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
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
> 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...
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...
ASKER
Let me check this again, I'll get back to you.
Thanks,
Bob
Thanks,
Bob
ASKER
Okay, I changed paramCompiler.GenerateInMe mory = 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(ri chtextObje ct.Text, "TestClass", _
"Test", errorMessage)
If Not boolValid Then
MsgBox(errorMessage)
End If
' m_scriptEngine.SystemFont = Me.Font
' m_scriptEngine.RunScript(s tringFileN ame, Me.ActiveScriptControl.Lin es)
buttonRun.Pushed = False
Catch ex As Exception
MsgBox(ex.ToString)
End Try
End Sub 'RunScript'
Thanks,
Bob
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(ri
"Test", errorMessage)
If Not boolValid Then
MsgBox(errorMessage)
End If
' m_scriptEngine.SystemFont = Me.Font
' m_scriptEngine.RunScript(s
buttonRun.Pushed = False
Catch ex As Exception
MsgBox(ex.ToString)
End Try
End Sub 'RunScript'
Thanks,
Bob
ASKER
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
http://www.dotnet247.com/247reference/msgs/33/166928.aspx
Bob
ASKER
Here is another that talks about AppDomains:
http://www.west-wind.com/presentations/dynamicCode/DynamicCode.htm
Bob
http://www.west-wind.com/presentations/dynamicCode/DynamicCode.htm
Bob
ASKER
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
Thanks for your help.
Bob
ASKER
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