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
Related
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
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.
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
A simple question: How do I set the prototype for a function that has not been implemented yet?
I just want to do this, cause I'm referring to a function that does not exist(yet).
In C, we would do something like this:
int foo(int bar);
int myint = foo(1);
int foo(int bar)
{
return bar;
}
How do I do this in Lua (with corona)?
You can't. Amber's comment is correct.
Lua doesn't have a concept of type signatures or function prototypes.
The type of foo is that of the object it contains, which is dynamic, changing at runtime. It could be function in one instant, and string or integer or something else in the next.
Conceptually Lua doesn't have a compilation step like C. When you say "run this code" it starts starts executing instructions at the top and works it's way down. In practice, Lua first compiles your code into bytecode before executing it, but the compiler won't balk at something like this:
greet()
function greet()
print('Hello.')
end
Because the value contained in greet is determined at runtime. It's only when you actually try to call (i.e. invoke like a function) the value in greet, at runtime, that Lua will discover that it doesn't contain a callable value (a function or a table/userdata with a metatable containing a __call member) and you'll get a runtime error: "attempt to call global 'greet' (a nil value)". Where "nil value" is whatever value greet contained at the time the call was attempted. In our case, it was nil.
So you will have to make sure that that the code that creates a function and assigns it to foo is called before you attempt to call foo.
It might help if you recognize that this:
local myint = foo(1)
function foo(bar)
return bar
end
Is syntax sugar for this:
local myint = foo(1)
foo = function(bar)
return bar
end
foo is being assigned a function value. That has to happen before you attempt to call that function.
The most common solution to this problem is to treat the file's function as the "compilation time", that is: declare all of your constant data and functions when the file is executed, ready to be used during "execution time". Then, call a main function to begin the "execution time".
For example:
function main()
greet()
end
function greet()
print('Hello.')
end
main()
As greet has been declared in _G, main can access it.
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.