/* dpnet.dll
 *
 * Copyright 2002, 2003 TransGaming Technologies
 *
 * David Hammerton (david@transgaming.com)
 */

/* THIS IS NOT AN IMPLEMENTATION OF THE DIRECTPLAY8THREADPOOL INTERFACE
 * INTRODUCED IN DIRECTPLAY9.
 * This is our INTERNAL INTERFACE for ThreadPools.
 * FIXME: Change this interface to "internal" use only and expose
 *        a DirectPlay8ThreadPool interface to the application, the
 *        one that has been introduced in DirectPlay9
 */

#include "windef.h"
#include "winbase.h"
#include "winuser.h"
#include "winreg.h"
#include "objbase.h"
#include "wine/debug.h"
#include "winerror.h"
#include "winsock2.h"

#include "dplay8.h"
#include "dplobby8.h"
#include "dplay8_private.h"

WINE_DEFAULT_DEBUG_CHANNEL(dplay);

static ICOM_VTABLE(IDirectPlay8ThreadPool_Internal) directPlay8ThreadPool_InternalVT;
static ICOM_VTABLE(IDirectPlay8ThreadPool) directPlay8ThreadPoolVT;

static void DPTP_TimerQueue(void *pvIface, void *pvReserved);

static WINAPI DWORD THREADPOOL_AThreadsLife(void *pvIface)
{
    PDIRECTPLAY8THREADPOOL_INTERNAL iface = pvIface;
    ICOM_THIS_MULTI(IDirectPlay8ThreadPoolImpl, lpVtbl2, iface);

    DWORD dwWaitResult;

    dwWaitResult = WaitForSingleObject(This->hQueueMutex, INFINITE);
    if (dwWaitResult != WAIT_OBJECT_0)
    {
        ERR("Something bad happened waiting for a mutex. This thread is outa here\n");
        return 0;
    }

    while (1)
    {
        tpJobFunc jobFunc;
        void *arg1, *arg2;
        tpJobQueueNode *tempQueue;

        while (This->pjqHead == NULL)
        { /* wait for a job to be available */
            HANDLE hWaitObjects[2];
            hWaitObjects[0] = This->hQueueMutex;
            hWaitObjects[1] = This->hJobPosted;
            ReleaseMutex(This->hQueueMutex);
            dwWaitResult = WaitForMultipleObjects(2, hWaitObjects, TRUE, INFINITE);
            ResetEvent(This->hJobPosted);
            if (dwWaitResult < WAIT_OBJECT_0 || dwWaitResult > WAIT_OBJECT_0 + 1)
            {
                ERR("WaitForMultipleObjects failed. thread outa here\n");
                return 0;
            }
        }

        /* grab the job and remove it from the queue */
        tempQueue = This->pjqHead; /* old head - this work order */
        This->pjqHead = tempQueue->next; /* new head */

        jobFunc = tempQueue->func;
        arg1 = tempQueue->arg1;
        arg2 = tempQueue->arg2;

        tempQueue->func = NULL;
        tempQueue->arg1 = NULL;
        tempQueue->arg2 = NULL;

        if (This->pjqHead != NULL)
            This->pjqHead->prev = NULL;

        if (This->dwCurrentJobQueues > This->dwMinJobQueues)
        {
            HeapFree(GetProcessHeap(), 0, tempQueue);
            This->dwCurrentJobQueues--;
        }
        else
        {
            if (This->pjqFreeTail)
                This->pjqFreeTail->next = tempQueue;
            tempQueue->prev = This->pjqFreeTail;
            This->pjqFreeTail = tempQueue;
            This->pjqFreeTail->next = NULL;
        }

        SetEvent(This->hJobTaken);
        ReleaseMutex(This->hQueueMutex);

        jobFunc(arg1, arg2);

        WaitForSingleObject(This->hQueueMutex, INFINITE);
    }

    return 0;
}

HRESULT WINAPI
DirectPlay8ThreadPool_Internal_QueryInterface(PDIRECTPLAY8THREADPOOL_INTERNAL iface,
                                       REFIID riid, LPVOID *obj)
{
    ICOM_THIS_MULTI(IDirectPlay8ThreadPoolImpl, lpVtbl2, iface);
    TRACE("(%p)->(%s, %p)\n", iface, debugstr_guid(riid), obj);
    return IDirectPlay8ThreadPool_QueryInterface((PDIRECTPLAY8THREADPOOL) This, riid, obj);
}

ULONG WINAPI
DirectPlay8ThreadPool_Internal_AddRef(PDIRECTPLAY8THREADPOOL_INTERNAL iface)
{
    ICOM_THIS_MULTI(IDirectPlay8ThreadPoolImpl, lpVtbl2, iface);
    TRACE("(%p)->()\n", iface);
    return IDirectPlay8ThreadPool_AddRef((PDIRECTPLAY8THREADPOOL) This);
}

ULONG WINAPI
DirectPlay8ThreadPool_Internal_Release(PDIRECTPLAY8THREADPOOL_INTERNAL iface)
{
    ICOM_THIS_MULTI(IDirectPlay8ThreadPoolImpl, lpVtbl2, iface);
    TRACE("(%p)->()\n", iface);
    return IDirectPlay8ThreadPool_Release((PDIRECTPLAY8THREADPOOL) This);
}

HRESULT WINAPI DirectPlay8ThreadPool_Internal_Initialize(PDIRECTPLAY8THREADPOOL_INTERNAL iface,
                                                DWORD dwMaxThreads,
                                                DWORD dwFlags)
{
    ICOM_THIS_MULTI(IDirectPlay8ThreadPoolImpl, lpVtbl2, iface);
    int i;
    TRACE("(%p)->(%li, 0x%08lx)\n", iface, dwMaxThreads, dwFlags);

    This->dwThreadCount = dwMaxThreads;
    /* fixme: this doesn't allow for threads to grow */
    This->hThreadArray = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(HANDLE) * This->dwThreadCount);

    This->dwMaxJobQueues = dwMaxThreads * 5;
    This->dwMinJobQueues = dwMaxThreads;
    This->hJobTaken = CreateEventA(NULL, FALSE, FALSE, "DPNET JobTakenEvent");
    This->hJobPosted = CreateEventA(NULL, FALSE, FALSE, "DPNET JobPostedEvent");
    This->hQueueMutex = CreateMutexA(NULL, TRUE, "DPNET QueueMutex");

    This->hTimerModified = CreateEventA(NULL, FALSE, FALSE, "DPNET TimerModifiedEvent");
    This->hTimerMutex = CreateMutexA(NULL, FALSE, "DPNET TimerMutex");

    for (i = 0; i < This->dwThreadCount; i++)
    {
        DWORD threadId;
        This->hThreadArray[i] = CreateThread(NULL, 0, THREADPOOL_AThreadsLife, (void *)iface, 0, &threadId);
        if (This->hThreadArray[i] == 0)
        {
            ERR("Couldn't create a thread. Oh no! Clean me up. Holding lock.\n");
            return DPNERR_GENERIC;
        }
    }

    ReleaseMutex(This->hQueueMutex);
    IDirectPlay8ThreadPool_Internal_QueueWorkItem(iface, DPTP_TimerQueue, (void *) iface, NULL);

    return S_OK;
}

HRESULT WINAPI DirectPlay8ThreadPool_Internal_QueueWorkItem(PDIRECTPLAY8THREADPOOL_INTERNAL iface,
                                                   tpJobFunc pfjCallback,
                                                   void *arg1, void *arg2)
{
    ICOM_THIS_MULTI(IDirectPlay8ThreadPoolImpl, lpVtbl2, iface);
    DWORD dwWaitResult;
    HRESULT hResult = S_OK;

    TRACE("(%p)->(%p, %p, %p)\n", iface, pfjCallback, arg1, arg2);

    dwWaitResult = WaitForSingleObject(This->hQueueMutex, 5000L);
    if (dwWaitResult != WAIT_OBJECT_0)
    {
        ERR("WaitForSingleObject timed out\n");
        return DPNERR_GENERIC;
    }

    if (This->pjqFreeTail == NULL || This->dwCurrentJobQueues < This->dwMinJobQueues)
    {
        while(This->pjqFreeTail == NULL && This->dwCurrentJobQueues >= This->dwMaxJobQueues)
        {
            HANDLE hWaitObjects[2];
            hWaitObjects[0] = This->hJobTaken;
            hWaitObjects[1] = This->hQueueMutex;
            ReleaseMutex(This->hQueueMutex);
            dwWaitResult = WaitForMultipleObjects(2, hWaitObjects, TRUE, 50000L);
            ResetEvent(This->hJobTaken);
            if (dwWaitResult < WAIT_OBJECT_0 || dwWaitResult > WAIT_OBJECT_0 + 1)
            {
                ERR("WaitForMultipleObjects timed out\n");
                return DPNERR_GENERIC;
            }
        }
        if (This->pjqFreeTail == NULL) /* this could have changed */
        {
            This->pjqFreeTail = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(tpJobQueueNode));
            This->dwCurrentJobQueues++;
        }
        else if (This->dwCurrentJobQueues < This->dwMinJobQueues)
        {
            tpJobQueueNode *tempQueue = This->pjqFreeTail;

            This->pjqFreeTail = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(tpJobQueueNode));
            This->pjqFreeTail->prev = tempQueue;
            tempQueue->next = This->pjqFreeTail;
            This->dwCurrentJobQueues++;
        }
    }

    if (This->pjqTail)
        This->pjqTail->next = This->pjqFreeTail;
    This->pjqFreeTail->prev = This->pjqTail;
    This->pjqTail = This->pjqFreeTail;
    if (!This->pjqHead)
        This->pjqHead = This->pjqTail;

    if (This->pjqFreeTail->prev)
        This->pjqFreeTail->prev->next = NULL;
    This->pjqFreeTail = This->pjqFreeTail->prev;

    This->pjqTail->func = pfjCallback;
    This->pjqTail->arg1 = arg1;
    This->pjqTail->arg2 = arg2;

    ReleaseMutex(This->hQueueMutex);
    SetEvent(This->hJobPosted);

    return hResult;
}

HRESULT WINAPI DirectPlay8ThreadPool_Internal_QueueTimerItem(PDIRECTPLAY8THREADPOOL_INTERNAL iface,
                                                    DPTP_timerInfo *newTimerInfo)
{
    ICOM_THIS_MULTI(IDirectPlay8ThreadPoolImpl, lpVtbl2, iface);

    DWORD dwWaitResult;
    HRESULT hResult = S_OK;
    tpTimerQueueNode *ptpNewTimer;

    TRACE("(%p)->(%p)\n", iface, newTimerInfo);

    dwWaitResult = WaitForSingleObject(This->hTimerMutex, 5000L);
    if (dwWaitResult != WAIT_OBJECT_0)
    {
        ERR("WaitForSingleObject timed out\n");
        return DPNERR_GENERIC;
    }

    ptpNewTimer = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(tpTimerQueueNode));
    newTimerInfo->dpData = ptpNewTimer;
    memcpy(&ptpNewTimer->timerInfo, newTimerInfo, sizeof(DPTP_timerInfo));
    ptpNewTimer->dwTimeRequested = GetTickCount();
    ptpNewTimer->waiting = TRUE;

    if (This->ptqTail)
        This->ptqTail->next = ptpNewTimer;
    ptpNewTimer->prev = This->ptqTail;
    This->ptqTail = ptpNewTimer;

    SetEvent(This->hTimerModified);
    ReleaseMutex(This->hTimerMutex);

    return hResult;
}

HRESULT WINAPI DirectPlay8ThreadPool_Internal_DeleteTimerItem(PDIRECTPLAY8THREADPOOL_INTERNAL iface,
                                                     void *pdpData)
{
    ICOM_THIS_MULTI(IDirectPlay8ThreadPoolImpl, lpVtbl2, iface);
    tpTimerQueueNode *ptpDeadTimer = pdpData;

    TRACE("(%p)->(%p)\n", iface, pdpData);

    WaitForSingleObject(This->hTimerMutex, INFINITE);
    if (ptpDeadTimer == This->ptqTail)
        This->ptqTail = ptpDeadTimer->prev;
    if (ptpDeadTimer->prev != NULL)
        ptpDeadTimer->prev->next = ptpDeadTimer->next;
    if (ptpDeadTimer->next != NULL)
        ptpDeadTimer->next->prev = ptpDeadTimer->prev;
    CloseHandle(ptpDeadTimer->waitableTimer);
    HeapFree(GetProcessHeap(), 0, ptpDeadTimer);
    SetEvent(This->hTimerModified);
    ReleaseMutex(This->hTimerMutex);
    return S_OK;
}

HRESULT WINAPI DirectPlay8ThreadPool_Internal_ResetTimerItem(PDIRECTPLAY8THREADPOOL_INTERNAL iface,
                                                    void *pdpData)
{
    ICOM_THIS_MULTI(IDirectPlay8ThreadPoolImpl, lpVtbl2, iface);
    tpTimerQueueNode *ptpResetTimer = pdpData;

    TRACE("(%p)->(%p)\n", iface, pdpData);

    WaitForSingleObject(This->hTimerMutex, INFINITE);
    ptpResetTimer->dwTimeRequested = GetTickCount();
    ptpResetTimer->waiting = TRUE;
    SetEvent(This->hTimerModified);
    ReleaseMutex(This->hTimerMutex);
    return S_OK;
}

static void DPTP_TimerQueue(void *pvIface, void *pvReserved)
{
    PDIRECTPLAY8THREADPOOL_INTERNAL *iface = (PDIRECTPLAY8THREADPOOL_INTERNAL *) pvIface;

    ICOM_THIS_MULTI(IDirectPlay8ThreadPoolImpl, lpVtbl2, iface);
    DWORD dwWaitResult;

    dwWaitResult = WaitForSingleObject(This->hTimerMutex, INFINITE);
    if (dwWaitResult != WAIT_OBJECT_0)
    {
        ERR("something bad happened waiting for a mutex. Gone.\n");
        return;
    }
    ResetEvent(This->hTimerModified);

    while (1)
    {
        HANDLE *hWaitObjects = NULL;
        int timerCount = 0, currentTimer = 0;
        tpTimerQueueNode *ptqCurrent;

        while (!timerCount)
        {
            timerCount = 0;
            ptqCurrent = This->ptqTail;
            while (ptqCurrent)
            {
                if (ptqCurrent->waiting)
                    timerCount++;
                ptqCurrent = ptqCurrent->prev;
            }
            if (!timerCount)
            { /* wait for a timer to be posted */
                ReleaseMutex(This->hTimerMutex);
                WaitForSingleObject(This->hTimerModified, INFINITE);
                WaitForSingleObject(This->hTimerMutex, INFINITE);
                ResetEvent(This->hTimerModified);
                continue;
            }
        }

        hWaitObjects = HeapAlloc(GetProcessHeap(), 0, sizeof(HANDLE) * (timerCount + 1));

        ptqCurrent = This->ptqTail;
        while (ptqCurrent)
        {
            int timeLeft; /* milliseconds */

            if (!ptqCurrent->waiting)
            {
                ptqCurrent = ptqCurrent->prev;
                continue;
            }

            if (!ptqCurrent->waitableTimer)
                ptqCurrent->waitableTimer = CreateWaitableTimerA(NULL, TRUE, NULL);
            if (!ptqCurrent->waitableTimer)
            {
                ERR("couldn't creater timer\n");
                timerCount--;
                continue;
            }
            timeLeft = ptqCurrent->timerInfo.dwTimerWait - (GetTickCount() - ptqCurrent->dwTimeRequested);
            if (timeLeft <= 0) timeLeft = 1; /* go streight away */
            ptqCurrent->liDueTime.QuadPart = -10000 * timeLeft;
            SetWaitableTimer(ptqCurrent->waitableTimer, &ptqCurrent->liDueTime, 0, NULL, NULL, 0);
            hWaitObjects[currentTimer] = ptqCurrent->waitableTimer;
            ptqCurrent = ptqCurrent->prev;
            currentTimer++;
        }
        hWaitObjects[currentTimer] = This->hTimerModified;
        ReleaseMutex(This->hTimerMutex);
        dwWaitResult = WaitForMultipleObjects(timerCount + 1, hWaitObjects, FALSE, INFINITE);
        if (dwWaitResult < WAIT_OBJECT_0 || dwWaitResult > WAIT_OBJECT_0 + timerCount)
        {
            ERR("WaitForMultipleObjects failed. don't know what to do.\n");
        }
        else if (dwWaitResult == WAIT_OBJECT_0 + timerCount) /* timer modified signal */
        {
            /* nothing here yet */
            WARN("timer was modified\n");
        }
        else
        {
            int timerSignaled = dwWaitResult - WAIT_OBJECT_0;

            WaitForSingleObject(This->hTimerMutex, INFINITE);

            if (WaitForSingleObject(This->hTimerModified, 0) != WAIT_TIMEOUT)
            {
                WARN("timer queue has been modified\n");
                ResetEvent(This->hTimerModified);
                ReleaseMutex(This->hTimerMutex);
            }
            else
            {
                tpJobFunc jobFunc;
                void *arg1, *arg2;

                currentTimer = 0;
                ptqCurrent = This->ptqTail;

                while (ptqCurrent && currentTimer != timerSignaled)
                {
                    ptqCurrent = ptqCurrent->prev;
                    currentTimer++;
                }
                if (currentTimer == timerSignaled)
                {
                    jobFunc = ptqCurrent->timerInfo.func;
                    arg1 = ptqCurrent->timerInfo.arg1;
                    arg2 = ptqCurrent->timerInfo.arg2;
/*                    if (ptqCurrent->next)
                        ptqCurrent->next->prev = ptqCurrent->prev;
                    if (ptqCurrent->prev)
                        ptqCurrent->prev->next = ptqCurrent->next;
                    if (This->ptqTail == ptqCurrent)
                        This->ptqTail = ptqCurrent->prev;
                    HeapFree(GetProcessHeap(), 0, ptqCurrent);*/
                    ptqCurrent->waiting = FALSE;
                    ReleaseMutex(This->hTimerMutex);
                    IDirectPlay8ThreadPool_Internal_QueueWorkItem(
                            (PDIRECTPLAY8THREADPOOL_INTERNAL)iface,
                            jobFunc, arg1, arg2);
                }
            }
        }
        HeapFree(GetProcessHeap(), 0, hWaitObjects);
        WaitForSingleObject(This->hTimerMutex, INFINITE);
    }
}

HRESULT DPNET_CreateDirectPlay8ThreadPool(IUnknown *pOuter, REFIID riid, LPVOID *ppobj)
{
    IDirectPlay8ThreadPoolImpl *ipDP8TP;
    HRESULT hr;
    TRACE("()\n");
    ipDP8TP = (IDirectPlay8ThreadPoolImpl *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,
                                        sizeof(IDirectPlay8ThreadPoolImpl));
    if (ipDP8TP == NULL) return E_OUTOFMEMORY;
    ICOM_VTBL(ipDP8TP) = &directPlay8ThreadPoolVT;
    ipDP8TP->lpVtbl2 = &directPlay8ThreadPool_InternalVT;
    IDirectPlay8ThreadPool_AddRef((PDIRECTPLAY8THREADPOOL)ipDP8TP);
    TRACE("Created new object: %p\n", ipDP8TP);
    hr = IDirectPlay8ThreadPool_QueryInterface((PDIRECTPLAY8THREADPOOL)ipDP8TP, riid, ppobj);
    IDirectPlay8ThreadPool_Release((PDIRECTPLAY8THREADPOOL)ipDP8TP);
    if (SUCCEEDED(hr))
        return S_OK;
    else
        return E_NOINTERFACE;
}


static ICOM_VTABLE(IDirectPlay8ThreadPool_Internal) directPlay8ThreadPool_InternalVT =
{
    ICOM_MSVTABLE_COMPAT_DummyRTTIVALUE
    DirectPlay8ThreadPool_Internal_QueryInterface,
    DirectPlay8ThreadPool_Internal_AddRef,
    DirectPlay8ThreadPool_Internal_Release,
    DirectPlay8ThreadPool_Internal_Initialize,
    (void*)0xdead0205, /* GetMaxThreads */
    (void*)0xdead0206,  /* SetMaxThreads */
    DirectPlay8ThreadPool_Internal_QueueWorkItem,
    DirectPlay8ThreadPool_Internal_QueueTimerItem,
    DirectPlay8ThreadPool_Internal_DeleteTimerItem,
    DirectPlay8ThreadPool_Internal_ResetTimerItem
};

/* IDirectPlay8ThreadPool */
HRESULT WINAPI
DirectPlay8ThreadPool_QueryInterface(PDIRECTPLAY8THREADPOOL iface,
                                     REFIID riid, LPVOID *obj)
{
    ICOM_THIS(IDirectPlay8ThreadPoolImpl, iface);
    TRACE("(%p)->(%s, %p)\n", iface, debugstr_guid(riid), obj);
    if (IsEqualGUID(&IID_IUnknown, riid) ||
        IsEqualGUID(&IID_IDirectPlay8ThreadPool, riid))
    {
        This->ref++;
        *obj = This;
        return S_OK;
    }
    else if (IsEqualGUID(&IID_IDirectPlay8ThreadPool_Internal, riid))
    {
        This->ref++;
        *obj = (LPVOID *)&This->lpVtbl2;
        return S_OK;
    }
    else
    {
        FIXME("(%p)->(%s, %p): no interface\n", iface, debugstr_guid(riid), obj);
        return E_NOINTERFACE;
    }
}

ULONG WINAPI
DirectPlay8ThreadPool_AddRef(PDIRECTPLAY8THREADPOOL iface)
{
    ICOM_THIS(IDirectPlay8ThreadPoolImpl, iface);
    TRACE("(%p)->()\n", iface);
    return ++This->ref;
}

ULONG WINAPI
DirectPlay8ThreadPool_Release(PDIRECTPLAY8THREADPOOL iface)
{
    ICOM_THIS(IDirectPlay8ThreadPoolImpl, iface);
    TRACE("(%p)->()\n", iface);
    if (--This->ref) return This->ref;

    FIXME("destroy this and everything\n");
    ERR("we still have running threads, this will bugger them up. expect a crash\n");

    HeapFree(GetProcessHeap(), 0, This);
    return 0;
}

HRESULT WINAPI
DirectPlay8ThreadPool_Initialize(PDIRECTPLAY8THREADPOOL iface,
                                 PVOID const pvUserContext,
                                 const PFNDPNMESSAGEHANDLER pfn,
                                 const DWORD dwFlags)
{
    FIXME("(%p)->(%p, %p, %08lx): stub\n", iface, pvUserContext, pfn, dwFlags);
    return S_OK;
}

HRESULT WINAPI
DirectPlay8ThreadPool_Close(PDIRECTPLAY8THREADPOOL iface,
                            const DWORD dwFlags)
{
    FIXME("(%p)->(%08lx): stub\n", iface, dwFlags);
    return S_OK;
}

HRESULT WINAPI
DirectPlay8ThreadPool_GetThreadCount(PDIRECTPLAY8THREADPOOL iface,
                                     const DWORD dwProcessorNum,
                                     DWORD *const pdwNumThreads,
                                     const DWORD dwFlags)
{
    FIXME("(%p)->(%08li, %p, %08lx): stub (returning 3)\n",
            iface, dwProcessorNum, pdwNumThreads, dwFlags);
    *pdwNumThreads = 3;
    return S_OK;
}

HRESULT WINAPI
DirectPlay8ThreadPool_SetThreadCount(PDIRECTPLAY8THREADPOOL iface,
                                     const DWORD dwProcessorNum,
                                     const DWORD dwNumThreads,
                                     const DWORD dwFlags)
{
    FIXME("(%p)->(%08li, %08li, %08lx): stub\n",
            iface, dwProcessorNum, dwNumThreads, dwFlags);
    return S_OK;
}

HRESULT WINAPI
DirectPlay8ThreadPool_DoWork(PDIRECTPLAY8THREADPOOL iface,
                             const DWORD dwAllowedTimeSlice,
                             const DWORD dwFlags)
{
    FIXME("(%p)->(%08li, %08lx): stub (if they are using this expect badness, app may not be thread safe when we need it to be)\n",
            iface, dwAllowedTimeSlice, dwFlags);
    return S_OK;
}

static ICOM_VTABLE(IDirectPlay8ThreadPool) directPlay8ThreadPoolVT =
{
    ICOM_MSVTABLE_COMPAT_DummyRTTIVALUE
    DirectPlay8ThreadPool_QueryInterface,
    DirectPlay8ThreadPool_AddRef,
    DirectPlay8ThreadPool_Release,
    DirectPlay8ThreadPool_Initialize,
    DirectPlay8ThreadPool_Close,
    DirectPlay8ThreadPool_GetThreadCount,
    DirectPlay8ThreadPool_SetThreadCount,
    DirectPlay8ThreadPool_DoWork
};


