I have some functions in a COM interface that return different success values via the HRESULT, but using the space that is defined as "successful" (i.e. SUCCEEDED(hr) is non-zero).
For example
HRESULT MyMessageBox( BSTR title /*[in]*/, BSTR text /*[in]*/, long buttons /*[in]*/ );
a function that displays a dialog similar to a MessageBox; it returns an indicator of which button the user used to dismiss the dialog. (I defined an enum for this with values within the space of HRESULTs which is reserved for user-defined codes).
This is fine in a C++ client; however when VB is the client, or Java wrappers such as JACOB, it appears to intercept the HRESULT and there is no way that the client can tell which successful code occurred.
Is it actually a terrible design to have the HRESULT indicate anything other than 0 or exceptions ; should I instead make new functions that have an [out] parameter to get which button is used?
Most language runtimes will map an HRESULT to an exception, makes writing COM code a lot easier. And yes, they'll ignore positive values. They might have an escape for that, in .NET the [PreserveSig] attribute suppresses the exception mapping and exposes the HRESULT return value as an int.
But that's painful and unnecessary. They will also map an argument that you decorate as [out, retval] to the function return value. Which is what you are looking for here:
HRESULT MyMessageBox([in] BSTR title, [in] BSTR text, [in] long buttons,
[out,retval] long* result);
And now the client programmer can write something like this:
int result = yadayada.MyMessageBox("title", "text", 0);
Giving you the opportunity to use the HRESULT only for "function failed" return values, S_OK otherwise. You can further improve it by using enum types to name the legal button and return values.
Related
It works, but is not listed here:
https://msdn.microsoft.com/en-us/library/office/gg278535.aspx
... > Office shared > Office VBA language reference > Visual Basic conceptual topics
Returning Strings from Functions
and in the VBA editor:
(View) - Object Browser - VBA - Strings
This function is discussed in many forums, so people use it.
The answer to your question depends on what you mean by "... exist[s] in Access". If we open an Immediate window in Access' VBA development environment and run
?Replace$("I like tofu!", "tofu", "bacon")
we get
I like bacon!
so clearly the Replace$ function does "exist" in Access itself. However, if we try to use Replace$ in a query against an Access database from an external application (e.g., VBScript, .NET, ...) we'll get
Undefined function 'Replace$' in expression.
Adding to the confusion is the fact that the older "Jet" ODBC/OLEDB drivers did not support the Replace function (without the dollar sign) but the newer "ACE" ODBC/OLEDB drivers do. (Neither flavour supports Replace$.) So a query like
SELECT Replace([Name], 'Gordon ', 'Gord ') AS newName FROM ...
will work if we use the ODBC driver
Driver={Microsoft Access Driver (*.mdb, .accdb)}
but not if we use
Driver={Microsoft Access Driver (*.mdb)}
The functions in the VBA.Strings module are "special" in how they are handled internally by VBA. For most of them, there are actually 2 versions in the type library - a version that returns a String (ends with $), and a version that returns a Variant. Internally these are declared as a pair of functions - for example, Right (from the vbe7.dll TypeLib):
[entry(618), helpcontext(0x000f6ea5)]
BSTR _stdcall _B_str_Right(
[in] BSTR String,
[in] long Length);
[entry(619), helpcontext(0x000f656e)]
VARIANT _stdcall _B_var_Right(
[in] VARIANT* String,
[in] long Length);
The compiler apparently treats the $ similarly to a "type hint" internally, because (using the example above), there actually isn't a function Left$ defined in the TypeLib. In fact, there isn't a function declared as VBA.Strings.Right either. These live in a special restricted interface named _HiddenInterface:
[
odl,
uuid(1E196B20-1F3C-1069-996B-00DD010EF676)
]
interface _HiddenInterface {
...
[restricted, helpcontext(0x000f6d7c)]
void _stdcall Right();
...
};
Note that Right$ doesn't appear in the in the _HiddenInterface, nor do any of the other string returning functions. The VBA compiler uses the "function type hint" to forward the function call to either _B_str_Right or _B_var_Right.
By now you're probably wonder what this has to do with your question. The answer is that Replace actually doesn't have two different internal representations. It always returns a string, doesn't exist on the _HiddenInterface, and lives directly in the VBA.Strings module:
[entry(712), helpstring("Find and replace a substring within a string"), helpcontext(0x000f6522)]
BSTR _stdcall Replace(
[in] BSTR Expression,
[in] BSTR Find,
[in] BSTR Replace,
[in, optional, defaultvalue(1)] long Start,
[in, optional, defaultvalue(-1)] long Count,
[in, optional, defaultvalue(0),
custom(270D72B0-FFB8-11CF-A4BD-00A0C90F26EE, 1)
] VbCompareMethod Compare);
Basically, there is no Replace$ function at all. VBA is treating the $ as a type hint for the return value (which is always a String anyway). As far as the ODBC and OLE drivers are concerned, I would imagine (TBH I really haven't looked into it much) that they are restricted to the names that are exposed by the TypeLib and are not interpreted by the VBA runtime as forwards to different functions. They simply don't exist if you're doing IDispatch lookups on vbe7.dll.
Example method in IDL:
HRESULT _stdcall a_method( [in] long value, [out] BSTR *comment );
My function logic is that for some values, no comment is necessary. Should I throw an exception if this function is called with comment == NULL by a client? Or is it OK to be permissive and allow this case?
(I'm developing my object in C++).
My rationale for trying to be strict with parameter checking is that I'm concerned about memory leaks, and about having the client make calls that are correct according to the COM spec but my object not accepting the call.
The semantics of [out] parameters are very explicit about this.
A method that gets an [out] parameter should never - ever - look at the parameter's value until it puts something on it. It is uninitialized memory. Garbage. In fact, if your method is called via a marshalled call (inter-apartment or inter-process), garbage is exactly what you get: whatever your caller might have put there when it called your method, was discarded and ignored by the proxy/stub; you never get it.
If the client/caller puts something on the parameter before making a call to your method, it is definitely a memory leak (given that it's an allocated object like a BSTR, of course), but it's the caller's fault. It is never the responsibility of a called method to deal with it. The called method can't handle the leak even if it wanted to.
If you want to handle whatever values might be passed in by the caller, you need to use an [in, out] parameter instead of [out].
One last warning: Automation clients (VBA, VBScript, etc.) don't support [out] parameters. Automation will silently handle any [out] parameter as if it was [in, out], which puts you in an awkward position: any value placed in the parameter by the client application will be leaked, and your method can't do anything about it.
If you plan on your object being used by an automation client, don't use [out] parameters. Use [in, out] instead, and make sure to check if the caller put a value on the parameter before the call. The proxy/stub will always marshal values both ways for an [in, out] parameter. If the caller placed a value on the parameter before the call, your method is responsible for releasing that value before writing to the parameter.
Edit: Expanding on the pointer itself being NULL:
You could think about checking for NULL and return E_INVALIDARG if it's NULL, but I wouldn't recommend it.
It is illegal to pass NULL as the pointer value for an [out] parameter. Even if your code handles a NULL value, if the call is marshalled, the marshaller will hit an Access Violation. The marshaller has to access the pointed value on the way back (to store the marshalled output on it) and it will do so without checking for null.
In your specific scenario (the call semantic being that there is nothing to return in a given case), the proper process is for the caller to always provide a pointer to storage, and for the called method to set the value to NULL. Something like this:
// Caller
BSTR comment;
hr = obj->a_method( 42, &comment);
// Callee
HRESULT a_method( value, BSTR *comment )
{
if (...)
{
//... I've decided we don't need to return a comment
*comment = NULL;
}
...
}
If you really want to have the pure null pointer semantic you mentioned, you can; but you have to mark the parameter with the [ptr] attribute. As far as I know, that doesn't work very well with Automation clients, and you have to use a custom marshaller. If you don't anticipate ever using an Automation client, this is clearly an option.
I'm not quite sure how [in] and [out] interact with the pass-by-value and pass-by-reference concepts. The MSDN documentation clearly states that [in] means data flows from caller to callee, and [out] is required for data to flow from callee to caller.
However someone suggested to me that I use [in] parameters for objects where the caller can retrieve the results.
Example method definition in IDL:
HRESULT _stdcall a_method( [in] long *arg1, [in] BSTR arg2, [in] IAnObject *arg3 );
In my server's implementation of this method (using C++), I can write:
*arg1 = 20;
arg2[0] = L'X'; // after checking length of string is not 0
arg3->set_value(50);
In the client code, using C++:
long val1 = 10;
BSTR val2 = SysAllocString(L"hello");
IAnObject *val3 = AnObject_Factory::Create();
ptr->a_method(&val1, val2, val3);
When I tried this out (using my object via in-process server), all three changes from the server were propagated to the client, i.e. val1 == 20, val2 was "Xello", and val3->get_value() got 50.
My question is: Is this guaranteed behaviour, i.e. if I am using out-of-process server, or DCOM to another machine, will it see the same changes in val1, val2, and val3 ?
I previously thought that [in] indicated to the underlying RPC that the argument only had to be marshaled in one direction; it didn't have to try and send changes back to the caller. But now I am not so sure.
I am intending that my object is Automation-compatible (i.e. usable from VB6, Java etc. - no custom marshaling required), and that it ought to be able to be used via DCOM instead of in-process, without any changes required in the client code.
You shouldn't change the contents of [in] arguments, so the following code is wrong:
*arg1 = 20;
arg2[0] = L'X'; // after checking length of string is not 0
You're seeing the changes being reflected because you're making calls in the same apartment, where marshaling isn't happening. The proper way to return values is with [out] or [in, out] arguments.
However, you may access its contents and call its methods (for interface pointers), so the following code is right:
arg3->set_value(50);
EDIT: Further answering your questions.
Marshaling can occur both ways, and the [in] and [out] attributes tell the way(s).
For automation, I recommend you don't return more than the typical [out, retval] argument, to support scripting languages. If you must return multiple values, return an IDispatch with properties. Take a look at this blog post as a good starting point if you're taking scriptable automation seriously.
To expand upon #Paulo-madeira's answer, I can guarantee that if a proxy is involved, that
*arg1 = 20;
arg2[0] = L'X'; // after checking length of string is not 0
will at best be ignored, and at worst will corrupt the heap.
I'm trying to define a function in a type library that takes a variable number of parameters and can be callable from VBA using ParamArray.
The entry in the type library is below.
[
helpstring("Get value from a Lookup table by an exact key."),
entry("UtilDll_LookupExact"),
vararg
]
HRESULT __stdcall LookupExact(
[out] LPVARIANT Result,
[in] LPVARIANTARG Table,
[in] LONG VarIndex,
[in] SAFEARRAY(VARIANT) Key,
[out, retval] VARIANT_BOOL *Found
);
I can see this VBA's object browser as:
Function LookupExact(Result, Table, VarIndex As Long, ParamArray Key() As Variant) As Boolean
So, at least the object browser seems to understand Key as ParmArray () Variant.
But when I call this function I get a compile error saying, "Compile Error: Function or Interface Marked as Restricted, or the Function Uses an Automation Type Not Supported in Visual Basic"
The closest question I found is below, but this case seems specific to managed code while I'm dealing with native code.
ParamArray Not Working With COM
How can I make a function in native code that has the ParamArray parameter and can be accessed via VBA?
I found the answer here:
http://computer-programming-forum.com/71-visual-basic-vb/a064ce72acaeb9d8.htm
Apparently, the Key parameter in IDL should have been:
[in] SAFEARRAY(VARIANT)* Key,
And C++ function's arguments type should be
SAFEARRAY** Key
I'm confused about COM string assignments. Which of the following string assignment is correct. Why?
CComBSTR str;
.
.
Obj->str = L"" //Option1
OR should it be
Obj->str = CComBSTR(L"") //Option2
What is the reason
A real BSTR is:
temporarily allocated from the COM heap (via SysAllocString() and family)
a data structure in which the string data is preceded by its length, stored in a 32-bit value.
passed as a pointer to the fifth byte of that data structure, where the string data resides.
See the documentation:
MSDN: BSTR
Most functions which accept a BSTR will not crash when passed a BSTR created the simple assignment. This leads to confusion as people observe what seems to be working code from which they infer that a BSTR can be initialized just like any WCHAR *. That inference is incorrect.
Only real BSTRs can be passed to OLE Automation interfaces.
By using the CComBSTR() constructor, which calls SysAllocString(), your code will create a real BSTR. The CComBSTR() destructor will take care of returning the allocated storage to the system via SysFreeString().
If you pass the CComBSTR() to an API which takes ownership, be sure to call the .Detach() method to ensure the BSTR is not freed. BSTRs are not reference counted (unlike COM objects, which are), and therefore an attempt to free a BSTR more than once will crash.
If you use str = CComBSTR(L"") you use the constructor:
CComBSTR( LPCSTR pSrc );
If you use str = L"" you use the assignment operator:
CComBSTR& operator =(LPCSTR pSrc);
They both would initialize the CComBSTR object correctly.
Personally, I'd prefer option 1, because that doesn't require constructing a new CComBSTR object. (Whether their code does so behind the scenes is a different story, of course.)
Option 1 is preferred because it only does one allocation for the string where as option 2 does 2 (not withstanding the creation of a new temporary object for no particular reason). Unlike the bstr_t type in VC++ the ATL one does not do referenced counted strings so it will copy the entire string across.