How do I call a subroutine from a string variable? - vb.net

Using VB.Net 4 and VS2012
I have a Module with some logic in it like this:
Module Mod1
If x = 1 then
Mod2("Mod3","save_it")
else
Mod2("Mod4","edit_it")
end if
End Module
Module Mod2(which_mod, which_action)
' call the correct subroutine
which_mod.which_action()
End Module
How do I use the strings to call the correct subroutine from different modules?

Look at the System.Reflection namespace, it contains a class called MethodInfo.
You can obtain the MethodInfo, for a given object, using the method name, then invoke it:
Dim method As MethodInfo = obj.GetType().GetMethod(methodName, BindingFlags.Instance Or BindingFlags.Public)
method.Invoke()

source
There is a function CallByName which does exactly that.
The function accepts 4 parameters:
object/class
function/procname
calltype (CallType.method, CallType.Get, CallType.Set)
(optional): parameter (in arrayformat)
The first parameter (object/class) can't be a string, so we have to cast your string to an object. The best way to do this is
Dim MyInstance As Object = Activator.CreateInstance(Type.GetType(which_mod))
So for your code:
Imports Microsoft.VisualBasic.CallType
Imports System.Reflection
Class Mod1
If x = 1 then
Mod2("Mod3","save_it")
else
Mod2("Mod4","edit_it")
end if
End Class
Module Mod2(which_mod, which_action)
' call the correct subroutine
Dim MyInstance As Object = Activator.CreateInstance(Type.GetType(which_mod))
CallByName(MyInstance , which_action, CallType.Method)
End Module

Related

Can UDT's be used as method parameters in any way?

For years I've been avoiding the use of Public Type UDT's in VBA, because they're hard to pass around and I never really bothered trying to understand why.. until now - it was simply easier to just create a class module and work with actual objects instead.
But recently I gave it a shot, and once I figured they had to be passed ByRef (as an array would), things started to look like I could start using them.
So I defined a Public Type in a standard module, got this compile error:
So I moved the Public Type into a class module, made the class PublicNotCreatable, and then got this compile error:
Here's some code to reproduce the compile error.
Class module "Something":
Option Explicit
' cannot define a public user-defined type within an object module
Public Type TSomething
Foo As Integer
End Type
Public Function Create(ByRef info As TSomething) As Something
End Function
If you move the definition of TSomething to a standard module, you'll get the other compiler error, telling you that the public UDT must be defined in a public object module (i.e. a class module)... which takes you back to square one.
So if you cannot define a Public Type in a class module, why would the compiler throw a fit and even mention "public user defined types defined in public object modules" if such a thing can't legally exist?
Did it work in VB6 and the compiler message is a remnant of that version? Or is the reason somewhere in how COM works? Is it just me or the two error messages are contradicting each other? Or there's something I'm not understanding?
Obviously I'm misusing/abusing UDT's here. So what are they supposed to be used for, if not for passing a "record" to some method?
From standard module it works without any error. Following code threw no error.
Public Type TEST_TYPE
Prop1 As String
End Type
Public Function fTest(ByRef param1 As TEST_TYPE) As String
param1.Prop1 = "Hello from function"
End Function
Public Sub sTest(ByRef param1 As TEST_TYPE)
param1.Prop1 = "Hello from Sub"
End Sub
Public Sub caller()
Dim p As TEST_TYPE
'/Call Sub
Call sTest(p)
MsgBox p.Prop1
'/Call Function
Call fTest(p)
MsgBox p.Prop1
End Sub
One issue with UDT is about Forward referencing. So this will not compile, apart from that It works perfectly fine with standard modules.
Public Type TEST_TYPE
Prop1 As String
Prop2 As TEST_TYPE2 '/ Fails due to Forward referencing. TEST_TYPE2 should be declared before this UDT.
End Type
Public Type TEST_TYPE2
Prop3 As String
End Type
Edit:
However, the work around to use the UDT in class is Friend
VBA Code for Class
'/ Using UDT in VBA-Class
Private Type TEST_TYPE3
Prop3 As String
End Type
Public Sub caller()
Dim p As TEST_TYPE3
p.Prop3 = "Hello from Class"
Call testClassUDT(p)
End Sub
Friend Sub testClassUDT(p As TEST_TYPE3)
MsgBox p.Prop3
End Sub
Here's a Type being passed as a parameter to a class method, and being returned by a class method.
First the class SomeClass (doesn't need to be PublicNotCreatable)
Option Explicit
Sub test(foo As TFooBar)
Dim s As String
s = foo.foo
End Sub
Function ReturnTFoo() As TFooBar
ReturnTFoo.bar = "bar"
ReturnTFoo.foo = " bar"
End Function
And the Module:
Option Explicit
Public Type TFooBar
foo As String
bar As String
End Type
Sub test()
Dim c As SomeClass
Set c = New SomeClass
Dim t1 As TFooBar
Dim t2 As TFooBar
t1.bar = "bar"
t1.foo = "Foo"
c.test t1
t2 = c.ReturnTFoo
End Sub

Is scoping broken in VBA?

Say you have this code in a module called Module1:
Option Explicit
Private Type TSomething
Foo As Integer
Bar As Integer
End Type
Public Something As TSomething
In equivalent C# code if you made the Something field public, the code would no longer compile, because of inconsistent accessibility - the type of the field being less accessible than the field itself. Which makes sense.
However in VBA you could have this code in Module2:
Sub DoSomething()
Module1.Something.Bar = 42
Debug.Print Module1.Something.Bar
End Sub
And you get IntelliSense while typing it, and it compiles, and it runs, and it outputs 42.
Why? How does it even work, from a COM standpoint? Is it part of the language specs?
As per my comment, VBA exposes a private Type, just like it exposes a Private Enum.
VBA assumes you can make use of the TypeInfo in the consuming context, but it won't allow you to declare or create instances of those types or enums.
This C++ answer is partly informative:
Access Control is applied to names
The access specifier for the name has nothing to do with it's type
But it's perhaps useful to think of a Private Type in a standard module, as something like a "PublicNotCreatable" class. If you provide a public wrapper, then the type is accessible outside the host module.
But VBA handles things differently when the Type is in a Public Class Module!
Here's your Module1 expanded:
Option Explicit
Private Type TSomething
Foo As Integer
Bar As Integer
End Type
Public Type TOtherThing
Foo As Integer
Bar As Integer
End Type
Public Type TWrapperThing
Something As TSomething
End Type
Public Something As TSomething
Public Otherthing As TOtherThing
Public Wrapperthing As TWrapperThing
Public Function GetSomething() As TSomething
GetSomething.Foo = 1
End Function
Public Function GetOtherthing() As TOtherThing
GetOtherthing.Foo = 1
End Function
And Module2 expanded:
Option Explicit
Sub DoThings()
'Compile Error: User-defined type not defined
'Dim oSomething As TSomething
Dim vSomething As Variant
Dim oOtherthing As Module1.TOtherThing
Dim vOtherthing As Variant
Dim oWrapperthing As Module1.TWrapperThing
Module1.Something.Foo = 42
Module1.Otherthing.Foo = 42
Module1.Wrapperthing.Something.Foo = 42
'Compile Error: Only user-defined types defined in public object modules can be coerced to or from a variant or passed to late-bound functions
'vSomething = Module1.Something
'vOtherthing = Module1.Otherthing
oOtherthing = Module1.Otherthing
oOtherthing.Foo = 43
'Is 43 > 42?
Debug.Assert oOtherthing.Foo > Module1.Otherthing.Foo
'Compile Errors: "GetSomething" User-defined type not defined
'Module1.GetSomething.Foo = 42
'Module1.GetSomething().Foo = 42
Module1.GetOtherthing.Foo = 42
Module1.GetOtherthing().Foo = 42
End Sub

How to call a function which name is stored in a sql table (VB.net)

Maybe someone of you can help me with that problem.
I have written a background task which gets several workers out of a database.
In the database I added to each worker the name of the function which should get called.
But I am not sure how to call that function from vb.net.
It would be awesome if someone of you can give me a hint :)
thanks
Cheers
Arthur
The namespace System.Reflection has numerous methods that enable this functionality, such as this one:
From MSDN, the example in the link above:
Imports System
Imports System.Reflection
Public Class MagicClass
Private magicBaseValue As Integer
Public Sub New()
magicBaseValue = 9
End Sub
Public Function ItsMagic(preMagic As Integer) As Integer
Return preMagic * magicBaseValue
End Function
End Class
Public Class TestMethodInfo
Public Shared Sub Main()
' Get the constructor and create an instance of MagicClass
Dim magicType As Type = Type.GetType("MagicClass")
Dim magicConstructor As ConstructorInfo = magicType.GetConstructor(Type.EmptyTypes)
Dim magicClassObject As Object = magicConstructor.Invoke(New Object(){})
' Get the ItsMagic method and invoke with a parameter value of 100
Dim magicMethod As MethodInfo = magicType.GetMethod("ItsMagic")
Dim magicValue As Object = magicMethod.Invoke(magicClassObject, New Object(){100})
Console.WriteLine("MethodInfo.Invoke() Example" + vbNewLine)
Console.WriteLine("MagicClass.ItsMagic() returned: {0}", magicValue)
End Sub
End Class
' The example program gives the following output:
'
' MethodInfo.Invoke() Example
'
' MagicClass.ItsMagic() returned: 900
I would suggest having a dictionary of delegates in your application code. Store the keys in the database, rather than the function names. When you retrieve the key from the database, look it up in the dictionary and, if present, execute it. You don't want to allow arbitrary methods to be executed based on names stored in a database.
They keys could be strings or integers. I'd prefer the latter just for space savings and ease of lookup, but strings would be easier for debugging, perhaps. So you'd have a dictionary like this:
Private m_WorkerDelegates As New Dictionary(Of String, Action)()
Somewhere else, you'd fill it up with the available workers:
m_WorkerDelegates.Add("worker1", AddressOf WorkerMethod1)
m_WorkerDelegates.Add("worker2", AddressOf WorkerMethod2)
And then, when retrieving from the database, you'd look up the method in your dictionary:
Public Sub ExecuteWorker(ByVal row As DataRow)
Dim key As String = CStr(row("worker_key"))
If Not m_WorkerDelegates.ContainsKey(key) Then
' either throw exception or report the error in some more effective way '
Throw New Exception("Invalid worker key specified")
End If
' actually call the worker method '
m_WorkerDelegates(key)()
End Sub

force module namespace qualification

VB.net allows you to skip the qualification of a function call with the module name:
Public Module EvalDataFetcher
Public Function JoinStr(ByVal values As IEnumerable(Of String)) As String
' body
End Function
End Module
And then do:
Dim foo As String = JoinStr(myBars)
How to force the users to use the fully qualified form? ie force:
Dim foo As String = EvalDataFetcher.JoinStr(myBars)
If there is a way to force you to specify the module name, I'm not sure what it would be. However, the way you ca do it is to make it a class with shared members rather than a module. For instance:
Public Class EvalDataFetcher
Public Shared Function JoinStr(ByVal values As IEnumerable(Of String)) As String
' body
End Function
End Module
Now, when you call the JoinStr method, you will be forced to specify the class name:
Dim foo1 As String = JoinStr(myBars) ' Won't compile
Dim foo2 As String = EvalDataFetcher.JoinStr(myBars) ' Works
This behavior can be achieved with HideModuleName attribute as workaround - HideModuleNameAttribute
Wrap your module with namespace and add HideModuleName attribute to the module
Namespace Utils
<HideModuleName()>
Friend Module UtilsModule
Public Sub YourMethod(parameter As Object)
'Method code
End Sub
End Module
End Namespace
If namespace will not be added with the Imports in the file, then
you will need to use namespace name and HideModuleName attribute will hide module name from the intellisense
Utils.YourMethod(param)

Script Task : write 2 classes and access Global variable

Can i write 2 classes for one Script Task editor in SSIS(2008). I tried to access the global variable like below. I created 2 classes and it doesn't show any compile error, but i couldn't access the global variable in class2 which was assigned as 2 in class, ScriptMain. Please suggest.
Imports System
Imports System.Data
Class ScriptMain
Dts.Variables("var").Value = 2
End Class
Class class2
Dim var2 As String
var2 = Dts.Variables("var").Value
End Class
Disclaimer - I don't know anything about SSIS, but ...
You can't use Dim statements in the body of a class - they need to be in a method in .net.
There are special methods called constructors (New), that get called when a class is instantiated though, so try
Imports System
Imports System.Data
Class ScriptMain
public sub new()
Dts.Variables("var").Value = 2
end sub
End Class
Class class2
public function GetGlobal() as string
Dim var2 As String
var2 = Dts.Variables("var").Value
return var2
end function
End Class
You'd need to do Dim x as new class2() somewhere and then you can do a call to x.GetGlobal(). Since GetGlobal doesn't make use of any instance state, you could just make it shared.
Beyond that, it's not clear as to what you are trying to accomplish with specificity.