5
votes

I'm trying to update an existing COM API to include a new optional output parameter, and have run into an issue with the enforced ordering of parameter types in the IDL file and the associated C++ header file.

Previously I had an IDL file like this (names changed to protect the innocent):

HRESULT CreateSomething(
    [in] BSTR base_uri,
    [in] ISomethingDescription* something_description,
    [out, retval] BSTR* something_uri
);

and the associated C++ header looked like:

HRESULT __stdcall CreateSomething(
    /* [in] */ BSTR study_uri,
    /* [in] */ ISomethingDescription* something_description,
    /* [out, retval] */ BSTR* something_uri
);

This needed to be updated to add an optional output parameter that could provide some clients with extra error reporting information, following an existing pattern against the rest of the in-house SDK.

To do this I was planning to update the IDL file to look like this:

HRESULT CreateSomething(
    [in] BSTR base_uri,
    [in] ISomethingDescription* something_description,
    [out, defaultvalue(0)] ErrorCode* error_code,
    [out, retval] BSTR* something_uri
);

Where ErrorCode is an enum defined in a separate IDL file. This follows the guidance I have seen online about how parameters with defaultvalue and retval attributes should be ordered. However, when I then try to upate the C++ header file, I run into the issue that the default parameter is not at the end of the parameter list, i.e.

HRESULT __stdcall CreateSomething(
    /* [in] */ BSTR study_uri,
    /* [in] */ ISomethingDescription* something_description,
    /* [out, defaultvalue(0)] */ ErrorCode* error_code = 0,   // this is clearly wrong
    /* [out, retval] */ BSTR* something_uri
);

The documentation I have seen on MSDN seems to indicate that you can use parameters with defaultvalue and retval attributes within the same function definition, and I have seen some examples of IDL files that contain such definitions, but I cannot work out how it is possible to write the equivalent C++ definition.

Some clients (and our own test code) use the MIDL generated header files directly, so if I omit the default values from the original C++ header file, the generated function in the MIDL generated file does not contain a default value entry, i.e. it looks like this:

virtual HRESULT STDMETHODCALLTYPE CreateSomething( 
            /* [in] */ BSTR base_uri,
            /* [in] */ ISomethingDescription *something_description,
            /* [defaultvalue][out] */ ErrorCode *error_code,
            /* [retval][out] */ BSTR *something_uri) = 0;

Similar functions in our SDK include the default values in the IDL file and the C++ header - not to say that that whole approach isn't questionable.

Any help/advice on this would be greatly appreciated.

1
MIDL doesn't try especially hard to make sense of your idl. It is a step up from a plain macro-style preprocessor but not much. So when you write nonsensical idl then you do need to expect a nonsensical outcome. I can't guess what you really meant to say either. - Hans Passant
I don't see how [defaultvalue] makes any sense for an [out] parameter. Under what circumstances, exactly, do you expect this default value to be used? - Igor Tandetnik
@IgorTandetnik: I have to agree. It would make sense for an [in, out] parameter or [in] parameter. - Michael Petch
I would consider this possibility though. CreateSomething is a method on an interface. Let us call it MyOldInterface since I don't know its real name. You could derives a new Interface from it. That new interface (let us call it MyNewInterface could expose a new method CreateSomethingEx that takes the new extra parameter(s). Old code would use the old MyOldInterface interface as they currently are, and new code that knows about MyNewInterface can instantiate that and call the method CreateSomethingEx with extra parameters. - Michael Petch
@michaelpetch - thanks for your further suggestion. I was hoping to avoid this level of duplication, as this problem exists across a large section of our code base. However it looks like I might have to end up doing something along those lines... - rounderdude

1 Answers

3
votes

MSDN is pretty clear about the parameters:

The MIDL compiler accepts the following parameter ordering (from left-to-right):

  1. Required parameters (parameters that do not have the [defaultvalue] or [optional] attributes),
  2. optional parameters with or without the [defaultvalue] attribute,
  3. parameters with the [optional] attribute and without the [defaultvalue] attribute,
  4. [lcid] parameter, if any,
  5. [retval] parameter

Note that there is no mention for "not optional default value" parameter. This is because default values apply to optional only - makes sense because a parameter which is not optional always have the explicit value, with no defaults applicable.

So your default value parameter has to be optional, and then new constraints are applicable: "The [optional] attribute is valid only if the parameter is of type VARIANT or VARIANT *.", which means that your optional enum parameter is basically invalid. It might so happen that MIDL compiler accepts this and put the respective flag on the type library, but eventually this is not how it is expected to work in first place: default values only apply to variants.

Then when you have C++ header generated from IDL, you will find out that optional parameters is simply a markup. Development environments such as scripting languages might detect it in order to update syntax for this COM method respectively, but on C++ side the parameter is always present and is basically mandatory. The only thing you basically have there is special convention to see that caller did not have the value for optional parameter without default value, in which case you have VT_ERROR, DISP_E_PARAMNOTFOUND in the respective variant parameter.