fhw72
fhw72

Reputation: 1146

How to properly convert and use native COM types in a ATL-based server?

I have to write a COM server DLL (using ATL) that is called by the plugin interface of an old (closed-source) VB6 application and would like to avoid possible leaks (of course)! The interface description is given as-is and cannot be changed unfortunately:

The class method that will be called is declared on the VB side like this:

Public Function Process(data As Object,
                        Params As Variant,
                        Results() As Single) As Integer

The interface in IDL is declared like this:

[id(1)] HRESULT Process([in,out] IDispatch**       pDataIDisp,
                        [in,out] VARIANT*          pParamsVar,
                        [in,out] SAFEARRAY(FLOAT)* pResSA,
                        [out,retval] SHORT*        pRetVal);

and finally the code that is called look like this:

STDMETHODIMP Analyzer::Process(IDispatch** pDataIDisp,
                               VARIANT*    pParamsVar,
                               SAFEARRAY** pResSA,
                               SHORT*      pRetVal)
{
    try
    {
        // Prepare for access
        CComPtr<IDispatch> dataComPtr = *pDataIDisp;

        // VARTYPE from caller is VT_VARIANT | VT_ARRAY | VT_BYREF;
        CComVariant prms = *pParamsVar;               // OR use .Attach ?
        CComSafeArray<VARIANT> prmsArr = prms.parray; // OR use prms.Attach ?

        // SafeArray is FADF_HAVEVARTYPE
        CComSafeArray<FLOAT> res = *pResSA; // OR use res.Attach(pResSA*) ?

        {
            // Use ATL types wrapped from above
        }

        // Cleanup ????
        .
        .
        .
    }
    catch (...) {}
    return S_OK;
}

What I'd like to know is:

  1. Is my method of accepting (and converting) the parameters to ATL types correct usage or is there another (better?) way?

  2. Do I have to call AddRef() and/or Release() on the IDispatch* myself or is it sufficient to assign to a CComPtr to get this all done?

  3. What are the implications of the 2nd parameter given as VT_BYREF?

    Header file says:

    *  VT_VARIANT          [V][T][P][S]  VARIANT *
    *  VT_ARRAY            [V]           SAFEARRAY*
    *  VT_BYREF            [V]           void* for local use
    

    Which is not clear to me.... #-o

  4. Is it sufficient to use SafeArray(Un)AccessData on the SAFEARRAY stored in the VARIANT (2nd parameter)?

  5. Any additional things to consider to get this thing work properly (robust)?

Thanks for taking your time and possibly helping me!

ps: I already got this working (more or less) I only want to avoid problems (LEAKS!) which I cannot debug as the calling application is closed source and not under my control...

Upvotes: 0

Views: 1235

Answers (1)

Roman Ryltsov
Roman Ryltsov

Reputation: 69632

You are making your like really compilcated using safe arrays and in/out parameters. Especially in conjunction with ancient VB6 you are going to interface to.

There are a few simple rules to make it clear, easier and reliable:

  • [in], [out], [out, retval] parameters; in your code snippet you used [in, out] without any shown intent to actually change the value to take advantage of out specifier: when you have no special reason to, it just makes C++ side complex and additional level of referencing increases chances for a mistake
  • in parameters don't need attaching to C++ classes and releasing
  • when you return from C++ method leaving a value for out parameter you typically Detach() interface pointers, strings, variants and safe arrays from holder C++ class - transferring ownership from internal class to obligation of caller to release the resources
  • instead of using safe arrays directly on IDL and trying to match signalture on VB side, I would suggest using variants: you can always put an array into variant, including array of variants, and VB6 will be able to roll this back
  • out and not retval parameters will be ByRef on VB6 side, in parameters will by ByVal

You would get it like this:

[id(1)] HRESULT Process([in] IDispatch* pDataIDisp,
                        [in] VARIANT pParamsVar,
                        [out, retval] VARIANT* pvResult);

On C++ server side you would only read from in arguments, no releasing required. And you will initialize output variant by building it entirely in C++ using ATL CComVariant and friends and then detaching it at the very last stage of your processing.

Public Function Process(data As Object,
                        Params As Variant) As Object
' ...
Dim Data, Params As Object
' ...
Dim Result As Object
Result = Server.Process(Data, Params)
' NOTE: Result is OK to be an array

Upvotes: 1

Related Questions