Passing optional arguments "by Value" in VBA - vba

In VBA, if I want to pass an optional argument ByVal (see mainly Var2, the other variables are only there to make sure it works within a "complex" parameter set but should work as well):
Sub Test(Var1 As String, Optional ByVal Var2 As String, Optional Var3 As String)
'Var1 should be passed ByRef (NOT optional) (ByRef through "default behaviour")
'Var2 should be passed ByVal (optional) (ByVal through specific notation)
'Var3 should be passed ByRef (optional) (ByRef through "default behaviour")
End Sub
I only found a reference for VB does it work for VBA in the same way?
Note: Of course I tested it and the test works, and others use it as well, I just want to make sure I don't run into problem with other instances.

Don't know what a "complex parameter set" could be, but it will work as officially documented:
Function Statement Syntax

Related

ByRef vs. ByVal for the ReadProcessMemory function

I'm using the windows function ReadProcessMemory in VBA/VB6 and I don't understand why when I change the passing mechanism of lpBuffer to ByVal the function still modifies the value of the original object passed through this argument. In the documentation, this argument is specified as an output that should be passed by reference. Shouldn't changing the passing mechanism to by value prevent the original instance from being modified? Why does it not?
Declare Function ReadProcessMemory Lib "kernel32" (ByVal hProcess As Long, ByVal lpBaseAddress As Any _
,byVal lpBuffer As Any, ByVal nSize As Long, lpNumberOfBytesWritten As Long) As Long
First, ByVal .. As Any for an _Out_ argument is not a good idea (I'm not even sure if that's possible); if you use ByVal for such you want it to be As Long (see further below for the "why").
So, for APIs having one or more _Out_ arguments meant to represent a buffer/variable/memory location, there are two ways (for each concerned argument anyway) to write the declaration, depending on what you want to pass:
ByRef lpBuffer As Any, or simply lpBuffer As Any: You use this in the declaration for an _Out_ argument if, when calling the API, you intend to pass the actual variable where data should be copied to. For example, you could use a Byte array like so:
Private Declare Function ReadProcessMemory Lib "kernel32" (ByVal hProcess As Long, _
ByVal lpBaseAddress As Long, lpBuffer As Any, ByVal nSize As Long, _
lpNumberOfBytesWritten As Long) As Long
'[..]
Dim bytBuffer(255) As Byte, lWrittenBytes As Long, lReturn As Long
lReturn = ReadProcessMemory(hTargetProcess, &H400000&, bytBuffer(0), 256, lWrittenBytes)
Note that the callee (here, ReadProcessMemory()) will fill whatever you provide as lpBuffer with data, regardless of the actual size of the variable passed. That's why the size of the buffer must be provided through nSize, because otherwise the callee has no way to know the size of the buffer being provided. Also note that we're passing the first item of the (byte) array, as this is where the callee should start writing data to.
With the same declaration, you could even pass a long if you wanted to (if, for example, what you want to retrieve is an address or a DWord value of some sort), but then nSize must be 4 bytes (at most).
Also note that the last argument, lpNumberOfBytesWritten, is also an _Out_ argument and passed ByRef but you don't need to provide the callee with its size; that's because there's an agreement between the caller & callee that whatever variable is passed, exactly 4 bytes will always be written to it.
ByVal lpBuffer As Long: You use this in a declaration for an _Out_ argument if, when calling the API, you intend to pass a memory location in the form of a 32-bit value (i.e. a pointer); the value of the Long being passed will not change, what will be overwritten is the memory location being referenced by the value of that Long. Reusing the same example, but with a slightly different declaration, we get:
Private Declare Function ReadProcessMemory Lib "kernel32" (ByVal hProcess As Long, _
ByVal lpBaseAddress As Long, ByVal lpBuffer As Long, ByVal nSize As Long, _
lpNumberOfBytesWritten As Long) As Long
'[..]
Dim bytBuffer(255) As Byte, lPointer As Long, lWrittenBytes As Long, lReturn As Long
lPointer = VarPtr(bytBuffer(0))
lReturn = ReadProcessMemory(hTargetProcess, &H400000&, lPointer, 256, lWrittenBytes)
' If we want to make sure the value of lPointer didn't change:
Debug.Assert (lPointer = VarPtr(bytBuffer(0)))
See, this is practically the same thing again, the only difference being we're providing a pointer (memory address) to bytBuffer instead of passing bytBuffer directly. We could even provide the value returned by VarPtr() directly instead of using a Long (here, lPointer):
lReturn = ReadProcessMemory(hTargetProcess, &H400000&, VarPtr(bytBuffer(0)), 256, _
lWrittenBytes)
Warning #1: For _Out_ arguments, if you declare them ByVal they should always be As Long. This is because the calling convention expects the value to be composed of exactly 4 bytes (32-bit value/DWORD). If you were to pass the value through an Integer type, for example, you'd get unexpected behaviour because what will be used as the value for the memory location are the 2 bytes of that Integer plus the next 2 bytes that come right after the content of that Integer variable in memory, which could be anything. And if this happens to be a memory location the callee will write to, you'll probably crash.
Warning #2: You DO NOT want to use VarPtrArray() (which would need to be explicitly declared anyway), as the value returned will be the address of the SAFEARRAY structure of the array (number of items, size of items, etc.), not the pointer to the array's data (which is the same address as the first item in the array).
In essence, for Win32 APIs (i.e. stdcall) arguments are always passed as 32-bit values, always. The meaning of those 32-bit values will depend on what the specific API expects, so its declaration must reflect this. So:
whenever an argument is declared ByRef, what will be used is the memory location of whatever variable is being passed;
whenever an argument is declared ByVal .. As Long, what will be used is the (32-bit) value of whatever variable is being passed (the value must not necessarily be a memory location, e.g. the hProcess argument of ReadProcessMemory()).
Finally, even if you declare an _Out_ argument ByRef (or if, for example, that's the way an API is declared and you cannot change it because if comes from a typelib) you can always pass a pointer instead of the actual variable by adding ByVal before it when making the call. Going back to the first declaration of ReadProcessMemory() (when lpBuffer is declared ByRef), we would do the following:
Private Declare Function ReadProcessMemory Lib "kernel32" (ByVal hProcess As Long, _
ByVal lpBaseAddress As Long, lpBuffer As Any, ByVal nSize As Long, _
lpNumberOfBytesWritten As Long) As Long
'[..]
Dim bytBuffer(255) As Byte, lWrittenBytes As Long, lReturn As Long
lReturn = ReadProcessMemory(hTargetProcess, &H400000&, ByVal VarPtr(bytBuffer(0)), 256, _
lWrittenBytes)
Adding ByVal tells the compiler that what should be passed on stack is not the address of VarPtr() but instead the value returned by VarPtr(bytBuffer(0)). But if the argument was declared ByVal .. As Long then you don't have a choice, you can only pass a pointer (i.e. address of a memory location).
NOTA: this answer assumed throughout the architecture being discussed was IA32 or an emulation of it
#polisha989 I believe the "lp" in lpBuffer indicates the type as a long pointer. I suspect that since the object you are passing is a pointer, it won't make any difference if it's passed by value or reference. Even if you pass the argument by value, the system is just making a copy of a pointer - so both objects will be pointing to the same value in memory. So the reason that you see the updated value whether you pass the pointer by ref or by val, is because that is what a pointer does; it points to a value in memory. No matter how many pointers you have, if they are all pointing to the same place in memory, they will all show the same thing.
One word of advice if you are getting into API calls is you really can't spend too much time wading through the MSDN. The better you can understand how a function works, the easier it will become to implement it. Making sure you are passing the right object types to the function will help you to ensure the results you get are expected.
CBRF23 is correct. When an API function has a string argument, the value that you pass is a long pointer to a buffer. That pointer value is a long integer, and for the life of the pointer its value is immutable. Therefore, whether you have two copies of the pointer value or not is irrelevant, since the value never changes.
The value changes whether you pass byref or byval because what gets changed is the memory in the buffer that the lpbuffer is pointing to. The pointer is just saying where to do the work, it isn't the entity that the work gets done on.
The pointer is (roughly) analogous to your email address, and the memory it points to is analogous to your inbox, if that helps to visualize the concept.
As Any declarations never get passed by value.
When you remove type restrictions, Visual Basic assumes the argument is passed by reference. Include ByVal in the actual call to the procedure to pass arguments by value.
Note the italics I added for the exception to "never."

VB.Net: Differences in DllImport and Declare statements

I'm trying to use a function from a Fortran library. The function is supposed to return a string. If I use this
Friend Declare Ansi Sub LaA_LabelGet Lib "V3Mat.dll" (ByRef hLaA As IntPtr, ByVal iRegion As Integer, ByVal cLabel As String, ByVal intLabelLen As Integer)
statement it works fine, if I use
<DllImport("V3Mat.dll", CallingConvention:=CallingConvention.StdCall, CharSet:=CharSet.Ansi)>
Friend Sub LaA_LabelGet(ByRef hLaA As IntPtr, ByVal iRegion As Integer, ByVal cLabel As String, ByVal intLabelLen As Integer)
End Sub
the cLabel is always blank.
Can anyone please point out the difference between the two. I'd rather use the second definition as it allows me to set the library to use by using a constant.
The Declare statement is legacy syntax that was adopted in VB.NET to work the way it did back in the VB6 days. The one thing it does that matters in your case is that it allows native code to write into the string. Like it was possible back in VB6. Which is entirely invalid in .NET, strings are immutable.
The exact equivalent in the <DllImport> declaration would be <MarshalAs(UnmanagedType.VBByRefStr)> ByRef cLabel As String
The better solution is to declare the argument as ByVal cLabel As StringBuilder and pass a properly initialized StringBuilder object with a sufficient Capacity. And use its ToString() method afterwards to obtain the returned string.

How are parameters passed to VB functions by default

Let's say I have the following function:
Function myFunction(j As Integer) As Double
myFunction = 3.87 * j
Exit Function
End Function
Is j passed as value ByVal or by reference ByRef?
Or does it depends of the data type? What if I have a complex object passed as the value?
Thanks in advance!
Parameters are passed ByVal unless explicitly specified. For details, see Passing Arguments by Value and by Reference, which states:
The default in Visual Basic is to pass arguments by value. You can make your code easier to read by using the ByVal keyword. It is good programming practice to include either the ByVal or ByRef keyword with every declared parameter.
As for:
What if I have a complex object passed as the value?
This is fine, provided the "complex object" is a class (Reference type), you're not going to be doing a lot of copying. This is because the reference to the object instance is passed by value (ByVal), which means you're only copying a single reference, even if the class is very large.
If, however, the complex object is a structure (value type), you will be causing the object to be copied when the method is called. This, btw, is why some frameworks like XNA provide alternative versions of many methods (like Matrix.Multiply) that have an option to pass ByRef - this avoids the expensive copies of the Matrix structures.
j in this case is passed ByVal. A parameter is always passed ByVal unless ByRef is explicitly stated. From section 9.2.5 of the VB.NET 10 Specification:
A parameter that does not specify ByRef or ByVal defaults to ByVal.

Using ByVal in vb.net methods, what's the common practice?

In vb.net the methods have their parameters using ByVal by default, it's better practice / common practice to make it explicit?
For example:
With ByVal:
Private Sub MySub(ByVal Q As String)
{
' ...
}
End Sub
Without ByVal:
Private Sub MySub(Q As String)
{
' ...
}
End Sub
According to Microsoft:
It is good programming practice to include either the ByVal or ByRef keyword with every declared parameter.
And if you use Visual Studio, it defaults to inserting ByVal if you don't explicitly specify it.
Starting with VS 2010 SP1, ByVal is no longer automatically inserted by the IDE.
I personally think it's better not to insert ByVal manually, because:
it's the default passing mechanism anyway, if neither ByVal nor ByRef are explicitly specified.
omitting ByVal from method signature makes ByRef stand out.
it adds 'noise' to the code. VB.Net is already very verbose, no need to clutter the code with unnecessary ByVals.
It is common practice that a method arguments can be specified at ByValue or ByReference. In VB.NET, the default argument type is ByVal. In many programming language, method arguments are by-value by default. If argument is not qualified with ByVal or ByRef then the argument type will be ByVal.

Alias in Function Declaration overloaded?

I have some VB6 code that I am converting to VB.net and came across this section
Declare Function TmSendByLen Lib "tmctl.dll" Alias "TmSendByLength"(ByVal id As Integer, ByRef msg As Any, ByVal blen As Integer) As Integer
'snip'
Function TmSendByLength(ByVal id As Integer, ByVal msg As String, ByVal blen As Integer) As Integer
TmSendByLength = TmSendByLen(id, msg, blen)
End Function
I have not come across the Alias term before but I can guess what it does. What I am unsure of is the reasoning behind overloading the alias. If that is what is happening.
I need to create overloads for the TmSendByLen function as the 'As Any' is not supported in VB.net so I am not sure if I should just remove the alias or if I should leave it in place.
The Alias doesn't specify that the function is overloaded exactly, but that the name specified is really named something else in the called dll.
Since your example is a bit confusing (because of the repeated names), I'll use a slightly modified version to explain:
Declare Function TmSendByLen Lib "tmctl.dll" Alias "TmSendByLength" (ByVal id As Integer, ByRef msg As Any, ByVal blen As Integer) As Integer)
Function InternalVersion(ByVal id As Integer, ByVal msg As String, ByVal blen As Integer) As Integer
InternalVersion = TmSendByLen(id, msg, blen)
End Function
So in this modified version, the TmSendByLength name is the one that the referenced function's entrypoint is really called in tmctl.dll. TmSendByLen is what we're referring to it as in our code, and InternalVersion is the name of the wrapper function.
I would imagine that this is so that InternalVersion can be called across modules/classes, while the TmSendByLen version was intended to be private.
To answer the second part of your question, Alias is still available in VB.NET, although As Any isn't. (You can find information about it here.) Whether you want to remove the Alias or not is completely up to you, but either way, I suspect you're going to need to use As IntPtr (or SafeHandle) instead of As Any.
The "Alias" keyword in VB6 is probably doing what you think it does, however, it's the function name in quotes after the keyword "alias" that is the actual function name in the DLL (i.e. TmSendByLength). The function name after the "Declare Function" part (i.e. TmSendByLen) is effectively the alias that the VB6 code will use.
As you correctly point out, VB6 will not allow the "As Any" parameter type, so from the original VB6 code you posted, the developer has declared a VB6 function which incidentally has the exact same name as the "real" function in the DLL, and altered the parameters to this function to accept only a string type for the "msg" parameter.
The VB6 code as-is isn't actually overloading any function, but rather it's wrapping the DLL function with a VB6 specific one that constrains the "msg" parameter type.
In VB.NET, since you can't specify "as any", I believe you can replace this with "as object", although that may not be very helpful since other VB.NET calling code could pass virtually anything to this parameter. What you're more likely going to want to do is to create real overloaded functions in VB.NET where the "msg" parameter differs by the type that you want to accept. This way, you can allow multiple different types, but still maintain some constraints on which types can be passed to the function.
Here's a couple of links that may well help:
VB6 "As Any" in VB.Net
PInvoke