5

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.

rounderdude
  • 65
  • 1
  • 6
  • 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 Oct 03 '14 at 16:30
  • 1
    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 Oct 03 '14 at 18:39
  • @IgorTandetnik: I have to agree. It would make sense for an [in, out] parameter or [in] parameter. – Michael Petch Oct 03 '14 at 18:47
  • @IgorTandetnik - thanks for responding. Using [defaultvalue] in this way was simply for the ease of our own implementation, i.e. so we could check if the parameter was set to this value, and if so not assign a real error code to it. As far as actual client use cases of the library are concerned the main driver for adding this parameter was so that some clients could get extra error reporting, and others who did not need this information wouldn't be required to make any code changes. – rounderdude Oct 03 '14 at 19:25
  • 1
    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 Oct 03 '14 at 19:42
  • @MichaelPetch - thanks for responding. Afraid all our classes are derived from IUnknown. I'm not sure about the dual or oleautomotion attributes. Afraid I don't have access to the code at the moment... – rounderdude Oct 03 '14 at 19:44
  • Okay see my followup comment. It would be how I'd approach your problem. Create a new interface derived from the old. Then add a new method to that new Interface in the IDL, and then create a corresponding implementation that handles the new method with the extended parameters. By doing this old clients don't need to be touched at all. – Michael Petch Oct 03 '14 at 19:46
  • 1
    @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 Oct 03 '14 at 19:51
  • Not a problem. I guess I also assumed that your current coclasses for your *current* (not new) idl don't have a `aggregatable` attribute applied to them. If they did (or you were so lucky) there is another option that makes the process simpler. – Michael Petch Oct 03 '14 at 20:43

1 Answers1

3

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.

Community
  • 1
  • 1
Roman R.
  • 68,205
  • 6
  • 94
  • 158
  • 1
    Thanks Roman! The examples I'd seen on MSDN confused me on this topic. By applying a [defaultvalue] attribute I'd assumed that it was also [optional], but this didn't always seem to be stated explicitly - [like this example](http://msdn.microsoft.com/en-us/library/aa366793.aspx). The remarks on this link made me think that applying a default value of 0 or NULL was acceptable, and that if you hadn't explicitly marked it as [optional] then the restrictions on VARIANT or VARIANT* didn't apply? If this isn't the case though, it looks like I'll have to change tack. Thanks again for your time. – rounderdude Oct 06 '14 at 10:18
  • My understanding is that you want to add an optional parameter to request extended information about error/status. IDL optional parameter is in my understanding of little use here. You could either add another method with new parameter, or instead change returned [retval] value to an interface/object with properties holding actual result and other status properties. – Roman R. Oct 07 '14 at 09:39
  • 1
    Simplifying the takeaway: [defaultvalue] parameters must also be [optional]. [optional] parameters must be of type VARIANT or VARIANT*. – andrew.rockwell Oct 12 '18 at 22:13