Workaround for VB.NET partial method using CodeDom? - vb.net

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))

Related

Event 'Load' cannot be found

Getting the error "Event 'Load' cannot be found" referring to "Handles MyBase.Load" Please see attached code. Any help much appreciated!
I have many other applications set up the same way and they all work. However, these were in an older version of Visual Studio.
Option Explicit On
Option Strict On
Imports System, System.IO
Imports System.Text
Public Class Form1
Private Sub cleanXMLDialog_Load(ByVal eventSender As System.Object, ByVal
eventArgs As System.EventArgs) Handles MyBase.Load
Main()
End
End Sub
Public Sub Main()
Dim directories() As String = Directory.GetDirectories("C:\")
Dim files() As String = Directory.GetFiles("C:\", "*.dll")
DirSearch("c:\")
End Sub
Sub DirSearch(ByVal sDir As String)
Dim d As String
Dim f As String
Try
For Each d In Directory.GetDirectories(sDir)
For Each f In Directory.GetFiles(d, "*.xml")
'Dim Response As String = MsgBox(f)
Debug.Write(f)
Next
DirSearch(d)
Next
Catch excpt As System.Exception
Debug.WriteLine(excpt.Message)
End Try
End Sub
End Class
Load should happen without this error.
This is the class declaration:
Public Class Form1
It looks like you intend this to inherit from a windows Form type, but there's nothing here to make that happen.
You may want this:
Public Class Form1 Inherits System.Windows.Forms.Form
but even this is unlikely to really accomplish anything. It's not enough just to inherit from the Form type if you don't have any controls are properties set and don't ever show the form.
Did you accidentally create a Console or Class Library project when you mean to create a WinForms project?

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

'Sub Main' was not found. Service management console application error?

There is possibly a few things wrong with the below. Haven't had a chance to test/debug the code yet as can't run it. It stating that no main method has been found. But there is? i've even changed it to shared etc. It's probably something obvious?
It's flagging - 'Sub Main' was not found in 'ConsoleApplication1.Module1' error.
Also the main method wasn't always a separate class, I was just trying stuff. I'm importing a reference - system.processes. Was initially created as a vb.form but realised i didn't want the form part and recreated as a console app (which is very possibly where the problem lies as it's one of the first console apps i've done).
Code is basically planned to act on a service dying. Report and try and manage the restart (not finished, ideas welcome).
Imports System
Imports System.Management
Imports System.ServiceProcess
Imports System.Diagnostics
Imports System.Threading
Imports System.IO
Module Module1
Public Class Control
Public Sub Main() 'Public Sub Main(ByVal sArgs() As String)
Dim restart As New Rest
restart.startTime = DateTime.Now()
restart.cr20Services()
restart.Report()
End Sub
End Class
Public Class Rest
public startTime As String
Dim logPath As String = "C:\cr20\restart.txt"
'Dim fileExists As Boolean = File.Exists(strFile)
Dim arrcr20ServicesInitialStatus As New ArrayList
Dim failedServices As New ArrayList
Dim arrcr20Services As New ArrayList
Public Sub cr20Services()
'cr20 Services
arrcr20Services.Add("cr20 service")
arrcr20Services.Add("cr20 router")
For Each cr20Service In arrcr20Services
arrcr20ServicesInitialStatus.Add(cr20Service & " - " & cr20Status(cr20Service))
cr20Restore(cr20Service)
Next
End Sub
Private Function cr20Status(ByVal cr20Service As String)
Dim service As ServiceController = New ServiceController(cr20Service)
Return service.Status.ToString
End Function
Private Sub cr20Restore(ByVal cr20Service As String)
Dim service As ServiceController = New ServiceController(cr20Service)
'Dim p() As System.Diagnostics.Process = System.Diagnostics.Process.GetProcessesByName("calc")
If (service.Status.Equals(ServiceControllerStatus.Stopped)) Or (service.Status.Equals(ServiceControllerStatus.StopPending)) Then
failedServices.Add(service)
service.Stop()
Thread.Sleep(10000) 'give service 10 seconds to stop
service.Start()
End If
End Sub

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

Executing runtime code parameters

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"})