Implementing the Event Handlers for Command Events in C++

When you subscribe to command events, you must implement a class that derives from IRemoteCommandEvents. The following C++ example shows a class that implements the IRemoteCommandEvents interface. For an example that shows how to subscribe to the events, see Executing Commands.

extern HANDLE g_CommandDoneEvent; // Defined in the "Executing Commands" topic's C++ example

// Dispath IDs that identify the methods for the IRemoteCommandEvents interface.
#define DISPID_0NCOMMANDOUTPUT      0x60020000
#define DISPID_ONCOMMANDRAWOUTPUT   0x60020001
#define DISPID_ONCOMMANDTASKSTATE   0x60020002
#define DISPID_ONCOMMANDJOBSTATE    0x60020003


class CCommandEventHandler : public IRemoteCommandEvents
{
    LONG m_lRefCount;
    ITypeInfo* m_pTypeInfo;
    

public:
    // Constructor, Destructor
    CCommandEventHandler()
    {
        m_lRefCount = 1;
        m_pTypeInfo = NULL;
    };

    ~CCommandEventHandler() 
    {
        if (m_pTypeInfo)
        {
            m_pTypeInfo->Release();
            m_pTypeInfo = NULL;
        }
    };

    // IUnknown methods
    HRESULT __stdcall QueryInterface(REFIID riid, LPVOID *ppvObj);
    ULONG __stdcall AddRef();
    ULONG __stdcall Release();

    // IDispatch methods
    HRESULT __stdcall GetTypeInfoCount(UINT* pCount);
    HRESULT __stdcall GetTypeInfo(UINT uiTInfoRequest, LCID lcid, ITypeInfo** ppTInfo);
    HRESULT __stdcall GetIDsOfNames(REFIID riid, OLECHAR** ppNames, UINT cNames, LCID lcid, DISPID* pDispId);
    HRESULT __stdcall Invoke(DISPID dispidMember, REFIID riid, LCID lcid, WORD wFlags,
        DISPPARAMS* pDispParams, VARIANT* pvarResult, EXCEPINFO* pExcepInfo, UINT* puArgErr);

    // IRemoteCommandEvents methods
    void __stdcall OnCommandOutput(VARIANT sender, ICommandOutputEventArg* args);
    void __stdcall OnCommandRawOutput(VARIANT sender, ICommandRawOutputEventArg* args);
    void __stdcall OnCommandTaskState(VARIANT sender, ICommandTaskStateEventArg* args);
    void __stdcall OnCommandJobState(VARIANT sender, IJobStateEventArg* args);

private:

    HRESULT LoadTypeInfo(void);
    ISchedulerJob* GetCommandJob(VARIANT sender, long JobId);
    LPWSTR GetJobStateString(JobState state);
    LPWSTR GetTaskStateString(TaskState state);
};
// Strings associated with the job state values.
LPWSTR JobStateStrings[] = {
    L"Configuring", L"Submitted", L"Validating", 
    L"External validation", L"Queued", L"Running",
    L"Finishing", L"Finished", L"Failed",
    L"Canceled", L"Canceling" 
    };

// Strings associated with the task state values.
LPWSTR TaskStateStrings[] = {
    L"Configuring", L"Submitted", L"Validating", 
    L"Queued", L"Dispatching", L"Running",
    L"Finishing", L"Finished", L"Failed",
    L"Canceled", L"Canceling" 
    };

// IUnknown implementation.

HRESULT CCommandEventHandler::QueryInterface(REFIID riid, LPVOID* ppvObj) 
{
    if (riid == __uuidof(IUnknown) || 
        riid == __uuidof(IDispatch) ||
        riid == __uuidof(IRemoteCommandEvents)) 
    {
        *ppvObj = this;
    }
    else
    {
        *ppvObj = NULL;
        return E_NOINTERFACE;
    }

    AddRef();
    return NOERROR;
}

ULONG CCommandEventHandler::AddRef() 
{
    return InterlockedIncrement(&m_lRefCount);
}

ULONG CCommandEventHandler::Release() 
{
    ULONG  ulCount = InterlockedDecrement(&m_lRefCount);

    if(0 == ulCount) 
    {
        delete this;
    }

    return ulCount;
}

// IDispatch implementation. 

HRESULT __stdcall CCommandEventHandler::GetTypeInfoCount(UINT* pctInfo)
{
    HRESULT hr = S_OK;

    if (pctInfo == NULL) 
        return E_INVALIDARG;

    hr = LoadTypeInfo();

    if (SUCCEEDED(hr))
        *pctInfo = 1;

    return hr;
}

HRESULT __stdcall CCommandEventHandler::GetTypeInfo(UINT itInfo, LCID lcid, ITypeInfo** ppTInfo)
{
    HRESULT hr = S_OK;

    if (ppTInfo == NULL)
        return E_POINTER;

    if(itInfo != 0)
        return DISP_E_BADINDEX;

    *ppTInfo = NULL;

    hr = LoadTypeInfo();

    if (SUCCEEDED(hr))
    {
        m_pTypeInfo->AddRef();
        *ppTInfo = m_pTypeInfo;
    }

    return hr;
}

HRESULT __stdcall CCommandEventHandler::GetIDsOfNames(REFIID riid, OLECHAR** rgszNames, UINT cNames, LCID lcid, DISPID* rgDispId)
{
    HRESULT hr = S_OK;

    hr = LoadTypeInfo();

    if (FAILED(hr))
        return hr;

    return (m_pTypeInfo->GetIDsOfNames(rgszNames, cNames, rgDispId));
}

// Invoke the event handler methods directly.
HRESULT __stdcall CCommandEventHandler::Invoke(DISPID dispId, REFIID riid, LCID lcid, WORD wFlags,
    DISPPARAMS* pDispParams, VARIANT* pvarResult, EXCEPINFO* pExcepInfo, UINT* puArgErr)
{
    HRESULT hr = S_OK;
    ICommandOutputEventArg* pOutputArgs = NULL;
    ICommandRawOutputEventArg* pRawOutputArgs = NULL;
    ICommandTaskStateEventArg* pCommandStateArgs = NULL;
    IJobStateEventArg* pJobStateArgs = NULL;
    _variant_t vSender;  
    _variant_t vArgs;


    if (2 != pDispParams->cArgs) 
        return E_INVALIDARG;

    vSender = pDispParams->rgvarg[1];  // IScheduler
    vArgs = pDispParams->rgvarg[0];    // Arguments 

    switch (dispId)
    {
        case DISPID_0NCOMMANDOUTPUT :
            hr = vArgs.pdispVal->QueryInterface(IID_ICommandOutputEventArg, (void**)&pOutputArgs);
            if (SUCCEEDED(hr))
            {
                OnCommandOutput(vSender, pOutputArgs);
                pOutputArgs->Release();
                pOutputArgs = NULL;
            }
            break;

        case DISPID_ONCOMMANDRAWOUTPUT :
            hr = vArgs.pdispVal->QueryInterface(IID_ICommandRawOutputEventArg, (void**)&pRawOutputArgs);
            if (SUCCEEDED(hr))
            {
                OnCommandRawOutput(vSender, pRawOutputArgs);
                pRawOutputArgs->Release();
                pRawOutputArgs = NULL;
            }
            break;

        case DISPID_ONCOMMANDTASKSTATE :
            hr = vArgs.pdispVal->QueryInterface(IID_ICommandTaskStateEventArg, (void**)&pCommandStateArgs);
            if (SUCCEEDED(hr))
            {
                OnCommandTaskState(vSender, pCommandStateArgs);
                pCommandStateArgs->Release();
                pCommandStateArgs = NULL;
            }
            break;

        case DISPID_ONCOMMANDJOBSTATE :
            hr = vArgs.pdispVal->QueryInterface(IID_IJobStateEventArg, (void**)&pJobStateArgs);
            if (SUCCEEDED(hr))
            {
                OnCommandJobState(vSender, pJobStateArgs);
                pJobStateArgs->Release();
                pJobStateArgs = NULL;
            }
            break;

        default:
            hr = DISP_E_BADINDEX;  // Bad dispId
    }

    return hr;
}


HRESULT CCommandEventHandler::LoadTypeInfo(void)
{
    HRESULT hr;
    ITypeLib* pTypeLib = NULL;

    if (m_pTypeInfo)
        return S_OK;

    // Load type library.
    hr = LoadRegTypeLib(LIBID_Microsoft_Hpc_Scheduler, 2, 0, LOCALE_USER_DEFAULT, &pTypeLib);

    if (SUCCEEDED(hr))
    {
        // Get type information for interface of the object.
        hr = pTypeLib->GetTypeInfoOfGuid(DIID_IRemoteCommandEvents, &m_pTypeInfo);
        pTypeLib->Release();

        if (FAILED(hr))
            m_pTypeInfo = NULL;
    }

    return hr;
}

// IRemoteCommandEvents implementation.

// The RemoteCommand object will call all event handler methods - there is no way to 
// subscribe to a single event. If you do not want to handle the event, simply return;

void CCommandEventHandler::OnCommandOutput(VARIANT sender, ICommandOutputEventArg* pargs)
{
    HRESULT hr = S_OK;
    BSTR bstrNodeName = NULL;
    BSTR bstrOutput = NULL;


    hr = pargs->get_NodeName(&bstrNodeName);
    if (FAILED(hr))
    {
        // Handle error;
        goto cleanup;
    }
    
    wprintf(L"\nFormatted line output from %s.\n", bstrNodeName);

    hr = pargs->get_Message(&bstrOutput);
    if (FAILED(hr))
    {
        // Handle error;
        goto cleanup;
    }

    wprintf(L"<%s>\n", bstrOutput);

cleanup:

    if (bstrNodeName)
        SysFreeString(bstrNodeName);

    if (bstrOutput)
        SysFreeString(bstrOutput);
}

void CCommandEventHandler::OnCommandRawOutput(VARIANT sender, ICommandRawOutputEventArg* pargs)
{
    HRESULT hr = S_OK;
    BSTR bstrNodeName = NULL;
    SAFEARRAY* psaBuffer = NULL;
    PBYTE pData = NULL;
    PBYTE pOutput = NULL;
    long cb = 0;


    hr = pargs->get_NodeName(&bstrNodeName);
    if (FAILED(hr))
    {
        // Handle error;
        goto cleanup;
    }
    
    wprintf(L"\nRaw output from %s.\n", bstrNodeName);

    hr = pargs->get_Output(&psaBuffer);
    if (FAILED(hr))
    {
        // Handle error;
        goto cleanup;
    }

    if (psaBuffer)
    {
        hr = SafeArrayAccessData(psaBuffer, (void**)&pData);

        cb = psaBuffer->rgsabound->cElements * sizeof(byte);

        // The output string is in ASCII, not Unicode.
        pOutput = (PBYTE)malloc(cb + 1);
        if (pOutput)
        {
            memcpy_s(pOutput, cb + 1, pData, cb);
            *(LPSTR)(pOutput+cb) = '\0';
            wprintf(L"<%S>\n\n", (LPSTR)pOutput);
            free(pOutput);
            pOutput = NULL;
        }
        else
        {
            // Handle error;
            goto cleanup;
        }
    }

cleanup:

    if (bstrNodeName)
        SysFreeString(bstrNodeName);

    if (pData)
        SafeArrayUnaccessData(psaBuffer);

    if (psaBuffer)
        SafeArrayDestroy(psaBuffer);
}

void CCommandEventHandler::OnCommandTaskState(VARIANT sender, ICommandTaskStateEventArg* pargs)
{
    HRESULT hr = S_OK;
    ITaskStateEventArg* pStateArgs = NULL;
    TaskState State;
    long lExitCode = 0;
    BSTR bstrNodeName = NULL;
    BSTR bstrMessage = NULL;
    VARIANT_BOOL fIsProxyTask = VARIANT_FALSE;


    hr = pargs->get_IsProxy(&fIsProxyTask);
    if (FAILED(hr))
    {
        // Handle error;
        goto cleanup;
    }

    // Report state changes for the command, not the proxy task.
    if (VARIANT_FALSE == fIsProxyTask)
    {
        // The state information is contained in the ITaskStateEventArg so you need to query
        // pargs for the interface.
        hr = pargs->QueryInterface(IID_ITaskStateEventArg, reinterpret_cast<void **> (&pStateArgs));

        hr = pargs->get_NodeName(&bstrNodeName);
        if (FAILED(hr))
        {
            // Handle error;
            goto cleanup;
        }

        hr = pStateArgs->get_NewState(&State);
        if (FAILED(hr))
        {
            // Handle error;
            goto cleanup;
        }

        wprintf(L"\nOnCommandTaskState Node: %s State: %s\n", bstrNodeName, GetTaskStateString(State));

        if (TaskState_Failed == State)
        {
            hr = pargs->get_ExitCode(&lExitCode);
            if (FAILED(hr))
            {
                // Handle error;
                goto cleanup;
            }

            hr = pargs->get_ErrorMessage(&bstrMessage);
            if (FAILED(hr))
            {
                // Handle error;
                goto cleanup;
            }

            wprintf(L"Exit code: %ld\nMessage: %s\n", lExitCode, bstrMessage);
        }
    }

cleanup:

    if (pStateArgs)
        pStateArgs->Release();

    if (bstrNodeName)
        SysFreeString(bstrNodeName);

    if (bstrMessage)
        SysFreeString(bstrMessage);
}


void CCommandEventHandler::OnCommandJobState(VARIANT sender, IJobStateEventArg* pargs)
{
    HRESULT hr = S_OK;
    ISchedulerJob* pJob = NULL;
    long JobId = 0;
    JobState State;


    hr = pargs->get_NewState(&State);
    if (FAILED(hr))
    {
        // Handle error;
        goto cleanup;
    }

    wprintf(L"\nOnCommandJobState State: %s\n", GetJobStateString(State));

    // If you need to do something with the job, remove the comments.
    //hr = pargs->get_JobId(&JobId);
    //if (FAILED(hr))
    //{
    //    // Handle error;
    //    goto cleanup;
    //}

    //pJob = GetCommandJob(sender, JobId);
    //if (NULL == pJob)
    //{
    //    // Handle error;
    //    goto cleanup;
    //}

    // TODO: Do something with the job.

    if (JobState_Finished == State ||
        JobState_Failed == State ||
        JobState_Canceled == State)
    {
        SetEvent(g_CommandDoneEvent);
    }

cleanup:

    if (pJob)
        pJob->Release();
}


// Called from CCommandEventHandler::OnCommandJobState.
ISchedulerJob* CCommandEventHandler::GetCommandJob(VARIANT sender, long JobId)
{
    HRESULT hr = S_OK;
    IScheduler* pScheduler = NULL;
    ISchedulerJob* pJob = NULL;


    hr = sender.pdispVal->QueryInterface(__uuidof(IScheduler), reinterpret_cast<void **> (&pScheduler));

    hr = pScheduler->OpenJob(JobId, &pJob);
    if (FAILED(hr))
    {
        // Handle error;
        goto cleanup;
    }

cleanup:

    if (pScheduler)
        pScheduler->Release();

    return pJob;
}



// Returns the string associated with the state value.
LPWSTR CCommandEventHandler::GetJobStateString(JobState state)
{
    DWORD flag = state;
    DWORD bitPosition = 0;

    for (bitPosition = 0; flag = flag >> 1; bitPosition++)
        ;

    return JobStateStrings[bitPosition];
}


// Returns the string associated with the state value.
LPWSTR CCommandEventHandler::GetTaskStateString(TaskState state)
{
    DWORD flag = state;
    DWORD bitPosition = 0;

    for (bitPosition = 0; flag = flag >> 1; bitPosition++)
        ;

    return TaskStateStrings[bitPosition];
}