COM interface (MIDL): size_is for an optional [out] parameter

679 views Asked by At

In short, in the following definition:

HRESULT GetStuff([in] long count, 
                 [out, size_is(count)] long * a,
                 [out, size_is(count)] long * b);

which fills a and b with count elements, is it legal for the caller to set a and / or b to null?

I want to allow the caller to query only one set of results, so the method may be called with

long only_a_s[10];
itf->GetStuff(10, a, 0);

Do I need to modify the MIDL declaration? I'm unsertain how the pointer/pointer_default attributes play into this.

Notes: There's overhead in acquiring them separately, but so is acquiring values the caller doesn't need, so separate getters or always having to get both is sub-par. I know it does work for inproc / in-apartment calls, but would the MIDL-generated proxy/stub deal with that correctly?

1

There are 1 answers

0
acelent On BEST ANSWER

You cannot pass a null pointer as an argument (a.k.a. top-level pointer), as they're [ref] by default.

But you may pass a null pointer, in or out, for non-top-level pointers.

The IDL method definition goes like this:

HRESULT GetStuffAB([in] long countA,
                   [in] long countB,
                   [out, size_is(, *pItemsA)] long **pA,
                   [out, size_is(, *pItemsB)] long **pB,
                   [out] long *pItemsA,
                   [out] long *pItemsB);

The C++ implementation:

HRESULT CMyClass::GetStuffAB(long countA,
                             long countB,
                             long **pA,
                             long **pB,
                             long *pItemsA,
                             long *pItemsB)
{
    // COM stubs will check this for you
    // However, you should (must?) manually check in same apartment calls
    if (!pA) return E_POINTER;
    if (!pB) return E_POINTER;
    if (!pItemsA) return E_POINTER;
    if (!pitemsB) return E_POINTER;

    *pA = nullptr;
    *pB = nullptr;
    *pItemsA = 0L;
    *pItemsB = 0L;

    if (countA < 0) return E_INVALIDARG;
    if (countB < 0) return E_INVALIDARG;

    // Get amount of As into *pItemsA if countA > 0
    // Get amount of Bs into *pItemsB if countB > 0

    if (*pItemsA < 0) return E_FAIL;
    if (*pItemsB < 0) return E_FAIL;

    if (*pItemsA > 0)
    {
        *pA = CoTaskMemAlloc(sizeof(long) * *pItemsA);
        if (!*pA) return E_OUTOFMEMORY;
    }

    if (*pItemsB > 0)
    {
        *pB = CoTaskMemAlloc(sizeof(long) * *pItemsB);
        if (!*pB)
        {
            if (*pA)
            {
                // You should not assume the memory will be freed by the caller
                // in such drastic situations, so free and clear *pA here before returning
                CoTaskMemFree(*pA);
                *pA = nullptr;
            }

            return E_OUTOFMEMORY;
        }
    }

    // Get As into *pA and Bs into *pB
    // Or just copy them if getting the amounts implied getting the items

    // You could just as well always return S_OK
    return (*pItemsA > 0 || *pItemsB > 0) ? S_OK : S_FALSE;
}

Not shown is whatever other code you must implement yourself to get the amount of As and the amount of Bs, and the As and Bs themselves.

If you must get the items to know the amounts, and you're not using RAII, you should free those resources manually before returning.

As an alternative to using CoTaskMemAlloc and CoTaskMemFree, you may want to use ATL's CComHeapPtr, which will automatically free memory RAII-style, thus simplifying your code. Just make sure you call Detach into *pA and *pB before returning successfully.