What is the vbError returned by VarType in VB6? - error-handling

I have been searching for this all over the web, but everything is either talking about the ErrObject class, or the constant vbError returned by the VarType function. I want to know what that type actually is, e.g. an integer is something like 4, a string is something like "hello world", etc.
For a little background, here is a link to the official MSDN page about VarType, which shows all the constants it returns and what they represent. What is Error value?

If you are familiar with COM in C/C++, you should know that from that perspective VarType() is a simple function that essentially extracts the value of the vt member from the Variant passed in as a parameter. The possible values of the vt member are documented in many places, such as here.
If you check inside of the COM system headers (e.g. WTypes.h distributed as part of in the Windows SDK), you will see that the VbError value of 10 indeed maps to the C/C++ enum value of VT_ERROR.
enum VARENUM
{ VT_EMPTY = 0,
...
VT_ERROR = 10,
...
} ;
The MSDN link above describes the meaning of a vt that equals VT_ERROR as follows:
An SCODE was specified. The type of the error is specified in scode.
Generally, operations on error values should raise an exception or propagate the error to the return value, as appropriate.
So, here's basically what it means:
A Variant obviously supports storing many types of values, and among them is the obscure possibility of storing an "Error Code". More correctly, these codes are formally called scode's, because they can indicate many types of "success" as well as "failures". Most people refer to these codes as HRESULTs.
These codes are the same kind of "Error Codes" that you get from Err.Number in VB6. So, you can tell a Variant to distinguish an "Error Code" from just a plain number.
In reality, few programs or COM components - if any - will ever put error codes in Variants. Almost everybody just issues COM Exceptions to communicate errors (this mechanism is exposed in VB6 via the Err object). Even those components that return error codes outside of the COM Exception mechanism, would likely do so in typed variables (e.g. Long's).
Therefore, the reason this possible return value exists is for completeness. You will almost never see it in real life.
(Edit: Remove bit about not being able to create an "Error" variant. #Joe proved me wrong on that. You can use CVErr() to create one)

In VB6 and VBA, error values are created by calling the CVErr function.
This function returns a Variant whose VarType is vbError, and for which the IsError function returns True.
A typical use case for this is an Excel UDF that returns a Variant: if you return an error value, it will display as #VALUE!.

Related

Passing an rvalue as a ByRef parameter to VB6?

Background: I've got a set of VB6 DLLs that share a common "interface". Whichever version is installed locally has members of this interface invoked via COM interop (from VB.Net code, which I suspect might matter). I noticed today that one of the invocations passes [what I understand to be] an rvalue (hereinafter "an rvalue") to a VB6 function that does not have that particular parameter defined as ByVal.
Example Code:
VB6:
Public Function VB6Function(input As String) As String
' Do interesting things with input
End Function
VB.Net:
' get an instance of the VB6 class and pass our trimmed localString to it
result = vb6Instance.VB6Function(localString.Trim())
' Do interesting things with localString
I have not yet noticed an instance of the VB6 code changing the value of input, but I also haven't done an exhaustive search of the different DLL implementations (there are several hundred).
What would happen if VB6Function did change the value of input when input is "an rvalue"? For that matter, why doesn't this method invocation simply error out when "an rvalue" is passed?
What would happen if VB6Function did change the value of input when input is "an rvalue"?
Nothing. Or rather, nothing interesting.
When the called function changes the value of its argument, it makes no difference for the insides of that function whether the argument was provided byval or byref. All that matters is that there is a variable of certain type, thus, it can be acted upon.
For that matter, why doesn't this method invocation simply error out when "an rvalue" is passed?
Why would it error out? The passed argument as correct type (string), that is all that matters.
There is no notion of an rvalue in VB.
When you pass what you would call an rvalue to a method accepting something by reference, the compiler automatically passes the reference to a temporary location where the rvalue actually resides. The method gets its value byref, the caller does not care about pointers.
localString.Trim() allocates and returns a string. It has an address and can be passed around. Your code does not explicitly capture that address, but the compiler has no problem passing it to VB6Function byref. If VB6Function changes the value, it changes what that temporary location points to, which has no observable difference because it's going to be destroyed after the call either way.
As for why some people may have preferred receiving strings byref in VBA, it's specifically to avoid copying the entire string each time when calling the function. In VB.NET it's not a problem because strings there are immutable and therefore can be passed byval without copying, but in VBA that is not the case, so a byval string needs to be cloned for the purpose of the call. People avoided that by specifying byref, even though that technically gave them the power to mess with the passed variable.

Is there a difference in reliability between these sum functions?

So the other day I was getting some strange sum values and I was completely stumped. I gave up on using a sum function in VBA and just added the values the long way (through loops), but then I read somewhere that using the sum functions in VBA isn't always reliable for developers? (I can't find the post anymore, but I'm still looking for it).
Is there any truth to this? I know many people have different ways to get the sum from a range of cells - without being too opinionated, which one of these will return the most exact result?
Sub testsums()
Dim metric1 As Integer, metric2 As Integer, metric3 As Integer
metric1 = Application.Sum(Range(("A1"), ("Z1")))
metric2 = Application.WorksheetFunction.Sum(Range(("A1"), ("Z1")))
metric3 = WorksheetFunction.Sum(Range(("A1"), ("Z1")))
End Sub
I'm working to reproduce my error - basically when looping through many rows (15,000+) and getting sums, some were returning zeroes where they shouldn't have been.
Application.Sum(Range(("A1"), ("Z1")))
That's a late-bound call against Excel.Application, resolved at run-time; as with any late-bound call (e.g. against Object or Variant), you get no IntelliSense, no auto-completion, and no compile-time validation, be it for typos in the name or for order or number of parameters. If the call is invalid or the function otherwise results in an error, this will return an Error value that you can validate with the IsError VBA function (of course if there's a typo in the function name what you'll get is a run-time error #438 "Object doesn't support this property or method").
What makes this syntax work is the fact that the Excel.Application COM interface has a flag that makes it extensible - I'm not sure if it's extended with straight-up WorksheetFunction interface or if it's merely doubling-up the members, but anyway that's what's going on: you're calling members that don't exist on the Application interface at compile-time.
Application.WorksheetFunction.Sum(Range(("A1"), ("Z1")))
That's an early-bound call against Excel.WorksheetFunction, resolved at compile-time; you get IntelliSense, auto-completion, and compile-time validation. A typo will fail to compile, as will missing required parameters. If the call is invalid or the function otherwise results in an error, this will raise a VBA run-time error that you can handle with an On Error statement using standard, VBA-idiomatic error handling.
WorksheetFunction.Sum(Range(("A1"), ("Z1")))
That's exactly the same as Application.WorksheetFunction.Sum, except it's not fully-qualified. If your project has a WorksheetFunction class, or if there's a WorksheetFunction object variable in scope, it will take precedence over a fully-qualified Application.WorksheetFunction call as far as resolution goes (which may result in compile errors). Otherwise, identical.
Which one is "more reliable" depends on exactly what you deem "reliable". Personally I consider compile-time resolution completely worth it, so the "most reliable" would be the fully-qualified early-bound version.

Literal description of object type

VB2010 with ArcObjects. Im having a problem recalling how to do this. I can test for an object type and then do something if it is the proper type. All else I want to display a message with the type that is not supported. So I have:
Dim pRasRenderer As IRasterRenderer
pRasRenderer = pRasterLayer.Renderer
If TypeOf pRasRenderer Is IRasterClassifyColorRampRenderer Then
'process the layer
Else
Debug.Print "Type not supported: " & pRasRenderer.ToString
End If
If not supported the print statement should read
"Type not supported: IRasterStretchColorRampRenderer"
or any of the other types that I do not process. But All I get back is
"Type not supported: System.__ComObject"
I tried GetType() and TypeName. Just cant remember if I can do this or not.
With COM, the type isn't IRasterStretchColorRampRenderer, it's some COM type that (most likely) implements quite a few distinctly different interfaces. Given that you're working from .NET, you'll get __ComObject any time you try to access the "type" of the object directly.
The actual "type" of the object which implements IRasterStretchColorRampRenderer, for example, is more than likely a native implementation of a COM CoClass. As such, you'll never get a "managed" type name from .NET code for this.
Unfortunately, this means that you're best solution is likely to report that the type passed in is not supported without trying to report the actual type passed into the method. This would likely be something like:
Debug.Print "Type not supported: Current layer is not using a Classify Color Ramp Renderer"

What's the Matlab equivalent of NULL, when it's calling COM/ActiveX methods?

I maintain a program which can be automated via COM. Generally customers use VBS to do their scripting, but we have a couple of customers who use Matlab's ActiveX support and are having trouble calling COM object methods with a NULL parameter.
They've asked how they do this in Matlab - and I've been scouring Mathworks' COM/ActiveX documentation for a day or so now and can't figure it out.
Their example code might look something like this:
function do_something()
OurAppInstance = actxserver('Foo.Application');
OurAppInstance.Method('Hello', NULL)
end
where NULL is where in another language, we'd write NULL or nil or Nothing, or, of course, pass in an object. The problem is this is optional (and these are implemented as optional parameters in most, but not all, cases) - these methods expect to get NULL quite often.
They tell me they've tried [] (which from my reading seemed the most likely) as well as '', Nothing, 'Nothing', None, Null, and 0. I have no idea how many of those are even valid Matlab keywords - certainly none work in this case.
Can anyone help? What's Matlab's syntax for a null pointer / object for use as a COM method parameter?
Update: Thanks for all the replies so far! Unfortunately, none of the answers seem to work, not even libpointer. The error is the same in all cases:
Error: Type mismatch, argument 2
This parameter in the COM type library is described in RIDL as:
HRESULT _stdcall OurMethod([in] BSTR strParamOne, [in, optional] OurCoClass* oParamTwo, [out, retval] VARIANT_BOOL* bResult);
The coclass in question implements a single interface descending from IDispatch.
I'm answering my own question here, after talking to Matlab tech support: There is no equivalent of Nothing, and Matlab does not support this.
In detail: Matlab does support optional arguments, but does not support passing in variant NULL pointers (actually, to follow exactly how VB's Nothing works, a VT_EMPTY variant, I think) whether as an optional argument or not. There is documentation about some null / pointerish types, a lot of which is mentioned in my question or in various answers, but these don't seem to be useable with their COM support.
I was given a workaround by Matlab support using a COM DLL they created and Excel to create a dummy nothing object that could be passed around in scripts. I haven't managed to get this workaround / hack working, and even if I had unfortunately I probably could not redistribute it. However, if you encounter the same problem this description might give you a starting point at least!
Edit
It is possible this Old New Thing blog post may be related. (I no longer work with access to the problematic source code, or access to Matlab, to refresh my memory or to test.)
Briefly, for IUnknown (or derived) parameters, you need a [unique] attribute for them to legally be NULL. The above declaration required Matlab create or pass in a VT_EMPTY variant, which it couldn't do. Perhaps adding [unique] may have prompted the Matlab engine to pass in a NULL pointer (or variant containing a NULL pointer), instead - assuming it was able to do that, which is guesswork.
This is all speculation since this code and the intricacies of it are several years behind me at this point. However, I hope it helps any future reader.
From the mathworks documentation, you can use the libpointer function:
p = libpointer;
and then p will be a NULL pointer. See that page for more details.
See also: more information about libpointer.
Peter's answer should work, but something you might want to try is NaN, which is what Matlab ususally uses as a NULL value.
In addition to using [] and libpointer (as suggested by Peter), you can also try {}.
The correct answer for something in VB that is expecting a Nothing argument, is to somehow get a COM/ActiveX Variant which has a variant type of VT_EMPTY. (see MSDN docs which reference marshaling behavior for Visual Basic Nothing)
MATLAB may do this with the empty array ([]), but I'm not sure.... so it may not be possible purely in MATLAB. Although someone could easily write a tiny COM library whose purpose is to create a Variant with VT_EMPTY.
But if the argument has the [optional] atttribute, and you want to leave that optional argument blank, you should not do this. See the COM/ActiveX docs on Variants which say under VT_EMPTY:
VT_EMPTY: No value was specified. If an optional argument to an Automation method is left blank, do not pass a VARIANT of type VT_EMPTY. Instead, pass a VARIANT of type VT_ERROR with a value of DISP_E_PARAMNOTFOUND.
Matlab should (but probably does not) provide methods to create these objects (a "nothing" and an "optional blank") so you can interface correctly with COM objects.

In VB6, how do I call a COM object requiring a pointer to an object?

I'm having trouble with a .NET Assembly that is com visible, and calling certain methods from VB6.
What I have found is that if the parameters are well defined types, (e.g. string), calls work fine. If they are higher level objects, it raises a runtime error '438' suggesting that the property or method is not present. I suspect that this is a question of having the correct signature on the call, but I can't see how to do this correctly.
I believe that I've done everything correct on the .NET side (ComVisible, public interfaces, etc. and even have it down to a simple enough case).
Looking at the output from the typelib viewer, I have the following:
dispinterface ISimple {
properties:
methods:
[id(0x60020000)]
void Add([in] ISimpleMember* member);
[id(0x60020001)]
ISimpleMember* Create();
};
OK. So I have 2 methods in my ISimple interface. One takes an ISimpleMember (Add), whilst the other, returns an ISimpleMember.
The corresponding code in VB looks like this:
Dim item As ISimpleMember
Dim simple As simple
Set item = New SimpleMember
item.S1 = "Hello"
item.S2 = "World"
Set simple = New simple
simple.Add (item) <---- This raised the run time error 438
Set item = simple.Create <---- This works fine, returning me an ISimpleMember
I've tried a couple of things:
1. Dim item as SimpleMember (makes no difference)
2. simple.Add(ObjPtr(item)) - Syntax error
3. simple.Add(ByRef item) - Syntax error
Basically, The run time error is the same as if I had
simple.AMethodThatIHaventWritten()
Also, If I browse References in the VB6 Environment, The Add method is well defined:
Sub Add(member As SimpleMember)
I've found the answer I believe. It was very simple:
When calling a SubRoutine, I shouldn't put the name in braces. the call should have been:
simple.add member
rather than
simple.add(member)
If I change it to a function (i.e. return a value rather than void) the braces are necessary
This seems to work
(Probably) The top 3 VB6 coding mistakes made by devs who now mainly code in C#, Javascript etc. Are:-
Placing ; at the end of lines. Its a syntax error very easily spotted and picked up the compiler.
Not placing Then on the other side of an If condition expression. Again its a syntax error.
Calling a method without retrieving a value and yet using ( ) to enclose the parameter list. With multiple parameters this is a syntax error and easily found. With only one parameter the use of ( ) is interpreted as an expression. Its the result of the ( ) expression which is passed as parameter. This causes problems when ByRef is expected by the callee.