/* $Id: tstCAPIGlue.c 125585 2018-10-08 13:51:49Z michael $ */
/** @file tstCAPIGlue.c
 * Demonstrator program to illustrate use of C bindings of Main API.
 *
 * It has sample code showing how to retrieve all available error information,
 * and how to handle active (event delivery through callbacks) or passive
 * (event delivery through a polling mechanism) event listeners.
 */

/*
 * Copyright (C) 2009-2017 Oracle Corporation
 *
 * This file is part of VirtualBox Open Source Edition (OSE), as
 * available from http://www.virtualbox.org. This file is free software;
 * you can redistribute it and/or modify it under the terms of the GNU
 * General Public License (GPL) as published by the Free Software
 * Foundation, in version 2 as it comes in the "COPYING" file of the
 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
 */


/** @todo
 * Our appologies for the 256+ missing return code checks in this sample file.
 *
 * We strongly recomment users of the VBoxCAPI to check all return codes!
 */


/*********************************************************************************************************************************
*   Header Files                                                                                                                 *
*********************************************************************************************************************************/
#include "VBoxCAPIGlue.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#ifndef WIN32
# include <signal.h>
# include <unistd.h>
# include <sys/poll.h>
#endif
#ifdef ___iprt_cdefs_h
# error "not supposed to involve any IPRT or VBox headers here."
#endif

/**
 * Select between active event listener (defined) and passive event listener
 * (undefined). The active event listener case needs much more code, and
 * additionally requires a lot more platform dependent code.
 */
#undef USE_ACTIVE_EVENT_LISTENER


/*********************************************************************************************************************************
*   Global Variables                                                                                                             *
*********************************************************************************************************************************/
/** Set by Ctrl+C handler. */
static volatile int g_fStop = 0;

#ifdef USE_ACTIVE_EVENT_LISTENER
# ifdef WIN32
/** The COM type information for IEventListener, for implementing IDispatch. */
static ITypeInfo *g_pTInfoIEventListener = NULL;
# endif /* WIN32 */
#endif /* USE_ACTIVE_EVENT_LISTENER */

static const char *GetStateName(MachineState_T machineState)
{
    switch (machineState)
    {
        case MachineState_Null:                return "<null>";
        case MachineState_PoweredOff:          return "PoweredOff";
        case MachineState_Saved:               return "Saved";
        case MachineState_Teleported:          return "Teleported";
        case MachineState_Aborted:             return "Aborted";
        case MachineState_Running:             return "Running";
        case MachineState_Paused:              return "Paused";
        case MachineState_Stuck:               return "Stuck";
        case MachineState_Teleporting:         return "Teleporting";
        case MachineState_LiveSnapshotting:    return "LiveSnapshotting";
        case MachineState_Starting:            return "Starting";
        case MachineState_Stopping:            return "Stopping";
        case MachineState_Saving:              return "Saving";
        case MachineState_Restoring:           return "Restoring";
        case MachineState_TeleportingPausedVM: return "TeleportingPausedVM";
        case MachineState_TeleportingIn:       return "TeleportingIn";
        case MachineState_FaultTolerantSyncing: return "FaultTolerantSyncing";
        case MachineState_DeletingSnapshotOnline: return "DeletingSnapshotOnline";
        case MachineState_DeletingSnapshotPaused: return "DeletingSnapshotPaused";
        case MachineState_RestoringSnapshot:   return "RestoringSnapshot";
        case MachineState_DeletingSnapshot:    return "DeletingSnapshot";
        case MachineState_SettingUp:           return "SettingUp";
        default:                               return "no idea";
    }
}

/**
 * Ctrl+C handler, terminate event listener.
 *
 * Remember that most function calls are not allowed in this context (including
 * printf!), so make sure that this does as little as possible.
 *
 * @param  iInfo    Platform dependent detail info (ignored).
 */
#ifdef WIN32
static BOOL VBOX_WINAPI ctrlCHandler(DWORD iInfo)
{
    (void)iInfo;
    g_fStop = 1;
    return TRUE;
}
#else
static void ctrlCHandler(int iInfo)
{
    (void)iInfo;
    g_fStop = 1;
}
#endif

/**
 * Sample event processing function, dumping some event information.
 * Shared between active and passive event demo, to highlight that this part
 * is identical between the two.
 */
static HRESULT EventListenerDemoProcessEvent(IEvent *event)
{
    VBoxEventType_T evType;
    HRESULT rc;

    if (!event)
    {
        printf("event null\n");
        return S_OK;
    }

    evType = VBoxEventType_Invalid;
    rc = IEvent_get_Type(event, &evType);
    if (FAILED(rc))
    {
        printf("cannot get event type, rc=%#x\n", rc);
        return S_OK;
    }

    switch (evType)
    {
        case VBoxEventType_OnMousePointerShapeChanged:
            printf("OnMousePointerShapeChanged\n");
            break;

        case VBoxEventType_OnMouseCapabilityChanged:
            printf("OnMouseCapabilityChanged\n");
            break;

        case VBoxEventType_OnKeyboardLedsChanged:
            printf("OnMouseCapabilityChanged\n");
            break;

        case VBoxEventType_OnStateChanged:
        {
            IStateChangedEvent *ev = NULL;
            enum MachineState state;
            rc = IEvent_QueryInterface(event, &IID_IStateChangedEvent, (void **)&ev);
            if (FAILED(rc))
            {
                printf("cannot get StateChangedEvent interface, rc=%#x\n", rc);
                return S_OK;
            }
            if (!ev)
            {
                printf("StateChangedEvent reference null\n");
                return S_OK;
            }
            rc = IStateChangedEvent_get_State(ev, &state);
            if (FAILED(rc))
                printf("warning: cannot get state, rc=%#x\n", rc);
            IStateChangedEvent_Release(ev);
            printf("OnStateChanged: %s\n", GetStateName(state));

            fflush(stdout);
            if (   state == MachineState_PoweredOff
                || state == MachineState_Saved
                || state == MachineState_Teleported
                || state == MachineState_Aborted
               )
                g_fStop = 1;
            break;
        }

        case VBoxEventType_OnAdditionsStateChanged:
            printf("OnAdditionsStateChanged\n");
            break;

        case VBoxEventType_OnNetworkAdapterChanged:
            printf("OnNetworkAdapterChanged\n");
            break;

        case VBoxEventType_OnSerialPortChanged:
            printf("OnSerialPortChanged\n");
            break;

        case VBoxEventType_OnParallelPortChanged:
            printf("OnParallelPortChanged\n");
            break;

        case VBoxEventType_OnStorageControllerChanged:
            printf("OnStorageControllerChanged\n");
            break;

        case VBoxEventType_OnMediumChanged:
            printf("OnMediumChanged\n");
            break;

        case VBoxEventType_OnVRDEServerChanged:
            printf("OnVRDEServerChanged\n");
            break;

        case VBoxEventType_OnUSBControllerChanged:
            printf("OnUSBControllerChanged\n");
            break;

        case VBoxEventType_OnUSBDeviceStateChanged:
            printf("OnUSBDeviceStateChanged\n");
            break;

        case VBoxEventType_OnSharedFolderChanged:
            printf("OnSharedFolderChanged\n");
            break;

        case VBoxEventType_OnRuntimeError:
            printf("OnRuntimeError\n");
            break;

        case VBoxEventType_OnCanShowWindow:
            printf("OnCanShowWindow\n");
            break;
        case VBoxEventType_OnShowWindow:
            printf("OnShowWindow\n");
            break;

        default:
            printf("unknown event: %d\n", evType);
    }

    return S_OK;
}

#ifdef USE_ACTIVE_EVENT_LISTENER

struct IEventListenerDemo;
typedef struct IEventListenerDemo IEventListenerDemo;

typedef struct IEventListenerDemoVtbl
{
    HRESULT (*QueryInterface)(IEventListenerDemo *pThis, REFIID riid, void **ppvObject);
    ULONG (*AddRef)(IEventListenerDemo *pThis);
    ULONG (*Release)(IEventListenerDemo *pThis);
#ifdef WIN32
    HRESULT (*GetTypeInfoCount)(IEventListenerDemo *pThis, UINT *pctinfo);
    HRESULT (*GetTypeInfo)(IEventListenerDemo *pThis, UINT iTInfo, LCID lcid, ITypeInfo **ppTInfo);
    HRESULT (*GetIDsOfNames)(IEventListenerDemo *pThis, REFIID riid, LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId);
    HRESULT (*Invoke)(IEventListenerDemo *pThis, DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pDispParams, VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr);
#endif
    HRESULT (*HandleEvent)(IEventListenerDemo *pThis, IEvent *aEvent);
} IEventListenerDemoVtbl;

typedef struct IEventListenerDemo
{
    struct IEventListenerDemoVtbl *lpVtbl;

    int cRef;

#ifdef WIN32
    /* Active event delivery needs a free threaded marshaler, as the default
     * proxy marshaling cannot deal correctly with this case. */
    IUnknown *pUnkMarshaler;
#endif
} IEventListenerDemo;

/* Defines for easily calling IEventListenerDemo functions. */

/* IUnknown functions. */
#define IEventListenerDemo_QueryInterface(This,riid,ppvObject) \
    ( (This)->lpVtbl->QueryInterface(This,riid,ppvObject) )

#define IEventListenerDemo_AddRef(This) \
    ( (This)->lpVtbl->AddRef(This) )

#define IEventListenerDemo_Release(This) \
    ( (This)->lpVtbl->Release(This) )

#ifdef WIN32
/* IDispatch functions. */
#define IEventListenerDemo_GetTypeInfoCount(This,pctinfo) \
    ( (This)->lpVtbl->GetTypeInfoCount(This,pctinfo) )

#define IEventListenerDemo_GetTypeInfo(This,iTInfo,lcid,ppTInfo) \
    ( (This)->lpVtbl->GetTypeInfo(This,iTInfo,lcid,ppTInfo) )

#define IEventListenerDemo_GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId) \
    ( (This)->lpVtbl->GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId) )

#define IEventListenerDemo_Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr) \
    ( (This)->lpVtbl->Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr) )
#endif

/* IEventListener functions. */
#define IEventListenerDemo_HandleEvent(This,aEvent) \
    ( (This)->lpVtbl->HandleEvent(This,aEvent) )


/**
 * Event handler function, for active event processing.
 */
static HRESULT IEventListenerDemoImpl_HandleEvent(IEventListenerDemo *pThis, IEvent *event)
{
    return EventListenerDemoProcessEvent(event);
}

static HRESULT IEventListenerDemoImpl_QueryInterface(IEventListenerDemo *pThis, const IID *iid, void **resultp)
{
    /* match iid */
    if (    !memcmp(iid, &IID_IEventListener, sizeof(IID))
        ||  !memcmp(iid, &IID_IDispatch, sizeof(IID))
        ||  !memcmp(iid, &IID_IUnknown, sizeof(IID)))
    {
        IEventListenerDemo_AddRef(pThis);
        *resultp = pThis;
        return S_OK;
    }
#ifdef WIN32
    if (!memcmp(iid, &IID_IMarshal, sizeof(IID)))
        return IUnknown_QueryInterface(pThis->pUnkMarshaler, iid, resultp);
#endif

    return E_NOINTERFACE;
}

static HRESULT IEventListenerDemoImpl_AddRef(IEventListenerDemo *pThis)
{
    return ++(pThis->cRef);
}

static HRESULT IEventListenerDemoImpl_Release(IEventListenerDemo *pThis)
{
    HRESULT c;

    c = --(pThis->cRef);
    if (!c)
        free(pThis);
    return c;
}

#ifdef WIN32
static HRESULT IEventListenerDemoImpl_GetTypeInfoCount(IEventListenerDemo *pThis, UINT *pctinfo)
{
    if (!pctinfo)
        return E_POINTER;
    *pctinfo = 1;
    return S_OK;
}

static HRESULT IEventListenerDemoImpl_GetTypeInfo(IEventListenerDemo *pThis, UINT iTInfo, LCID lcid, ITypeInfo **ppTInfo)
{
    if (!ppTInfo)
        return E_POINTER;
    ITypeInfo_AddRef(g_pTInfoIEventListener);
    *ppTInfo = g_pTInfoIEventListener;
    return S_OK;
}

static HRESULT IEventListenerDemoImpl_GetIDsOfNames(IEventListenerDemo *pThis, REFIID riid, LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId)
{
    return ITypeInfo_GetIDsOfNames(g_pTInfoIEventListener, rgszNames, cNames, rgDispId);
}

static HRESULT IEventListenerDemoImpl_Invoke(IEventListenerDemo *pThis, DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pDispParams, VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr)
{
    return ITypeInfo_Invoke(g_pTInfoIEventListener, (IDispatch *)pThis, dispIdMember, wFlags, pDispParams, pVarResult, pExcepInfo, puArgErr);
}

static HRESULT LoadTypeInfo(REFIID riid, ITypeInfo **pTInfo)
{
    HRESULT rc;
    ITypeLib *pTypeLib;
    rc = LoadRegTypeLib(&LIBID_VirtualBox, 1 /* major */, 0 /* minor */, 0 /* lcid */, &pTypeLib);
    if (FAILED(rc))
        return rc;
    rc = ITypeLib_GetTypeInfoOfGuid(pTypeLib, riid, pTInfo);

    /* No longer need access to the type lib, release it. */
    ITypeLib_Release(pTypeLib);

    return rc;
}
#endif

#ifdef __GNUC__
typedef struct IEventListenerDemoVtblInt
{
    ptrdiff_t offset_to_top;
    void *typeinfo;
    IEventListenerDemoVtbl lpVtbl;
} IEventListenerDemoVtblInt;

static IEventListenerDemoVtblInt g_IEventListenerDemoVtblInt =
{
    0,      /* offset_to_top */
    NULL,   /* typeinfo, not vital */
    {
        IEventListenerDemoImpl_QueryInterface,
        IEventListenerDemoImpl_AddRef,
        IEventListenerDemoImpl_Release,
#ifdef WIN32
        IEventListenerDemoImpl_GetTypeInfoCount,
        IEventListenerDemoImpl_GetTypeInfo,
        IEventListenerDemoImpl_GetIDsOfNames,
        IEventListenerDemoImpl_Invoke,
#endif
        IEventListenerDemoImpl_HandleEvent
    }
};
#elif defined(_MSC_VER)
typedef struct IEventListenerDemoVtblInt
{
    IEventListenerDemoVtbl lpVtbl;
} IEventListenerDemoVtblInt;

static IEventListenerDemoVtblInt g_IEventListenerDemoVtblInt =
{
    {
        IEventListenerDemoImpl_QueryInterface,
        IEventListenerDemoImpl_AddRef,
        IEventListenerDemoImpl_Release,
#ifdef WIN32
        IEventListenerDemoImpl_GetTypeInfoCount,
        IEventListenerDemoImpl_GetTypeInfo,
        IEventListenerDemoImpl_GetIDsOfNames,
        IEventListenerDemoImpl_Invoke,
#endif
        IEventListenerDemoImpl_HandleEvent
    }
};
#else
# error Port me!
#endif

/**
 * Register active event listener for the selected VM.
 *
 * @param   virtualBox ptr to IVirtualBox object
 * @param   session    ptr to ISession object
 */
static void registerActiveEventListener(IVirtualBox *virtualBox, ISession *session)
{
    IConsole *console = NULL;
    HRESULT rc;

    rc = ISession_get_Console(session, &console);
    if ((SUCCEEDED(rc)) && console)
    {
        IEventSource *es = NULL;
        rc = IConsole_get_EventSource(console, &es);
        if (SUCCEEDED(rc) && es)
        {
            static const ULONG s_auInterestingEvents[] =
            {
                VBoxEventType_OnMousePointerShapeChanged,
                VBoxEventType_OnMouseCapabilityChanged,
                VBoxEventType_OnKeyboardLedsChanged,
                VBoxEventType_OnStateChanged,
                VBoxEventType_OnAdditionsStateChanged,
                VBoxEventType_OnNetworkAdapterChanged,
                VBoxEventType_OnSerialPortChanged,
                VBoxEventType_OnParallelPortChanged,
                VBoxEventType_OnStorageControllerChanged,
                VBoxEventType_OnMediumChanged,
                VBoxEventType_OnVRDEServerChanged,
                VBoxEventType_OnUSBControllerChanged,
                VBoxEventType_OnUSBDeviceStateChanged,
                VBoxEventType_OnSharedFolderChanged,
                VBoxEventType_OnRuntimeError,
                VBoxEventType_OnCanShowWindow,
                VBoxEventType_OnShowWindow
            };
            SAFEARRAY *interestingEventsSA = NULL;
            IEventListenerDemo *consoleListener = NULL;

            /* The VirtualBox API expects enum values as VT_I4, which in the
             * future can be hopefully relaxed. */
            interestingEventsSA = g_pVBoxFuncs->pfnSafeArrayCreateVector(VT_I4, 0,
                                                                           sizeof(s_auInterestingEvents)
                                                                         / sizeof(s_auInterestingEvents[0]));
            g_pVBoxFuncs->pfnSafeArrayCopyInParamHelper(interestingEventsSA, &s_auInterestingEvents,
                                                        sizeof(s_auInterestingEvents));

            consoleListener = calloc(1, sizeof(IEventListenerDemo));
            if (consoleListener)
            {
                consoleListener->lpVtbl = &(g_IEventListenerDemoVtblInt.lpVtbl);
#ifdef WIN32
                CoCreateFreeThreadedMarshaler((IUnknown *)consoleListener, &consoleListener->pUnkMarshaler);
#endif
                IEventListenerDemo_AddRef(consoleListener);

                rc = IEventSource_RegisterListener(es, (IEventListener *)consoleListener,
                                                   ComSafeArrayAsInParam(interestingEventsSA),
                                                   1 /* active */);
                if (SUCCEEDED(rc))
                {
                    /* Just wait here for events, no easy way to do this better
                     * as there's not much to do after this completes. */
                    printf("Entering event loop, PowerOff the machine to exit or press Ctrl-C to terminate\n");
                    fflush(stdout);
#ifdef WIN32
                    SetConsoleCtrlHandler(ctrlCHandler, TRUE);
#else
                    signal(SIGINT, (void (*)(int))ctrlCHandler);
#endif

                    while (!g_fStop)
                        g_pVBoxFuncs->pfnProcessEventQueue(250);

#ifdef WIN32
                    SetConsoleCtrlHandler(ctrlCHandler, FALSE);
#else
                    signal(SIGINT, SIG_DFL);
#endif
                }
                else
                    printf("Failed to register event listener.\n");
                IEventSource_UnregisterListener(es, (IEventListener *)consoleListener);
#ifdef WIN32
                if (consoleListener->pUnkMarshaler)
                    IUnknown_Release(consoleListener->pUnkMarshaler);
#endif
                IEventListenerDemo_Release(consoleListener);
            }
            else
                printf("Failed while allocating memory for console event listener.\n");
            g_pVBoxFuncs->pfnSafeArrayDestroy(interestingEventsSA);
            IEventSource_Release(es);
        }
        else
            printf("Failed to get the event source instance.\n");
        IConsole_Release(console);
    }
}

#else /* !USE_ACTIVE_EVENT_LISTENER */

/**
 * Register passive event listener for the selected VM.
 *
 * @param   virtualBox ptr to IVirtualBox object
 * @param   session    ptr to ISession object
 */
static void registerPassiveEventListener(ISession *session)
{
    IConsole *console = NULL;
    HRESULT rc;

    rc = ISession_get_Console(session, &console);
    if (SUCCEEDED(rc) && console)
    {
        IEventSource *es = NULL;
        rc = IConsole_get_EventSource(console, &es);
        if (SUCCEEDED(rc) && es)
        {
            static const ULONG s_auInterestingEvents[] =
            {
                VBoxEventType_OnMousePointerShapeChanged,
                VBoxEventType_OnMouseCapabilityChanged,
                VBoxEventType_OnKeyboardLedsChanged,
                VBoxEventType_OnStateChanged,
                VBoxEventType_OnAdditionsStateChanged,
                VBoxEventType_OnNetworkAdapterChanged,
                VBoxEventType_OnSerialPortChanged,
                VBoxEventType_OnParallelPortChanged,
                VBoxEventType_OnStorageControllerChanged,
                VBoxEventType_OnMediumChanged,
                VBoxEventType_OnVRDEServerChanged,
                VBoxEventType_OnUSBControllerChanged,
                VBoxEventType_OnUSBDeviceStateChanged,
                VBoxEventType_OnSharedFolderChanged,
                VBoxEventType_OnRuntimeError,
                VBoxEventType_OnCanShowWindow,
                VBoxEventType_OnShowWindow
            };
            SAFEARRAY *interestingEventsSA = NULL;
            IEventListener *consoleListener = NULL;

            /* The VirtualBox API expects enum values as VT_I4, which in the
             * future can be hopefully relaxed. */
            interestingEventsSA = g_pVBoxFuncs->pfnSafeArrayCreateVector(VT_I4, 0,
                                                                           sizeof(s_auInterestingEvents)
                                                                         / sizeof(s_auInterestingEvents[0]));
            g_pVBoxFuncs->pfnSafeArrayCopyInParamHelper(interestingEventsSA, &s_auInterestingEvents,
                                                        sizeof(s_auInterestingEvents));

            rc = IEventSource_CreateListener(es, &consoleListener);
            if (SUCCEEDED(rc) && consoleListener)
            {
                rc = IEventSource_RegisterListener(es, consoleListener,
                                                   ComSafeArrayAsInParam(interestingEventsSA),
                                                   0 /* passive */);
                if (SUCCEEDED(rc))
                {
                    /* Just wait here for events, no easy way to do this better
                     * as there's not much to do after this completes. */
                    printf("Entering event loop, PowerOff the machine to exit or press Ctrl-C to terminate\n");
                    fflush(stdout);
#ifdef WIN32
                    SetConsoleCtrlHandler(ctrlCHandler, TRUE);
#else
                    signal(SIGINT, ctrlCHandler);
#endif

                    while (!g_fStop)
                    {
                        IEvent *ev = NULL;
                        rc = IEventSource_GetEvent(es, consoleListener, 250, &ev);
                        if (FAILED(rc))
                        {
                            printf("Failed getting event: %#x\n", rc);
                            g_fStop = 1;
                            continue;
                        }
                        /* handle timeouts, resulting in NULL events */
                        if (!ev)
                            continue;
                        rc = EventListenerDemoProcessEvent(ev);
                        if (FAILED(rc))
                        {
                            printf("Failed processing event: %#x\n", rc);
                            g_fStop = 1;
                            /* finish processing the event */
                        }
                        rc = IEventSource_EventProcessed(es, consoleListener, ev);
                        if (FAILED(rc))
                        {
                            printf("Failed to mark event as processed: %#x\n", rc);
                            g_fStop = 1;
                            /* continue with event release */
                        }
                        if (ev)
                        {
                            IEvent_Release(ev);
                            ev = NULL;
                        }
                    }

#ifdef WIN32
                    SetConsoleCtrlHandler(ctrlCHandler, FALSE);
#else
                    signal(SIGINT, SIG_DFL);
#endif
                }
                else
                    printf("Failed to register event listener.\n");
                IEventSource_UnregisterListener(es, (IEventListener *)consoleListener);
                IEventListener_Release(consoleListener);
            }
            else
                printf("Failed to create an event listener instance.\n");
            g_pVBoxFuncs->pfnSafeArrayDestroy(interestingEventsSA);
            IEventSource_Release(es);
        }
        else
            printf("Failed to get the event source instance.\n");
        IConsole_Release(console);
    }
}

#endif /* !USE_ACTIVE_EVENT_LISTENER */

/**
 * Print detailed error information if available.
 * @param   pszExecutable   string with the executable name
 * @param   pszErrorMsg     string containing the code location specific error message
 * @param   rc              COM/XPCOM result code
 */
static void PrintErrorInfo(const char *pszExecutable, const char *pszErrorMsg, HRESULT rc)
{
    IErrorInfo *ex;
    HRESULT rc2;
    fprintf(stderr, "%s: %s (rc=%#010x)\n", pszExecutable, pszErrorMsg, (unsigned)rc);
    rc2 = g_pVBoxFuncs->pfnGetException(&ex);
    if (SUCCEEDED(rc2) && ex)
    {
        IVirtualBoxErrorInfo *ei;
        rc2 = IErrorInfo_QueryInterface(ex, &IID_IVirtualBoxErrorInfo, (void **)&ei);
        if (SUCCEEDED(rc2) && ei != NULL)
        {
            /* got extended error info, maybe multiple infos */
            do
            {
                LONG resultCode = S_OK;
                BSTR componentUtf16 = NULL;
                char *component = NULL;
                BSTR textUtf16 = NULL;
                char *text = NULL;
                IVirtualBoxErrorInfo *ei_next = NULL;
                fprintf(stderr, "Extended error info (IVirtualBoxErrorInfo):\n");

                IVirtualBoxErrorInfo_get_ResultCode(ei, &resultCode);
                fprintf(stderr, "  resultCode=%#010x\n", (unsigned)resultCode);

                IVirtualBoxErrorInfo_get_Component(ei, &componentUtf16);
                g_pVBoxFuncs->pfnUtf16ToUtf8(componentUtf16, &component);
                g_pVBoxFuncs->pfnComUnallocString(componentUtf16);
                fprintf(stderr, "  component=%s\n", component);
                g_pVBoxFuncs->pfnUtf8Free(component);

                IVirtualBoxErrorInfo_get_Text(ei, &textUtf16);
                g_pVBoxFuncs->pfnUtf16ToUtf8(textUtf16, &text);
                g_pVBoxFuncs->pfnComUnallocString(textUtf16);
                fprintf(stderr, "  text=%s\n", text);
                g_pVBoxFuncs->pfnUtf8Free(text);

                rc2 = IVirtualBoxErrorInfo_get_Next(ei, &ei_next);
                if (FAILED(rc2))
                    ei_next = NULL;
                IVirtualBoxErrorInfo_Release(ei);
                ei = ei_next;
            } while (ei);
        }

        IErrorInfo_Release(ex);
        g_pVBoxFuncs->pfnClearException();
    }
}

/**
 * Start a VM.
 *
 * @param   argv0       executable name
 * @param   virtualBox  ptr to IVirtualBox object
 * @param   session     ptr to ISession object
 * @param   id          identifies the machine to start
 */
static void startVM(const char *argv0, IVirtualBox *virtualBox, ISession *session, BSTR id)
{
    HRESULT rc;
    IMachine  *machine    = NULL;
    IProgress *progress   = NULL;
    BSTR env              = NULL;
    BSTR sessionType;
    SAFEARRAY *groupsSA = g_pVBoxFuncs->pfnSafeArrayOutParamAlloc();

    rc = IVirtualBox_FindMachine(virtualBox, id, &machine);
    if (FAILED(rc) || !machine)
    {
        PrintErrorInfo(argv0, "Error: Couldn't get the Machine reference", rc);
        return;
    }

    rc = IMachine_get_Groups(machine, ComSafeArrayAsOutTypeParam(groupsSA, BSTR));
    if (SUCCEEDED(rc))
    {
        BSTR *groups = NULL;
        ULONG cbGroups = 0;
        ULONG i, cGroups;
        g_pVBoxFuncs->pfnSafeArrayCopyOutParamHelper((void **)&groups, &cbGroups, VT_BSTR, groupsSA);
        g_pVBoxFuncs->pfnSafeArrayDestroy(groupsSA);
        cGroups = cbGroups / sizeof(groups[0]);
        for (i = 0; i < cGroups; ++i)
        {
            /* Note that the use of %S might be tempting, but it is not
             * available on all platforms, and even where it is usable it
             * may depend on correct compiler options to make wchar_t a
             * 16 bit number. So better play safe and use UTF-8. */
            char *group;
            g_pVBoxFuncs->pfnUtf16ToUtf8(groups[i], &group);
            printf("Groups[%d]: %s\n", i, group);
            g_pVBoxFuncs->pfnUtf8Free(group);
        }
        for (i = 0; i < cGroups; ++i)
            g_pVBoxFuncs->pfnComUnallocString(groups[i]);
        g_pVBoxFuncs->pfnArrayOutFree(groups);
    }

    g_pVBoxFuncs->pfnUtf8ToUtf16("gui", &sessionType);
    rc = IMachine_LaunchVMProcess(machine, session, sessionType, env, &progress);
    g_pVBoxFuncs->pfnUtf16Free(sessionType);
    if (SUCCEEDED(rc))
    {
        BOOL completed;
        LONG resultCode;

        printf("Waiting for the remote session to open...\n");
        IProgress_WaitForCompletion(progress, -1);

        rc = IProgress_get_Completed(progress, &completed);
        if (FAILED(rc))
            fprintf(stderr, "Error: GetCompleted status failed\n");

        IProgress_get_ResultCode(progress, &resultCode);
        if (FAILED(resultCode))
        {
            IVirtualBoxErrorInfo *errorInfo;
            BSTR textUtf16;
            char *text;

            IProgress_get_ErrorInfo(progress, &errorInfo);
            IVirtualBoxErrorInfo_get_Text(errorInfo, &textUtf16);
            g_pVBoxFuncs->pfnUtf16ToUtf8(textUtf16, &text);
            printf("Error: %s\n", text);

            g_pVBoxFuncs->pfnComUnallocString(textUtf16);
            g_pVBoxFuncs->pfnUtf8Free(text);
            IVirtualBoxErrorInfo_Release(errorInfo);
        }
        else
        {
            fprintf(stderr, "VM process has been successfully started\n");

            /* Kick off the event listener demo part, which is quite separate.
             * Ignore it if you need a more basic sample. */
#ifdef USE_ACTIVE_EVENT_LISTENER
            registerActiveEventListener(virtualBox, session);
#else
            registerPassiveEventListener(session);
#endif
        }
        IProgress_Release(progress);
    }
    else
        PrintErrorInfo(argv0, "Error: LaunchVMProcess failed", rc);

    /* It's important to always release resources. */
    IMachine_Release(machine);
}

/**
 * List the registered VMs.
 *
 * @param   argv0       executable name
 * @param   virtualBox  ptr to IVirtualBox object
 * @param   session     ptr to ISession object
 */
static void listVMs(const char *argv0, IVirtualBox *virtualBox, ISession *session)
{
    HRESULT rc;
    SAFEARRAY *machinesSA = g_pVBoxFuncs->pfnSafeArrayOutParamAlloc();
    IMachine **machines = NULL;
    ULONG machineCnt = 0;
    ULONG i;
    unsigned start_id;

    /*
     * Get the list of all registered VMs.
     */
    rc = IVirtualBox_get_Machines(virtualBox, ComSafeArrayAsOutIfaceParam(machinesSA, IMachine *));
    if (FAILED(rc))
    {
        PrintErrorInfo(argv0, "could not get list of machines", rc);
        return;
    }

    /*
     * Extract interface pointers from machinesSA, and update the reference
     * counter of each object, as destroying machinesSA would call Release.
     */
    g_pVBoxFuncs->pfnSafeArrayCopyOutIfaceParamHelper((IUnknown ***)&machines, &machineCnt, machinesSA);
    g_pVBoxFuncs->pfnSafeArrayDestroy(machinesSA);

    if (!machineCnt)
    {
        g_pVBoxFuncs->pfnArrayOutFree(machines);
        printf("\tNo VMs\n");
        return;
    }

    printf("VM List:\n\n");

    /*
     * Iterate through the collection.
     */
    for (i = 0; i < machineCnt; ++i)
    {
        IMachine *machine      = machines[i];
        BOOL      isAccessible = FALSE;

        printf("\tMachine #%u\n", (unsigned)i);

        if (!machine)
        {
            printf("\t(skipped, NULL)\n");
            continue;
        }

        IMachine_get_Accessible(machine, &isAccessible);

        if (isAccessible)
        {
            BSTR machineNameUtf16;
            char *machineName;

            IMachine_get_Name(machine, &machineNameUtf16);
            g_pVBoxFuncs->pfnUtf16ToUtf8(machineNameUtf16,&machineName);
            g_pVBoxFuncs->pfnComUnallocString(machineNameUtf16);
            printf("\tName:        %s\n", machineName);
            g_pVBoxFuncs->pfnUtf8Free(machineName);
        }
        else
            printf("\tName:        <inaccessible>\n");

        {
            BSTR uuidUtf16;
            char      *uuidUtf8;

            IMachine_get_Id(machine, &uuidUtf16);
            g_pVBoxFuncs->pfnUtf16ToUtf8(uuidUtf16, &uuidUtf8);
            g_pVBoxFuncs->pfnComUnallocString(uuidUtf16);
            printf("\tUUID:        %s\n", uuidUtf8);
            g_pVBoxFuncs->pfnUtf8Free(uuidUtf8);
        }

        if (isAccessible)
        {
            {
                BSTR      configFileUtf16;
                char      *configFileUtf8;

                IMachine_get_SettingsFilePath(machine, &configFileUtf16);
                g_pVBoxFuncs->pfnUtf16ToUtf8(configFileUtf16, &configFileUtf8);
                g_pVBoxFuncs->pfnComUnallocString(configFileUtf16);
                printf("\tConfig file: %s\n", configFileUtf8);
                g_pVBoxFuncs->pfnUtf8Free(configFileUtf8);
            }

            {
                ULONG memorySize;

                IMachine_get_MemorySize(machine, &memorySize);
                printf("\tMemory size: %uMB\n", memorySize);
            }

            {
                BSTR typeId;
                BSTR osNameUtf16;
                char *osName;
                IGuestOSType *osType = NULL;

                IMachine_get_OSTypeId(machine, &typeId);
                IVirtualBox_GetGuestOSType(virtualBox, typeId, &osType);
                g_pVBoxFuncs->pfnComUnallocString(typeId);
                IGuestOSType_get_Description(osType, &osNameUtf16);
                g_pVBoxFuncs->pfnUtf16ToUtf8(osNameUtf16,&osName);
                g_pVBoxFuncs->pfnComUnallocString(osNameUtf16);
                printf("\tGuest OS:    %s\n\n", osName);
                g_pVBoxFuncs->pfnUtf8Free(osName);

                IGuestOSType_Release(osType);
            }
        }
    }

    /*
     * Let the user chose a machine to start.
     */
    printf("Type Machine# to start (0 - %u) or 'quit' to do nothing: ",
           (unsigned)(machineCnt - 1));
    fflush(stdout);

    if (scanf("%u", &start_id) == 1 && start_id < machineCnt)
    {
        IMachine *machine = machines[start_id];

        if (machine)
        {
            BSTR uuidUtf16 = NULL;

            IMachine_get_Id(machine, &uuidUtf16);
            startVM(argv0, virtualBox, session, uuidUtf16);
            g_pVBoxFuncs->pfnComUnallocString(uuidUtf16);
        }
    }

    /*
     * Don't forget to release the objects in the array.
     */
    for (i = 0; i < machineCnt; ++i)
    {
        IMachine *machine = machines[i];

        if (machine)
            IMachine_Release(machine);
    }
    g_pVBoxFuncs->pfnArrayOutFree(machines);
}

/* Main - Start the ball rolling. */

int main(int argc, char **argv)
{
    IVirtualBoxClient *vboxclient = NULL;
    IVirtualBox *vbox            = NULL;
    ISession   *session          = NULL;
    ULONG       revision         = 0;
    BSTR        versionUtf16     = NULL;
    BSTR        homefolderUtf16  = NULL;
    HRESULT     rc;     /* Result code of various function (method) calls. */
    (void)argc;

    printf("Starting main()\n");

    if (VBoxCGlueInit())
    {
        fprintf(stderr, "%s: FATAL: VBoxCGlueInit failed: %s\n",
                argv[0], g_szVBoxErrMsg);
        return EXIT_FAILURE;
    }

    {
        unsigned ver = g_pVBoxFuncs->pfnGetVersion();
        printf("VirtualBox version: %u.%u.%u\n", ver / 1000000, ver / 1000 % 1000, ver % 1000);
        ver = g_pVBoxFuncs->pfnGetAPIVersion();
        printf("VirtualBox API version: %u.%u\n", ver / 1000, ver % 1000);
    }

    g_pVBoxFuncs->pfnClientInitialize(NULL, &vboxclient);
    if (!vboxclient)
    {
        fprintf(stderr, "%s: FATAL: could not get VirtualBoxClient reference\n", argv[0]);
        return EXIT_FAILURE;
    }

    printf("----------------------------------------------------\n");

    rc = IVirtualBoxClient_get_VirtualBox(vboxclient, &vbox);
    if (FAILED(rc) || !vbox)
    {
        PrintErrorInfo(argv[0], "FATAL: could not get VirtualBox reference", rc);
        return EXIT_FAILURE;
    }
    rc = IVirtualBoxClient_get_Session(vboxclient, &session);
    if (FAILED(rc) || !session)
    {
        PrintErrorInfo(argv[0], "FATAL: could not get Session reference", rc);
        return EXIT_FAILURE;
    }

#ifdef USE_ACTIVE_EVENT_LISTENER
# ifdef WIN32
    rc = LoadTypeInfo(&IID_IEventListener, &g_pTInfoIEventListener);
    if (FAILED(rc) || !g_pTInfoIEventListener)
    {
        PrintErrorInfo(argv[0], "FATAL: could not get type information for IEventListener", rc);
        return EXIT_FAILURE;
    }
# endif /* WIN32 */
#endif /* USE_ACTIVE_EVENT_LISTENER */

    /*
     * Now ask for revision, version and home folder information of
     * this vbox. Were not using fancy macros here so it
     * remains easy to see how we access C++'s vtable.
     */

    /* 1. Revision */
    rc = IVirtualBox_get_Revision(vbox, &revision);
    if (SUCCEEDED(rc))
        printf("\tRevision: %u\n", revision);
    else
        PrintErrorInfo(argv[0], "GetRevision() failed", rc);

    /* 2. Version */
    rc = IVirtualBox_get_Version(vbox, &versionUtf16);
    if (SUCCEEDED(rc))
    {
        char *version = NULL;
        g_pVBoxFuncs->pfnUtf16ToUtf8(versionUtf16, &version);
        printf("\tVersion: %s\n", version);
        g_pVBoxFuncs->pfnUtf8Free(version);
        g_pVBoxFuncs->pfnComUnallocString(versionUtf16);
    }
    else
        PrintErrorInfo(argv[0], "GetVersion() failed", rc);

    /* 3. Home Folder */
    rc = IVirtualBox_get_HomeFolder(vbox, &homefolderUtf16);
    if (SUCCEEDED(rc))
    {
        char *homefolder = NULL;
        g_pVBoxFuncs->pfnUtf16ToUtf8(homefolderUtf16, &homefolder);
        printf("\tHomeFolder: %s\n", homefolder);
        g_pVBoxFuncs->pfnUtf8Free(homefolder);
        g_pVBoxFuncs->pfnComUnallocString(homefolderUtf16);
    }
    else
        PrintErrorInfo(argv[0], "GetHomeFolder() failed", rc);

    listVMs(argv[0], vbox, session);
    ISession_UnlockMachine(session);

    printf("----------------------------------------------------\n");

    /*
     * Do as mom told us: always clean up after yourself.
     */
#ifdef USE_ACTIVE_EVENT_LISTENER
# ifdef WIN32
    if (g_pTInfoIEventListener)
    {
        ITypeInfo_Release(g_pTInfoIEventListener);
        g_pTInfoIEventListener = NULL;
    }
# endif /* WIN32 */
#endif /* USE_ACTIVE_EVENT_LISTENER */

    if (session)
    {
        ISession_Release(session);
        session = NULL;
    }
    if (vbox)
    {
        IVirtualBox_Release(vbox);
        vbox = NULL;
    }
    if (vboxclient)
    {
        IVirtualBoxClient_Release(vboxclient);
        vboxclient = NULL;
    }

    g_pVBoxFuncs->pfnClientUninitialize();
    VBoxCGlueTerm();
    printf("Finished main()\n");

    return 0;
}
/* vim: set ts=4 sw=4 et: */
