Executing runtime code parameters - vb.net

The following is some code to execute code at runtime:
Dim SourceCode As String = txtCode.Text
Dim Dlls() As String = {"System.dll", "System.Core.dll", "System.Data.dll", "System.Windows.Forms.dll"} 'Any referenced dll's
Dim Compiler As New VbCompiler(SourceCode, Dlls)
Dim CodeAssembly As Assembly = Compiler.Compile
If Compiler.Successful Then
Dim instance As Object = CodeAssembly.CreateInstance("TestCode.Class1")
Dim CodeType As Type = instance.GetType
Dim Info As MethodInfo = CodeType.GetMethod("ShowMessage")
Info.Invoke(instance, Nothing)
Else
For Each i As CompilerError In Compiler.Errors
MsgBox(i.ErrorText)
Next
End If
txtCode.text =:
Imports System.Windows.Forms
Namespace TestCode
Class Class1
Sub ShowMessage()
MessageBox.Show("Test")
End Sub
End Class
End Namespace
This works perfectly. I am wanting to know how to pass arguments to a function. ie
txtCode.text = :
Imports System.Windows.Forms
Namespace TestCode
Class Class1
Sub ShowMessage(ByVal x As String)
MessageBox.Show(x)
End Sub
End Class
End Namespace
I would like to run the 'ShowMessage" function with a string as the parameter, ("Test") for an example.
I am pretty sure it is at the following lines:
Dim Info As MethodInfo = CodeType.GetMethod("ShowMessage")
Info.Invoke(instance, Nothing)
But I cannot get it working.

You need to pass string value.
Info.Invoke(instance, New Object(){"Test"})
EDIT: Two arguments
Info.Invoke(instance, New Object(){"First","Second"})

Related

How to get access to the functions from a module

I need to get access to the public functions in a module (not a class). This is what I have tried:
Dim asm As Reflection.Assembly = Assembly.GetExecutingAssembly
Dim my_modules = asm.GetModules
For Each my_module In my_modules
Dim all_methods = my_module.GetMethods
Stop
Next
But that doesn't even get down to the module itself, I just get the name of the executable.
As #jmcilhinney said in the comments a Module is like a Class when using reflection. You can access it using GetType or GetTypes method.
Dim asm As Assembly = Assembly.GetExecutingAssembly
Dim my_module = asm.GetType("Module_Full_Name")
Dim allMethods = my_module.GetMethods()
Module in VB.NET is nothing else than static class. You not create instance of static class, just call its members from anywhere in scope where module is declared.
Public Module Mathematics
Public Function Sum(x As Integer, y As Integer) As Integer
Return x + y
End Function
End Module
Class Form1
Public Sub New()
InitializeComponent()
Dim result = Mathematics.Sum(1, 2)
End Sub
End Class

Invoke member through reflection passing derived class as argument

I'm trying to call the method 'MyMethod' of class 'CMyClass'. This method has a parameter of type "CBaseClass", and I'm passing an object of type "CDerivedClass".
Class CBaseClass
Public m_AMember As String
Sub MethodOne()
// DoSomething
End Sub
End Class
Class CDerivedClass
Inherits CBaseClass
Public m_MyMember As Integer
Sub MethodTwo()
// DoSomething
End Sub
End Class
Class CMyClass
Sub MyMethod(ByVal obj As CBaseClass)
// DoSomething
End Sub
End Class
Sub Main()
'load assembly
Dim objAssembly As Assembly = Assembly.LoadFrom("myfile.dll")
'create class instance and MethodInfo object
Dim t As Type = objAssembly.GetType("MyNamespace.CMyClass")
Dim obj As Object = Activator.CreateInstance(t)
Debug.Assert(obj IsNot Nothing)
Dim m As MethodInfo = t.GetMethod("MyMethod")
Debug.Assert(m IsNot Nothing)
'Init arguments (only one)
Dim par As New CDerivedClass()
Dim parameters As Object() = New Object(0) {par}
'invoke method
m.Invoke(obj, parameters) '<<<< ArgumentException here!
End Sub
The argument exception says "object of type 'MyNamespace.CDerivedClass' cannot be converted to type 'MyNamespace.CBaseClass'.
I changed "ByRef" to "ByVal" in MyMethod signature, but nothing changed.
I tried to change type of 'par' object with:
Dim par As CBaseClass = New CDerivedClass()
without success.
How I can invoke correctly the method "MyMethod" with an instance of derived class?
Thank you very much.
I just tried your code in a stand alone project and it worked fine. I only adjusted the assembly line to get the current assembly instead of from a file, and I changed the name in GetType to reflect my namespace. My guess is that perhaps your DLL you are loading is out of date, or you have duplicate definitions of the classes in your calling assembly, and thats why it cannot do the type conversion.
Imports System.Reflection
Module Module1
Sub Main()
'load assembly
Dim objAssembly As Assembly = Assembly.GetExecutingAssembly()
'create class instance and MethodInfo object
Dim t As Type = objAssembly.GetType("ConsoleApplication1.CMyClass")
Dim obj As Object = Activator.CreateInstance(t)
Debug.Assert(obj IsNot Nothing)
Dim m As MethodInfo = t.GetMethod("MyMethod")
Debug.Assert(m IsNot Nothing)
'Init arguments (only one)
Dim par As New CDerivedClass()
Dim parameters As Object() = New Object(0) {par}
'invoke method
m.Invoke(obj, parameters) '<<<< No ArgumentException here!
End Sub
End Module
Class CBaseClass
Public m_AMember As String
Sub MethodOne()
End Sub
End Class
Class CDerivedClass
Inherits CBaseClass
Public m_MyMember As Integer
Sub MethodTwo()
End Sub
End Class
Class CMyClass
Sub MyMethod(ByVal obj As CBaseClass)
End Sub
End Class
Finally I solved using serialization...
So 'par' is the string containing the serialized object of type CDerivedClass in the calling project.
MyMethod is changed to:
MyMethod(xml_CBaseClass As String)
In dll project the string parameter xml_CBaseClass is deserialized creating an object of CBaseClass.
Note: since I have derived type, Deserialization of derived class give another problem. The solution is https://stackoverflow.com/a/590711/1315873
(I just made a little change, using StringWriter for serialization, StringReader for deserialization, instead of using MemoryBuffer).
CBaseClass has fixed derived types so I wrote them hard-coded, but to be flexible you can do something like:
Dim subTypes as New List(Of Type) '<- all classes derived from 'myType'
For Each t In myType.Assembly.GetTypes()
If t.IsSubclassOf(myType) Then
subTypes.Add(t)
End If
Next
CBaseClass and all its derivated classes must have constructor New() without parameters.
I load assemblies using LoadFrom() since I don't know their names (I use Dir() to get all them from a known fixed folder).

How to load an internal class in end-user compiled code ? (advanced)

I have a main program with two classes
1- A winform that contains two elements :
Memo Edit to type some code;
Button named compile.
The end user may type some VB.Net code in the memo edit and then compile it.
2 - A simple test class :
Code :
Public Class ClassTest
Public Sub New()
MsgBox("coucou")
End Sub
End Class
Now I would like to use the class ClassTest in the code that will be typed in the MemoEdit and then compile it :
When hitting compile I recieve the error :
The reason is that, the compiler can't find the namespace ClassTest
So to summarize :
The class ClassTest is created in the main program
The end user should be able to use it and create a new assembly at run time
Does anyone know how to do that please ?
Thank you in advance for your help.
Code of the WinForm :
Public Class Form1
Private Sub SimpleButtonCompile_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles SimpleButtonCompile.Click
Dim Code As String = Me.MemoEdit1.Text
Dim CompilerResult As CompilerResults
CompilerResult = Compile(Code)
End Sub
Public Function Compile(ByVal Code As String) As CompilerResults
Dim CodeProvider As New VBCodeProvider
Dim CodeCompiler As System.CodeDom.Compiler.CodeDomProvider = CodeDomProvider.CreateProvider("VisualBasic")
Dim Parameters As New System.CodeDom.Compiler.CompilerParameters
Parameters.GenerateExecutable = False
Dim CompilerResult As CompilerResults = CodeCompiler.CompileAssemblyFromSource(Parameters, Code)
If CompilerResult.Errors.HasErrors Then
For i = 0 To CompilerResult.Errors.Count - 1
MsgBox(CompilerResult.Errors(i).ErrorText)
Next
Return Nothing
Else
Return CompilerResult
End If
End Function
End Class
Here is the solution :
If the end user wants to use internal classes he should use the command : Assembly.GetExecutingAssembly
The full code will be :
Code :
Imports System.Reflection
Imports System
Public Class EndUserClass
Public Sub New()
Dim Assembly As Assembly = Assembly.GetExecutingAssembly
Dim ClassType As Type = Assembly.GetType(Assembly.GetName().Name & ".ClassTest")
Dim Instance = Activator.CreateInstance(ClassType)
End Sub
End class

Cannot load temporary file in VBCodeProvider

Here's my script code:
Imports System.Diagnostics
Public Class Script
Implements IScript
Public Sub DoWork(w As WebBrowser, f As Form1) Implements IScript.DoWork
w.Navigate("http://www.google.com")
wait("5000")
w.Document.All("input").InvokeMember("click")
w.Document.All("input").SetAttribute("value", "Torrenter is the best!")
wait("2000")
w.Document.All("421").InvokeMember("click")
wait("1000")
End Sub
Public Sub wait(ByVal interval As Integer)
Dim sw As New Stopwatch
sw.Start()
Do While sw.ElapsedMilliseconds < interval
' Allows UI to remain responsive
Application.DoEvents()
Loop
sw.Stop()
End Sub
End Class
In-code:
Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
If int1.Text = "1" Then
int1.Text = "0"
Dim script As IScript = GenerateScript(File.ReadAllText(ListBox2.Items.Item(int2).ToString()))
script.DoWork(WebBrowser1, Me) 'Object reference not set to an instance of an object.
int2 = int2 + 1
int1.Text = "1"
End If
End Sub
Why? :(
It's supposed to start the next script after the first was done. I tried 4 methods but I can't understand why.
The problem is that your script code is failing to compile, but then you are trying to instantiate an object from the compiled assembly anyway. Since it failed to compile, the assembly doesn't actually exist, hence the error. If you modify the Return line in the GenerateScript method, so that it shows the compile errors, the actual problem will be more clear:
Dim results As CompilerResults = provider.CompileAssemblyFromSource(parameters, codes)
If results.Errors.HasErrors Then
Dim builder As New StringBuilder()
builder.AppendLine("Script failed to compile due to the following errors:")
For Each i As CompilerError In results.Errors
builder.AppendFormat("Line {0}: {1}", i.Line, i.ErrorText)
builder.AppendLine()
Next
Throw New Exception(builder.ToString())
Else
Return CType(results.CompiledAssembly.CreateInstance("Script"), IScript)
End If
I suspect that one of the reasons it's failing to compile is because the script uses IScript which is undefined. The reason it would complain that it's undefined is for two reasons. First, you declared the IScript interface as nested inside the Form1 class. You should move that outside of the form class so that it's not nested inside of any other type. Second, you are not specifying the full namespace nor importing the namespace in your script. You can automatically add the Imports line to the beginning of the script code before compiling it, like this:
Dim interfaceNamespace As String = GetType(IScript).Namespace
Dim codes As String = "Imports " & interfaceNamespace & Environment.NewLine & code
As I mentioned in the comments above, you really ought to be passing a string array into the CompileAssemblyFromSource method, not a string. I'm not sure how that even compiles, unless that's something having Option Strict Off somehow allows? In any case, it expects an array, so you should really be giving it one, like this:
Dim interfaceNamespace As String = GetType(IScript).Namespace
Dim codeArray() As String = New String() {"Imports " & interfaceNamespace & Environment.NewLine & code}
Dim results As CompilerResults = provider.CompileAssemblyFromSource(parameters, codeArray)
Another obvious reason why the script would fail to compile is because you have it using methods and properties of your Form1 class, as if it were a member of that class. Remember, the Script class defined by the script file source code is a completely separate class in a separate assembly. It will have no reference to the form unless you give it a reference, for instance, you could define the interface like this:
Public Interface IScript
Sub DoWork(f As Form1)
End Interface
Then, in your script, you could do this:
Public Class Script
Implements IScript
Public Sub DoWork(f As Form1) Implements IScript.DoWork
f.WebBrowser1.Navigate("http://www.google.com")
f.wait("5000")
f.wait("4000")
f.WebBrowser1.Document.All("input").InvokeMember("click")
f.WebBrowser1.Document.All("input").SetAttribute("value", "User")
f.wait("2000")
f.WebBrowser1.Document.All("421").InvokeMember("click")
End Sub
End Class
UPDATE
Ok, since you can't get it working, and I don't want this whole conversation to be a total loss, I put together a working project and tested it. Here's what you need to do to get it to work.
Contents of IScript.vb
Public Interface IScript
Sub DoWork(w As WebBrowser)
End Interface
Contents of Form1.vb
Imports Microsoft.VisualBasic
Imports System.CodeDom.Compiler
Imports System.Reflection
Imports System.IO
Imports System.Text
Public Class Form1
Dim int1 As Integer = 0
Dim int2 As Integer = 0
Dim p As Point
Public Function GenerateScript(ByVal code As String) As IScript
Using provider As New VBCodeProvider()
Dim parameters As New CompilerParameters()
parameters.GenerateInMemory = True
parameters.ReferencedAssemblies.Add(GetType(WebBrowser).Assembly.Location)
parameters.ReferencedAssemblies.Add(Assembly.GetExecutingAssembly().Location)
Dim interfaceNamespace As String = GetType(IScript).Namespace
code = "Imports System.Windows.Forms" & Environment.NewLine & "Imports " & interfaceNamespace & Environment.NewLine & code
Dim results As CompilerResults = provider.CompileAssemblyFromSource(parameters, code)
If results.Errors.HasErrors Then
Dim builder As New StringBuilder()
builder.AppendLine("Script failed to compile due to the following errors:")
For Each i As CompilerError In results.Errors
builder.AppendFormat("Line {0}: {1}", i.Line, i.ErrorText)
builder.AppendLine()
Next
Throw New Exception(builder.ToString())
Else
Return CType(results.CompiledAssembly.CreateInstance("Script"), IScript)
End If
End Using
End Function
Public Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
For Each File As FileInfo In New System.IO.DirectoryInfo(Application.StartupPath & "/scripts").GetFiles
If CheckedListBox1.GetItemCheckState(int2) = CheckState.Checked Then
ListBox1.Items.Add(File.FullName)
End If
int2 = int2 + 1
Next
int2 = 0
Dim script As IScript = GenerateScript(File.ReadAllText(ListBox1.Items.Item(int2).ToString()))
script.DoWork(WebBrowser1)
End Sub
End Class
Contents of script file
Imports System.Diagnostics
Public Class Script
Implements IScript
Public Sub DoWork(w As WebBrowser) Implements IScript.DoWork
w.Navigate("http://www.google.com")
wait("5000")
wait("4000")
w.Document.All("input").InvokeMember("click")
w.Document.All("input").SetAttribute("value", "User")
wait("2000")
w.Document.All("421").InvokeMember("click")
End Sub
Public Sub wait(ByVal interval As Integer)
Dim sw As New Stopwatch
sw.Start()
Do While sw.ElapsedMilliseconds < interval
' Allows UI to remain responsive
Application.DoEvents()
Loop
sw.Stop()
End Sub
End Class

Workaround for VB.NET partial method using CodeDom?

I know CodeDom doesn't support partial methods, but is there a workaround? I found a workaround for C#, but I need one for VB.NET. Thanks.
It is a horrible hack, as wicked as the C# one, but it does work:
Imports System.CodeDom
Imports System.CodeDom.Compiler
Imports System.IO
Module Module1
Sub Main()
Dim unit As New CodeCompileUnit
Dim nspace As New CodeNamespace("SomeNamespace")
Dim vbclass As New CodeTypeDeclaration("SomeClass")
vbclass.IsClass = True
Dim snippet As New CodeSnippetTypeMember("Partial _")
vbclass.Members.Add(snippet)
Dim method As New CodeMemberMethod()
method.Name = "SomeMethod"
method.Attributes = MemberAttributes.Private
vbclass.Members.Add(method)
nspace.Types.Add(vbclass)
unit.Namespaces.Add(nspace)
dim provider As CodeDomProvider = CodeDomProvider.CreateProvider("VB")
Dim options As New CodeGeneratorOptions()
options.BlankLinesBetweenMembers = False
dim writer As new StringWriter()
provider.GenerateCodeFromCompileUnit(unit, writer, options)
Console.WriteLine(writer.ToString())
Console.ReadLine()
End Sub
End Module
Note that the BlankLinesBetweenMembers option is crucial to make the hack work. Output:
Option Strict Off
Option Explicit On
Namespace SomeNamespace
Public Class SomeClass
Partial _
Private Sub SomeMethod()
End Sub
End Class
End Namespace
I ended up detecting the language and using CodeSnippetTypeMember()
Dim onDeleting = "Partial Private Sub OnDeleting()" & Environment.NewLine & "End Sub"
type.Members.Add(New CodeSnippetTypeMember(onDeleting))