Application.run doesn't work with module - ms-access

I have two modules. In one module I want to run a sub from the other module indirectly. According to MS and a multitude of online ressources this should work - but it doesn't. What could be the problem?
'Module: "Helpers"
Public Sub ubTest()
MsgBox "ubTest is called"
End Sub
'Another Module -> I also tried this from a form and a class...
Public Sub test()
Dim s As String
Helpers.ubTest 'works
s = "ubTest"
Application.Run s 'works
s = "Helpers.ubTest"
Application.Run s 'doesn't work
End Sub
(Obviously this is a test - in the real application I will have multiple modules and will not always have control over the procedure names - so I have to use the module-prefix)
I tried to /decompile and compact the database - no luck there either.

The Access Application.Run Method help topic says this about the Name parameter:
'If you are calling a procedure in another database use the project name and the procedure name separated by a dot in the form: "projectname.procedurename".'
So I think when you supply "modulename.procedurename" (ie "Helpers.ubTest"), Access thinks your modulename is the name of a VBA project. Since it can't find a project named Helpers, it throws error #2517, " ... can't find the procedure 'Helpers.ubTest.'"
Unfortunately, I can't find a way to do what I think you want with Application.Run. I hoped "projectname.modulename.procedurename" would work, but that also triggered the 2517 error.

After a long research I came up with a workaround that suits for me perfectly, maybe it works for you too.
Public Function RunModuleFunction(moduleName As String, functionName As String, Optional arg1 As Variant, Optional arg2 As Variant, Optional arg3 As Variant, Optional arg4 As Variant, Optional arg5 As Variant) As Variant
Dim mdl As Module
Set mdl = Modules(moduleName)
RunModuleFunction = mdl.Application.Run(functionName, arg1, arg2, arg3, arg4, arg5)
Set mdl = Nothing
End Function
Unfortunately Appliaction.Run has not parameters defined as ParamArray, in definition is optional parameters arg1-arg30. So you must use it as above and write them one by one.
For my solution 5 parameters is enough, but you can add more up to 30.

The answers of #HansUp and #Johannes (although I don't quite understand his solution) (and lot's of trial and error) lead me to this knowledge:
Application.Run within Access won't try to use the first part of the procedurename (before the dot) as Module name, only as Project name
Thus, the most important lesson, if you want to call a specific function name of a module through Application.Run, the function must be named uniquely in the whole project.
See the Microsoft docs:
Application.Run in Access
Application.Run in Excel
Application.Run in Word
For example, two Modules that will have the same function DoSomething will always throw error 2517 when called through Application.Run: Access can't find the right function then.
You'll have to either rename (one of them) or create a wrapper function with a globally unique name for this goal.
So:
' Module1
Public Function GetName() As String
' Module2
Public Function GetName() As String
Should be changed to for example:
' Module1
Public Function Module1_GetName() As String
' Module2
Public Function Module2_GetName() As String
Or, add a wrapper for calls through Application.Run:
' Module1
Public Function GetName() As String
GetName = "Module1"
End Function
Public Function Module1_GetName() As String
Module1_GetName = GetName()
End Function
To test my gained knowledge, I made a wrapper function around Application.Run calls that, upon an error 2517, will replace the dot in the procedure name with an underscore and rerun, so the wrapper functions within the modules will be automatically called.
This makes the code more or less compatible with Excel Application.Run.
If you then create wrapper functions in your modules like my second example your 'good to go'.
Private Function RunApplicationFunction(Name As String, Value As Variant) As Variant
On Error GoTo TryUniqueName
Set RunApplicationFunction = Application.Run(Name, Value)
Exit Function
' In MS Access we catch Error 2517 and try to call the function with the application/module name prefixed to it
' So WebHelpers.ParseXML becomes WebHelpers_ParseXML
TryUniqueName:
If Err.Number = 2517 And Application.Name = "Microsoft Access" And InStr(Name, ".") > 0 Then
Set RunApplicationFunction = Application.Run(Replace(Name, ".", "_"), Value)
Else
Err.Raise Err.Number
End If
End Function

Related

VBA (Ms-Access) calling a memberfunction from a macro

In VBA (Access) I created a classmodule called "ZFormHelperClass" with a public function "FromStart"
Public Function FromStart(ByVal name As String, Optional ByVal lfd As Long = InvalidLfd, Optional ByVal centerd As Boolean = False) As ZFormContainerClass
'Do something
End Function
I use a public function in a module as a constructor for this class:
Public Function ZFormHelper() As ZFormHelperClass
Set ZFormHelper = New ZFormHelperClass
End Function
Then I tried to use a macro ("Test") to call this function in "Run Code" (or "Execute Code") like this:
=ZFormHelper.FromStart("Start", -1, True)
But it does not even let me save the Macro (Error: german version of: "Invalid value form the functionname argument"). I had to create a function do do this.
Function doStart()
Call ZFormHelper.FromStart("Start", -1, True)
End Function
So I wonder why this is the way it is and if there is a way to call a class function directly without having to create a dummy-function.
According to Albert Kallal you can't.
Review his comment here MS Access RunCode Macro cannot find my procedure

Pass type as parameter to newtonsoft json deserialize

I have this working line of code
Public Shared Function JSONArrayToObject(source As String, result As Type) As Object
Return JsonConvert.DeserializeObject(Of PAManufacturer())(source)
End Function
result is a type and I would like to replace PAManufacturer with GetType(result) or the equivalent.
I haven't been able to get around this problem. I could have a hundred of these deserialize methods but i'm sure I should be able to use the result variable somehow.
You can use generic functions (of which DeserializeObject is one) for this:
Public Shared Function JSONArrayToObject(Of T)(source As String) As T()
Return JsonConvert.DeserializeObject(Of T())(source)
End Function
Where you could call it as:
Dim arr as PAManufacturer() = JSONArrayToObject(Of PAManufacturer())(someSourceString)
Though if you get to that level, one wonders why you use a function at all, and not just have the code call JsonConvert.DeserializeObject directly.

difference between property and method of an instance

Im reading the tutorials here: http://www.adobe.com/devnet/actionscript/learning/oop-concepts/objects-and-classes.html and an on the second paragraph of the Dot Notation section. It uses the 'Sprite' class in ActionScript 3. The tutorial created an instance of the Sprite class and called it myFirstObject. It says..
"Then, using that reference variable and dot notation, values are assigned to the x and visible properties of the instance, and the methods startDrag and stopDrag are called."
I noticed that there are no () after a property. For example:
myFirstObject.x = 300;
compared to a method
myFirstObject.startDrag();
So, what's the difference between a property and method of an instance? I think it would help if I can see the Sprite class but I wasn't able to find it when I tried google'ing.
A property has a Get() and Set() method that allow you to use the same call to get or assign a value. When you assign the property a value, you are calling the Set method. When you retrieve a value, you are using the Get method. Properties automatically call the appropriate Get or Set method based on the operation.
To help you visualize the setup, here is a sample property (VB.Net):
Private _name As String
Public Property Name() As String
Get
Return _name
End Get
Private Set(ByVal value As String)
_name = value
End Set
End Property
To call it, you would use:
MyObject.Name = "Test" <- Sets the name to test
MsgBox("The name is: " & MyObject.Name) <- Gets the value of name
Although the example is in VB.Net, the theory is still the same.
A method, on the other hand, would be the equivalent of either the Get or Set routines. As a method, you have to call it and supply the parameters inside of the parenthesis. Even if it has none, you still need the (). When you want to update a variable, you have to pass values to the method instead of setting it equal to the value.
Here is a similar example:
Private _name As String
Public Function Name(Optional ByVal strName as String = "") as String
If strName <> "" then
_name = strName
End If
Return _name
End Function
Here is a similar example of how to use it:
MyObject.Name("Test") <- Sets the name to test
MsgBox("The name is: " & MyObject.Name()) <- Gets the value of name
Properties and methods are similar in that both are implemented as procedures that accept arguments. In general, properties store data for an object, and methods are actions an object can be asked to perform.

Using named arguments with Application.Run (or equiv)

I'm attempting to pass arguments to a VBA function via a string (user input from form)
The following code is throwing Runtime Error 2517 (Access cannot find the procedure '.') after it finishes running function a
Public Function a(Optional al As Boolean, Optional bl As Boolean)
Debug.Print al
End Function
Public Sub b()
Application.Run a, "bl:=false, al:=false"
End Sub
The correct syntax for the function would be Application.Run "a", "false", "false" but this approach cannot handle named arguments (which is a must for me)
I tried using the eval() function but it cannot resolve the named arguments.
Any suggestions on runtime error or any way in which I can pass a string with named arguments (as above) to a VBA function?
A is a function, functions return results
try
make it a procedure and just call it
forget using application.run

How to pass a function for parameter using VB6?

How can I do to pass a function by parameter, later to be called in VB6?
would be something like what I need, can be any of these options:
Private Sub Command1_Click()
Call callMethod(MyPrivateFunction)
Call callMethod(MyPublicFunction)
Call callMethod(MyPrivateSub)
Call callMethod(MyPublicSub)
End Sub
Private Function MyPrivateFunction()
MsgBox "MyPrivateFunction"
End Function
Public Function MyPublicFunction()
MsgBox "MyPublicFunction"
End Function
Private Sub MyPrivateSub()
MsgBox "MyPrivateSub"
End Sub
Public Sub MyPublicSub()
MsgBox "MyPublicSub"
End Sub
Public Function callMethod(ByRef func As Object)
Call func
End Function
IIRC there is an AddressOf function in VB6 to get function addresses, but you will likely have great difficulty actually using that function address from within VB6.
The SOP way to handle this is with CallByName() which allows you to, you know, call functions, etc. by their names.
finally, you can also take the high road, by using the standard OO solution to this: Instead of passing the function, write your own class that implements a special interface of your own design MyFunctionInterface. This interface has only one method FunctionToCall(..), which you can implement in different classes to call the different functions that you need. Then you pass an instance of one of these classes to your routine, that receives it as MyFunctonInterface and calls the FunctionToCall method on it. Of course that takes a whole lot of minor design changes...
You can't pass a function, but you can pass an object that behaves as a function (called a "functor" sometimes). I use this all the time. If you "functor" class Implements an interface, the call will be type safe. For example:
Abstract class (Interface) IAction.cls:
Option Explicit
Public Sub Create(ByVal vInitArgs As Variant)
End Sub
Public Function exe() As Variant
End Function
Functor that displays a url in the default browser:
Option Explicit
Implements IAction
Dim m_sUrl As String
Public Sub IAction_Create(ByVal vInitArgs As Variant)
m_sUrl = vInitArgs
End Sub
Public Function IAction_exe() As Variant
Call RunUrl(m_sUrl) 'this function is defined elsewhere
Exit Function
You can now create a bunch of these classes, save them in a collection, pass them to any function or method that expects an IAction, etc...
Your comment that you are using Microsoft.XMLHTTP OnReadyStateChange is interesting. The MSDN page for OnReadyStateChange says it "is designed for use in scripting environments and is not readily accessible in Microsoft® Visual Basic®".
The same page says that in Visual Basic 6 you should do this. Put this variable declaration at module level
Dim WithEvents xmldoc As DOMDocument30
and then you will be able to handle the event in the usual way like this.
Private Sub xmldoc_onreadystatechange()
' Deal with the event here '
End Sub
As an aside there's more discussion on calling functions by name in this question. But I think it's the wrong solution for your problem.