Share via


Sample TSP Source File

A version of this page is also available for

Windows Embedded CE 6.0 R3

4/8/2010

The following code example illustrates how to write a customized TSP by using the TSPI that is supported in Windows Embedded CE.

/*++
THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
PARTICULAR PURPOSE.
Copyright (c) 1995-2000 Microsoft Corporation.  All rights reserved.

Module Name:  

    Samptspi.c

Abstract:  

    Core functions for the Windows Embedded CE sample TAPI Service Provider

Notes:


--*/

#include "Windows.h"
#include "Types.h"
#include "Tchar.h"
#include "Memory.h"
#include "Mcx.h"
#include "Tspi.h"
#include "Linklist.h"
#include "Samptspi.h"
#include "Tapicomn.h"


TSPIGLOBALS TspiGlobals;

const GETIDINFO aGetID[] ={{TEXT("tapi/line"),     STRINGFORMAT_BINARY},
                           {TEXT("comm"),          STRINGFORMAT_UNICODE},
                           {TEXT("comm/datamodem"),STRINGFORMAT_BINARY},
                           {TEXT("ndis"),          STRINGFORMAT_BINARY}};

const TCHAR g_szDeviceClass[] = TEXT("com");

// Some generic strings for later
//
const TCHAR szSemicolon[] = TEXT(";");

DEVCFG DefaultDevCfg;

// Debug Zones.
#ifdef DEBUG

// Defines to ease setting of dpCurSettings.ulZoneMask
#define DEBUG_INIT         0x0001
#define DEBUG_CALLSTATE    0x0400
#define DEBUG_MISC         0x0800
#define DEBUG_ALLOC        0x1000
#define DEBUG_FUNCTION     0x2000
#define DEBUG_WARNING      0x4000
#define DEBUG_ERROR        0x8000

DBGPARAM dpCurSettings = {
    TEXT("Samptspi"), {
        TEXT("Init"),TEXT(""),TEXT(""),TEXT(""),
        TEXT(""),TEXT(""),TEXT(""),TEXT(""),
        TEXT(""),TEXT(""),TEXT("Call State"),TEXT("Misc"),
        TEXT("Alloc"),TEXT("Function"),TEXT("Warning"),TEXT("Error") },
    DEBUG_INIT
};
#endif

extern BOOL LineConfigEdit(HWND hParent, PDEVCFG pCfg);





// **********************************************************************
// TSPI_provider functions
// **********************************************************************

LONG TSPIAPI
TSPI_providerInit(
    DWORD             dwTSPIVersion,            // TSPI Version - in
    DWORD             dwPermanentProviderID,    // Permanent Provider ID - in
    DWORD             dwLineDeviceIDBase,       // Line Base ID - in
    DWORD             dwPhoneDeviceIDBase,      // Phone Base ID - in
    DWORD             dwNumLines,               // Number of lines - in
    DWORD             dwNumPhones,              // Number of phones - in
    ASYNC_COMPLETION  lpfnCompletionProc,       // Pointer to callback - in
    LPDWORD           lpdwTSPIOptions           // Optional Behavior Flags - out
    )
{
    DEBUGMSG(ZONE_FUNCTION|ZONE_INIT,
             (TEXT("+TSPI_providerInit, dwPPID 0x%X, dwDeviceIDBase 0x%X, dwNumLines 0x%X\r\n"),
              dwPermanentProviderID,
              dwLineDeviceIDBase,
              dwNumLines));
    
    TspiGlobals.fnCompletionCallback = lpfnCompletionProc;

    DefaultDevCfg.wWaitBong = 90;
    DefaultDevCfg.dwModemOptions = MDM_FLOWCONTROL_SOFT;
    DefaultDevCfg.dwCallSetupFailTimer = 180;

    DefaultDevCfg.commconfig.dwSize = sizeof(COMMCONFIG);
    DefaultDevCfg.commconfig.wVersion = SAMPTSPI_VERSION;
    DefaultDevCfg.commconfig.wReserved = 0;
    DefaultDevCfg.commconfig.dwProviderSubType = SAMPTSPI_SUBTYPE;
    DefaultDevCfg.commconfig.dwProviderOffset = 0;
    DefaultDevCfg.commconfig.dwProviderSize = 0;
    
    DefaultDevCfg.commconfig.dcb.DCBlength          = sizeof(DCB);
    DefaultDevCfg.commconfig.dcb.BaudRate           = CBR_19200;
    DefaultDevCfg.commconfig.dcb.fBinary            = TRUE;
    DefaultDevCfg.commconfig.dcb.fParity            = TRUE;
    DefaultDevCfg.commconfig.dcb.fOutxCtsFlow       = TRUE;
    DefaultDevCfg.commconfig.dcb.fOutxDsrFlow       = TRUE;
    DefaultDevCfg.commconfig.dcb.fDtrControl        = DTR_CONTROL_HANDSHAKE;
    DefaultDevCfg.commconfig.dcb.fDsrSensitivity    = TRUE;
    DefaultDevCfg.commconfig.dcb.fTXContinueOnXoff  = TRUE;
    DefaultDevCfg.commconfig.dcb.fOutX              = FALSE;
    DefaultDevCfg.commconfig.dcb.fInX               = FALSE;
    DefaultDevCfg.commconfig.dcb.fErrorChar         = FALSE;
    DefaultDevCfg.commconfig.dcb.fNull              = FALSE;
    DefaultDevCfg.commconfig.dcb.fRtsControl        = RTS_CONTROL_TOGGLE;
    DefaultDevCfg.commconfig.dcb.fAbortOnError      = TRUE;
    DefaultDevCfg.commconfig.dcb.wReserved          = 0;
    DefaultDevCfg.commconfig.dcb.XonLim             = 3;
    DefaultDevCfg.commconfig.dcb.XoffLim            = 12;
    DefaultDevCfg.commconfig.dcb.ByteSize           = 8;
    DefaultDevCfg.commconfig.dcb.Parity             = EVENPARITY;
    DefaultDevCfg.commconfig.dcb.StopBits           = ONESTOPBIT;
    DefaultDevCfg.commconfig.dcb.XonChar            = 0;
    DefaultDevCfg.commconfig.dcb.XoffChar           = 0;
    DefaultDevCfg.commconfig.dcb.ErrorChar          = 0;
    DefaultDevCfg.commconfig.dcb.EofChar            = 0;
    DefaultDevCfg.commconfig.dcb.EvtChar            = 0;
    DefaultDevCfg.commconfig.dcb.wReserved1         = 0;

    DEBUGMSG(ZONE_FUNCTION|ZONE_INIT,
             (TEXT("-TSPI_providerInit\r\n")));
    return SUCCESS;
}

LONG TSPIAPI
TSPI_providerInstall(
    HWND   hwndOwner,
    DWORD  dwPermanentProviderID
    )
{
    DEBUGMSG(ZONE_FUNCTION|ZONE_INIT,
             (TEXT("+TSPI_providerInstall, dwPPID 0x%X\r\n"),
              dwPermanentProviderID ));
    
    DEBUGMSG(ZONE_FUNCTION|ZONE_INIT,
             (TEXT("-TSPI_providerInstall\r\n")));

    return SUCCESS;
}

LONG TSPIAPI
TSPI_providerShutdown(
    DWORD    dwTSPIVersion
    )
{
    DEBUGMSG(ZONE_FUNCTION|ZONE_INIT,
             (TEXT("+TSPI_providerShutdown\r\n")));

    DEBUGMSG(ZONE_FUNCTION|ZONE_INIT,
             (TEXT("-TSPI_providerShutdown\r\n")));
    return SUCCESS;
}


LONG TSPIAPI TSPI_providerEnumDevices(DWORD dwPermanentProviderID,
                                      LPDWORD   lpdwNumLines,
                                      LPDWORD   lpdwNumPhones,
                                      HPROVIDER hProvider,
                                      LINEEVENT lpfnLineEventProc,
                                      PHONEEVENT lpfnPhoneEventProc
                                      )

{
    DEBUGMSG(ZONE_FUNCTION|ZONE_INIT,
             (TEXT("+TSPI_providerEnumDevices\r\n")));

    *lpdwNumLines = 0;

    // This should be the same event proc that gets passed in to
    // lineOpen, but I need it here and now so that I can notify
    // TAPI about devices that are coming and going.
    TspiGlobals.fnLineEventProc  = lpfnLineEventProc;

    TspiGlobals.dwProviderID     = dwPermanentProviderID;
    TspiGlobals.hProvider        = hProvider;
    
    DEBUGMSG(ZONE_FUNCTION|ZONE_INIT,
             (TEXT("-TSPI_providerEnumDevices\r\n")));
    return SUCCESS;
}

// **********************************************************************
// TSPI_line functions
// **********************************************************************

//
// This function serves as a stub in the vtbl for any of the TSPI
// functions that are chosen not to be supported.
//
LONG TSPIAPI
TSPI_Unsupported( void )
{
    DEBUGMSG(ZONE_FUNCTION,
             (TEXT("+TSPI_Unsupported\r\n")));

    DEBUGMSG(ZONE_FUNCTION,
             (TEXT("-TSPI_Unsupported\r\n")));
    return LINEERR_OPERATIONUNAVAIL;
}

LONG TSPIAPI
TSPI_lineClose(
    HDRVLINE hdLine
    )
{
    PTLINEDEV  pLineDev;
  
    DEBUGMSG(ZONE_FUNCTION,
             (TEXT("+TSPI_lineClose\r\n")));

    if ((pLineDev = GetLineDevfromHandle ((DWORD)hdLine)) == NULL)
        return LINEERR_OPERATIONFAILED;

     // Make sure that nothing is left open
    DevlineClose(pLineDev, TRUE);

    // Reinit the line device
    NullifyLineDevice(pLineDev);

    DEBUGMSG(ZONE_FUNCTION,
             (TEXT("-TSPI_lineClose\r\n")));
    return SUCCESS;
}

//
// Close a specified open line device
// 
LONG TSPIAPI
TSPI_lineConfigDialogEdit(
    DWORD dwDeviceID,
    HWND hwndOwner, 
    LPCWSTR lpszDeviceClass,
    LPVOID const lpDeviceConfigIn, 
    DWORD dwSize,
    LPVARSTRING lpDeviceConfigOut
    )
{
    PTLINEDEV pLineDev;
    BYTE cbSize;
    DWORD dwRet = SUCCESS;
    PDEVCFG pCfg;
    
    DEBUGMSG(ZONE_FUNCTION, (TEXT("+TSPI_lineConfigDialogEdit\r\n")));
    
    // Validate the input/output buffer
    //
    if (lpDeviceConfigOut == NULL)
        return LINEERR_INVALPOINTER;

    if (lpDeviceConfigIn == NULL)
        return LINEERR_INVALPOINTER;

    if (lpDeviceConfigOut->dwTotalSize < sizeof(VARSTRING))
        return LINEERR_STRUCTURETOOSMALL;

    // Validate the requested device class
    //
    if (lpszDeviceClass != NULL)
    {
        if (!ValidateDevCfgClass(lpszDeviceClass))
            return LINEERR_INVALDEVICECLASS;
    };

    // Validate the device ID
    //
    if ((pLineDev = GetLineDevfromID(dwDeviceID)) == NULL)
        return LINEERR_NODEVICE;

    // Set the output buffer size
    //
    cbSize  = sizeof( DEVCFG );
    lpDeviceConfigOut->dwUsedSize = sizeof(VARSTRING);
    lpDeviceConfigOut->dwNeededSize = sizeof(VARSTRING) + cbSize;

    if (dwSize < sizeof(DEVCFG)) {
        return LINEERR_INVALPARAM;
    }

    // Validate the output buffer size
    //
    if (lpDeviceConfigOut->dwTotalSize >= lpDeviceConfigOut->dwNeededSize)
    {
        // Initialize the buffer
        //
        lpDeviceConfigOut->dwStringFormat = STRINGFORMAT_BINARY;
        lpDeviceConfigOut->dwStringSize   = cbSize;
        lpDeviceConfigOut->dwStringOffset = sizeof(VARSTRING);
        lpDeviceConfigOut->dwUsedSize    += cbSize;
        pCfg = (PDEVCFG)(lpDeviceConfigOut+1);
        memcpy(pCfg, lpDeviceConfigIn, sizeof(DEVCFG));

        // Bring up property sheets for modems, and get the updated commconfig
        //
        LineConfigEdit(hwndOwner, pCfg);
    }
    else
    {
        DEBUGMSG(ZONE_FUNCTION,
                 (TEXT("Insufficient space in output buffer (passed %d, needed %d)\r\n"),
                  lpDeviceConfigOut->dwTotalSize, lpDeviceConfigOut->dwNeededSize));
    }


    DEBUGMSG(ZONE_FUNCTION,
             (TEXT("-TSPI_lineConfigDialogEdit x%X (Used %d, Need %d)\r\n"),
              dwRet, lpDeviceConfigOut->dwUsedSize, lpDeviceConfigOut->dwNeededSize));
    return dwRet;
}

//
// Terminate a call or abandon a call attempt that is in progress
// 
LONG TSPIAPI
TSPI_lineDrop(DRV_REQUESTID dwRequestID,
              HDRVCALL hdCall,
              LPCSTR lpsUserUserInfo,
              DWORD dwSize
    )
{
    PTLINEDEV pLineDev;
    DWORD    dwRet;
  
    DEBUGMSG(ZONE_FUNCTION|ZONE_CALLS,
             (TEXT("+TSPI_lineDrop\r\n")));

    if ((pLineDev = GetLineDevfromHandle ((DWORD)hdCall)) == NULL)
        return LINEERR_INVALCALLHANDLE;



    if (pLineDev->fTakeoverMode)
    {
        // The TAPI docs state that the app is required to close the handle after
        // calling line drop.
        pLineDev->fTakeoverMode = FALSE;
        pLineDev->DevState = DEVST_DISCONNECTED;

        NewCallState(pLineDev, LINECALLSTATE_IDLE, 0L);

        TspiGlobals.fnCompletionCallback(dwRequestID, 0L);
        DEBUGMSG(ZONE_FUNCTION|ZONE_CALLS,
                 (TEXT("-TSPI_lineDrop\r\n")));
        return dwRequestID;
    }
    else
    {
        // Disconnect the line. Grab the CloseCS
        // to ensure that the app does not try to drop a line
        // while the dialer thread is in the process of closing
        // the handle.
        EnterCriticalSection(&pLineDev->OpenCS);
        dwRet = DevlineDrop(pLineDev);
        LeaveCriticalSection(&pLineDev->OpenCS);
        
        if (dwRet == SUCCESS)
        {
            pLineDev->dwPendingID = INVALID_PENDINGID;
            pLineDev->dwPendingType = INVALID_PENDINGOP;
            DEBUGMSG(ZONE_FUNCTION|ZONE_CALLS,
                     (TEXT("-TSPI_lineDrop, Success\r\n")));
            return dwRet;
        }
        else
        {
            // Note that Windows Embedded CE does not do asynch drops right now.
            DEBUGMSG(ZONE_FUNCTION|ZONE_CALLS,
                     (TEXT("-TSPI_lineDrop, Asynchronous\r\n")));
            pLineDev->dwPendingID = dwRequestID;
            pLineDev->dwPendingType = PENDING_LINEDROP;
            return dwRequestID;
        }
    }    
}


static const WCHAR szProviderName[] = TEXT("SAMPTSPI");

//
// Determine telephony capabilites for the specified device.
// 
LONG TSPIAPI
TSPI_lineGetDevCaps(
    DWORD dwDeviceID,
    DWORD dwTSPIVersion,
    DWORD dwExtVersion,
    LPLINEDEVCAPS lpLineDevCaps
    )
{
    PTLINEDEV pLineDev;
    int  cbLineNameLen = 0;
    int cbDevSpecificLen = 0;
    int cbProviderNameLen = 0;
    int cbAvailMem = 0;
    DWORD dwRet = SUCCESS;
    
    DEBUGMSG(ZONE_FUNCTION,
             (TEXT("+TSPI_lineGetDevCaps\r\n")));

     // A device caps structure must be filled in that is specific to
     // the device.
    if ((pLineDev = GetLineDevfromID ((DWORD)dwDeviceID)) == NULL)
        return LINEERR_NODEVICE;

    // Check to see how much memory is needed.
    cbLineNameLen = (_tcslen(pLineDev->szFriendlyName) + 1) * sizeof(TCHAR);
    cbProviderNameLen = (wcslen(szProviderName) + 1) * sizeof(WCHAR);
    
    lpLineDevCaps->dwUsedSize = sizeof(LINEDEVCAPS);
    
    cbAvailMem = (int) (lpLineDevCaps->dwTotalSize - lpLineDevCaps->dwUsedSize);
  
    // Enter the size that is ideally needed.
    lpLineDevCaps->dwNeededSize = lpLineDevCaps->dwUsedSize +
        cbLineNameLen +         // room for linename
        cbProviderNameLen +     // room for provider name
        (2*sizeof(WORD));       // and room for DevSpecific info

    // On Windows Embedded CE, there is no VCOMM available for an app
    // to determine the device type (modem or DCC/NULL). So, a DevSpecific
    // field is added, which is a WORD indicating the device type.
    // DT_NULL, DT_PCMCIA_MODEM, and so on. This is followed by a second word that is
    // 1 if the device is ready/available, or 0 if the device is not currently
    // available (a removed PC card, for example).
    if (cbAvailMem >= sizeof(DWORD) )
    {
        *(LPWORD)((LPSTR)lpLineDevCaps + lpLineDevCaps->dwUsedSize) = pLineDev->wDeviceType;
        *((LPWORD)((LPSTR)lpLineDevCaps + lpLineDevCaps->dwUsedSize) + 1) = pLineDev->wDeviceAvail;
        
        DEBUGMSG(ZONE_FUNCTION,
                 (TEXT("Storing Device Type x%X, available x%X\r\n"),
                  pLineDev->wDeviceType,
                  pLineDev->wDeviceAvail));

        lpLineDevCaps->dwDevSpecificSize = sizeof( DWORD );
        lpLineDevCaps->dwDevSpecificOffset = lpLineDevCaps->dwUsedSize;
        lpLineDevCaps->dwUsedSize += lpLineDevCaps->dwDevSpecificSize;
        cbAvailMem -= lpLineDevCaps->dwDevSpecificSize;
    }
    else
    {
        lpLineDevCaps->dwDevSpecificSize = 0;
        lpLineDevCaps->dwDevSpecificOffset = 0;
        lpLineDevCaps->dwNeededSize += sizeof(WORD);
    }
    
     // If the provider info fits, also append the name
    if (cbAvailMem >= cbLineNameLen)
    {
        _tcscpy((LPWSTR)((LPSTR)lpLineDevCaps + lpLineDevCaps->dwUsedSize),
                pLineDev->szFriendlyName);
        lpLineDevCaps->dwLineNameSize = cbLineNameLen;
        lpLineDevCaps->dwLineNameOffset = lpLineDevCaps->dwUsedSize;
        lpLineDevCaps->dwUsedSize += cbLineNameLen;
        cbAvailMem -= cbLineNameLen;
    }
    else
    {
        lpLineDevCaps->dwLineNameSize = 0;
        lpLineDevCaps->dwLineNameOffset = 0;
    }
    

    if (cbAvailMem >= cbProviderNameLen)
    {
        _tcscpy((LPWSTR)((LPSTR)lpLineDevCaps + lpLineDevCaps->dwUsedSize), szProviderName);
        lpLineDevCaps->dwProviderInfoSize = cbProviderNameLen;
        lpLineDevCaps->dwProviderInfoOffset = lpLineDevCaps->dwUsedSize;
        lpLineDevCaps->dwUsedSize += cbProviderNameLen;
        cbAvailMem -= cbProviderNameLen;
    }
    else
    {
        lpLineDevCaps->dwProviderInfoSize = 0;
        lpLineDevCaps->dwProviderInfoOffset = 0;
    }

     // You do not have permanent IDs.
    lpLineDevCaps->dwPermanentLineID = 0;
    lpLineDevCaps->dwStringFormat = STRINGFORMAT_UNICODE;
    lpLineDevCaps->dwAddressModes = LINEADDRESSMODE_ADDRESSID;
    lpLineDevCaps->dwNumAddresses = 1;

     // Bearer mode & information
    lpLineDevCaps->dwMaxRate      = pLineDev->dwMaxDCERate;
    lpLineDevCaps->dwBearerModes  = pLineDev->dwBearerModes;

     // Media mode
    lpLineDevCaps->dwMediaModes = pLineDev->dwMediaModes;

    // Wait-for-bong can be simulated if the modem is not capable of
    // supporting it.
    lpLineDevCaps->dwDevCapFlags  = pLineDev->dwDevCapFlags |
        LINEDEVCAPFLAGS_DIALBILLING |
        LINEDEVCAPFLAGS_CLOSEDROP;

    lpLineDevCaps->dwRingModes         = 1;
    lpLineDevCaps->dwMaxNumActiveCalls = 1;

     // Line device state to be notified
    lpLineDevCaps->dwLineStates = LINEDEVSTATE_CONNECTED |
        LINEDEVSTATE_DISCONNECTED |
        LINEDEVSTATE_OPEN |
        LINEDEVSTATE_CLOSE |
        LINEDEVSTATE_INSERVICE |
        LINEDEVSTATE_OUTOFSERVICE |
        LINEDEVSTATE_REMOVED |
        LINEDEVSTATE_RINGING |
        LINEDEVSTATE_REINIT;

    DEBUGMSG(ZONE_FUNCTION,
             (TEXT("-TSPI_lineGetDevCaps x%X\r\n"),
              dwRet));
    return dwRet;
}

//
// Determine device configuration for the specified device.
// 
LONG TSPIAPI
TSPI_lineGetDevConfig(
    DWORD dwDeviceID,
    LPVARSTRING lpDeviceConfig,
    LPCWSTR lpszDeviceClass
    )
{
    PTLINEDEV pLineDev;
    DWORD dwRet = SUCCESS;
    BYTE  cbSize;
    PDEVCFG pDevCfg;
    
    DEBUGMSG(ZONE_FUNCTION,
             (TEXT("+TSPI_lineGetDevConfig\r\n")));

    // Validate the requested device class
    //
    if (lpszDeviceClass != NULL)
    {
        if (!ValidateDevCfgClass(lpszDeviceClass))
            return LINEERR_INVALDEVICECLASS;
    }
    
    // Validate the buffer
    //
    if (lpDeviceConfig == NULL)
        return LINEERR_INVALPOINTER;

    if (lpDeviceConfig->dwTotalSize < sizeof(VARSTRING))
        return LINEERR_STRUCTURETOOSMALL;

    // Validate the device ID
    //
    if ((pLineDev = GetLineDevfromID (dwDeviceID)) == NULL)
        return LINEERR_NODEVICE;
    
    // Validate the buffer size
    //
    cbSize = sizeof(DEVCFG);
    lpDeviceConfig->dwUsedSize = sizeof(VARSTRING);
    lpDeviceConfig->dwNeededSize = sizeof(VARSTRING) + cbSize;

    if (lpDeviceConfig->dwTotalSize >= lpDeviceConfig->dwNeededSize)
    {
        // The Windows Embedded CE Networking and Dial-up Connections application does not store the DEVCFG
        // that is associated with a connectoid until the user changes the settings.
        // This means that it can end up using settings from a previous connection,
        // instead of the defaults that it expects.
        pDevCfg = (PDEVCFG)(((LPBYTE)lpDeviceConfig) + sizeof(VARSTRING));
        *pDevCfg = pLineDev->DevCfg;
        
        lpDeviceConfig->dwStringFormat = STRINGFORMAT_BINARY;
        lpDeviceConfig->dwStringSize = cbSize;
        lpDeviceConfig->dwStringOffset = sizeof(VARSTRING);
        lpDeviceConfig->dwUsedSize += cbSize;

        DEBUGMSG(ZONE_FUNCTION|ZONE_CALLS,
                 (TEXT("  TSPI_lineGetDevConfig (DevID %d):\r\n"),
                  dwDeviceID));
    }
    else
    {
        // Not enough room
        DEBUGMSG(ZONE_FUNCTION,
                 (TEXT("  TSPI_lineGetDevConfig needed %d bytes, had %d\r\n"),
                  lpDeviceConfig->dwNeededSize, lpDeviceConfig->dwTotalSize));
    };

    DEBUGMSG(ZONE_FUNCTION,
             (TEXT("-TSPI_lineGetDevConfig x%X (Used %d, Need %d)\r\n"),
              dwRet, lpDeviceConfig->dwUsedSize, lpDeviceConfig->dwNeededSize));
    return dwRet;
}

//
// Get the device ID for the specified device. The caller
// can use the returned ID with the corresponding media (for example, for
// serial lines the ID can be passed to ReadFile(), WriteFile(),
// and so on).
// 
LONG TSPIAPI
TSPI_lineGetID(
    HDRVLINE       hdLine,
    DWORD          dwAddressID,
    HDRVCALL       hdCall,
    DWORD          dwSelect,
    LPVARSTRING    lpDeviceID,
    LPCWSTR        lpszDeviceClass
    )
{
    PTLINEDEV   pLineDev;
    UINT       cbPort;
    UINT       idClass;

    DEBUGMSG(ZONE_FUNCTION,
             (TEXT("+TSPI_lineGetID - ProcPerm=0x%X\r\n"),
           GetCurrentPermissions()));

    switch (dwSelect)
    {
        case LINECALLSELECT_ADDRESS:
            if (dwAddressID != 0)
            {
                DEBUGMSG(ZONE_FUNCTION,
                         (TEXT("-TSPI_lineGetID - INVALADDRESSID\r\n")));
                return LINEERR_INVALADDRESSID;
            }
             // FALLTHROUGH

        case LINECALLSELECT_LINE:
            if ((pLineDev = GetLineDevfromHandle ((DWORD)hdLine)) == NULL)
            {
                DEBUGMSG(ZONE_FUNCTION,
                         (TEXT("-TSPI_lineGetID - INVALLINEHANDLE\r\n")));
                return LINEERR_INVALLINEHANDLE;
            }
            break;

        case LINECALLSELECT_CALL:
            if ((pLineDev = GetLineDevfromHandle ((DWORD)hdCall)) == NULL)
            {
                DEBUGMSG(ZONE_FUNCTION,
                         (TEXT("-TSPI_lineGetID - INVALCALLHANDLE\r\n")));
                return LINEERR_INVALCALLHANDLE;
            }
            break;

        default:
            DEBUGMSG(ZONE_FUNCTION,
                     (TEXT("-TSPI_lineGetID - Invalid dwSelect x%X\r\n"),
                         dwSelect));
            return LINEERR_OPERATIONFAILED;
    }


     // Determine the device class
     //
    for (idClass = 0; idClass < MAX_SUPPORT_CLASS; idClass++)
    {
        
        DEBUGMSG(ZONE_FUNCTION,
                 (TEXT("Comparing strings %s to %s\r\n"),
                  lpszDeviceClass, aGetID[idClass].szClassName));
        if (_tcsicmp(lpszDeviceClass, aGetID[idClass].szClassName) == 0)
            break;
    };
   DEBUGMSG(ZONE_FUNCTION,
          (TEXT("Class ID = %d (%s)\r\n"), idClass,
           aGetID[idClass].szClassName));
    
     // Calculate the required size
     //
    switch (idClass)
    {
        case TAPILINE:
            cbPort = sizeof(DWORD);
            break;

        case COMM:
            cbPort = (_tcslen(pLineDev->szFriendlyName) + 1) * sizeof(TCHAR);
            break;

        case COMMMODEM:
            cbPort = (_tcslen(pLineDev->szFriendlyName) + 1) * sizeof(TCHAR) + sizeof(DWORD);
            break;

        default:
            DEBUGMSG(ZONE_FUNCTION,
                     (TEXT("-TSPI_lineGetID - Invalid ID Class x%X\r\n"),
                      idClass ));
            return LINEERR_OPERATIONFAILED;
    };

     // Calculate the required size
     //
    lpDeviceID->dwNeededSize = sizeof(VARSTRING) + cbPort;
    lpDeviceID->dwStringFormat = aGetID[idClass].dwFormat;
    ASSERT(lpDeviceID->dwUsedSize == sizeof(VARSTRING));
    if ((lpDeviceID->dwTotalSize - lpDeviceID->dwUsedSize) <
        cbPort)
    {
        DEBUGMSG(ZONE_FUNCTION,
             (TEXT("-TSPI_lineGetID - Inusufficient space\r\n")));
        return SUCCESS;
    }
    
     // Enough space exists to return valid information
     //
    lpDeviceID->dwStringSize   = cbPort;
    lpDeviceID->dwStringOffset = sizeof(VARSTRING);
    lpDeviceID->dwUsedSize    += cbPort;


     // Return the valid information
    switch (idClass)
    {
        case TAPILINE:
        {
            LPDWORD lpdwDeviceID;

            lpdwDeviceID = (LPDWORD)(((LPBYTE)lpDeviceID) + sizeof(VARSTRING));
            *lpdwDeviceID = (DWORD) pLineDev->dwDeviceID;
            DEBUGMSG(ZONE_MISC,
                     (TEXT("-TSPI_lineGetID TAPILINE - Device ID x%X\r\n"),
                      pLineDev->dwDeviceID));
            break;
        }
        case COMM:
        {
            _tcsncpy( (LPWSTR)((LPBYTE)lpDeviceID + sizeof(VARSTRING)),
                      pLineDev->szFriendlyName, (cbPort/sizeof(TCHAR)) );
            DEBUGMSG(ZONE_MISC,
                     (TEXT("-TSPI_lineGetID COMM - Device name \"%s\" (len %d)\r\n"),
                      pLineDev->szFriendlyName, (cbPort/sizeof(TCHAR))));
            break;
        }
        case COMMMODEM:
        {
            LPDWORD lpdwDeviceHandle;

            lpdwDeviceHandle = (LPDWORD)(((LPBYTE)lpDeviceID) + sizeof(VARSTRING));
            if (pLineDev->hDevice != (HANDLE)INVALID_DEVICE)
            {
                *lpdwDeviceHandle = (DWORD)pLineDev->hDevice;
            SetHandleOwner((HANDLE)*lpdwDeviceHandle, GetCallerProcess());
            }
            else
            {
                *lpdwDeviceHandle = (DWORD)NULL;
            };
            _tcscpy((LPWSTR)(lpdwDeviceHandle+1), pLineDev->szFriendlyName );
            DEBUGMSG(ZONE_MISC,
                     (TEXT("-TSPI_lineGetID COMMMODEM - Device Handle x%X, Device name %s\r\n"),
                      *lpdwDeviceHandle, pLineDev->szFriendlyName));
            break;
        }
    };

    DEBUGMSG(ZONE_FUNCTION,
             (TEXT("-TSPI_lineGetID\r\n")));
    
    return SUCCESS;
}

LONG TSPIAPI
TSPI_lineMakeCall(
    DRV_REQUESTID          dwRequestID,
    HDRVLINE               hdLine,
    HTAPICALL              htCall,
    LPHDRVCALL             lphdCall,
    LPCWSTR                lpszDestAddress,
    DWORD                  dwCountryCode,
    LPLINECALLPARAMS const lpCallParams
    )
{
    PTLINEDEV pLineDev;
    DWORD dwRet;
    BOOL  fDoTakeover = FALSE;

    DEBUGMSG(ZONE_FUNCTION|ZONE_CALLS, (TEXT("+TSPI_lineMakeCall\r\n")));

    if (lpszDestAddress) {
        DEBUGMSG(ZONE_FUNCTION|ZONE_CALLS,
            (TEXT("+TSPI_lineMakeCall(%s)\r\n"), lpszDestAddress));
    }

    if ((pLineDev = GetLineDevfromHandle ((DWORD)hdLine)) == NULL)
    {
        DEBUGMSG(ZONE_FUNCTION|ZONE_ERROR|ZONE_CALLS,
                 (TEXT("-TSPI_lineMakeCall ** Invalid Handle\r\n")));
        return LINEERR_INVALLINEHANDLE;
    }
    
     // See if there is a free call structure.
    if (pLineDev->dwCall & CALL_ALLOCATED)
    {
        DEBUGMSG(ZONE_FUNCTION|ZONE_ERROR|ZONE_CALLS,
                 (TEXT("-TSPI_lineMakeCall ** Call already allocated\r\n")));
        return LINEERR_CALLUNAVAIL;
    }    

    // By default, do not perform blind dialing.
    pLineDev->dwDialOptions &= ~MDM_BLIND_DIAL;


     // Examine LINECALLPARAMS, if it is present.
    if (lpCallParams)
    {
        DEBUGMSG(ZONE_FUNCTION|ZONE_ERROR|ZONE_CALLS,
                 (TEXT("   lineMakeCall - Check CallParams\r\n")));
        
        if (lpCallParams->dwBearerMode & LINEBEARERMODE_PASSTHROUGH)
        {
            fDoTakeover = TRUE;
        }
        else
        {
             // This check is to prevent G3FAX from being used without pass-through...
             // (You can dial only with DATAMODEM)
            if ((lpCallParams->dwMediaMode &
                 (LINEMEDIAMODE_DATAMODEM)) == 0)
            {
                DEBUGMSG(ZONE_FUNCTION|ZONE_ERROR|ZONE_CALLS,
                         (TEXT("-TSPI_lineMakeCall ** Invalid Media Mode\r\n")));
                return LINEERR_INVALMEDIAMODE;
            }
        }

        pLineDev->dwCurBearerModes = lpCallParams->dwBearerMode;
        pLineDev->dwCurMediaModes = lpCallParams->dwMediaMode;

        DEBUGMSG(ZONE_MISC,
                 (TEXT("   lineMakeCall - got media & bearer modes\r\n")));

        if (!(lpCallParams->dwCallParamFlags & LINECALLPARAMFLAGS_IDLE))
        {
            DEBUGMSG(ZONE_FUNCTION|ZONE_CALLS,
             (TEXT("LINECALLPARAMFLAGS_IDLE: not set, no dialtone detect\r\n") ));
             // Turn on blind dialing
            pLineDev->dwDialOptions |= MDM_BLIND_DIAL;
        }


    }
    else
    {
         // set the standard defaults - for peg tapi, it is DATAMODEM
        ASSERT(pLineDev->dwMediaModes & LINEMEDIAMODE_DATAMODEM);
        pLineDev->dwCurMediaModes = LINEMEDIAMODE_DATAMODEM;

        pLineDev->dwCurBearerModes = pLineDev->dwBearerModes & ~LINEBEARERMODE_PASSTHROUGH;
    }

     // Is there a telephone number?
     //
    if (!fDoTakeover)
    {
         // Validate lpszDestAddress, and get the processed form of it.
        DEBUGMSG(ZONE_MISC|ZONE_CALLS,
                 (TEXT("TSPI_lineMakeCall - validating destination address\r\n")));
        dwRet = ValidateAddress(pLineDev, lpszDestAddress, pLineDev->szAddress);
        if (SUCCESS != dwRet)
        {
            DEBUGMSG(ZONE_FUNCTION|ZONE_ERROR|ZONE_CALLS,
                     (TEXT("-TSPI_lineMakeCall ** Invalid Address\r\n")));
            return dwRet;
        }

         // if the lpszDestAddress was NULL or "", you just want to perform a
         // dial-tone detection. It is expected that lineDial will be called.
         // Setting the szAddress to ";" will accomplish this.
        if ('\0' == pLineDev->szAddress[0])
        {
            _tcscpy(pLineDev->szAddress, szSemicolon);
        }
    }

     // Record the call attributes
    pLineDev->htCall = htCall;
    pLineDev->dwCall = CALL_ALLOCATED;
    pLineDev->dwCallState = LINECALLSTATE_UNKNOWN;

    *lphdCall = (HDRVCALL)pLineDev;

     // It is allowed to make a call to an already opened line if the line is monitoring
     // a call. Therefore, if the line is in use, try making a call. The make-call
     // routine will return error if the state is not appropriate.
     //
    if (((dwRet = DevlineOpen(pLineDev)) == SUCCESS) ||
        (dwRet == LINEERR_ALLOCATED))
    {
        if (fDoTakeover)
        {
            DEBUGMSG(ZONE_MISC|ZONE_CALLS,
                     (TEXT("   lineMakeCall - Takeover\r\n")));
            
            // For takeover, no calling is actually done.  Just open the
            // port, so that the application can get the device handle from
            // lineGetID.
            if (pLineDev->DevState == DEVST_DISCONNECTED)
            {
                // You can go into pass-through only if device is not in use

                // OK, the device was opened above, so now you just need to
                // let the apps know that the call state has changed.
                pLineDev->DevState = DEVST_CONNECTED;
                TspiGlobals.fnCompletionCallback(dwRequestID, 0L);
                NewCallState(pLineDev, LINECALLSTATE_CONNECTED, 0L);

                pLineDev->fTakeoverMode = TRUE;
                dwRet = dwRequestID;
            }
            else
            {
                dwRet = LINEERR_OPERATIONFAILED;
            }
        }
        else
        {
             // The call can be made here
            pLineDev->dwPendingID = dwRequestID;
            pLineDev->dwPendingType = PENDING_LINEMAKECALL;

            DEBUGMSG(ZONE_CALLS,
                     (TEXT("\tTSPI_lineMakeCall, Ready to make call for ReqID x%X\r\n"),
                      pLineDev->dwPendingID));

            if (((dwRet = DevlineMakeCall(pLineDev)) != SUCCESS) &&
                (IS_TAPI_ERROR(dwRet)))
            {
                DEBUGMSG(ZONE_CALLS | ZONE_ERROR,
                         (TEXT("\tDevLineMakeCall error - dwRet x%X. Closing port\r\n"),
                          dwRet));
                DevlineClose(pLineDev, TRUE);
            }
        }
    };

     // Check if an error occurs
     //
    if (IS_TAPI_ERROR(dwRet))
    {
        DEBUGMSG(ZONE_CALLS,
                 (TEXT("\tTSPI_lineMakeCall, Ret Code x%X, invalidating pending ID.\r\n"),
                  dwRet ));
        
        pLineDev->dwPendingID   = INVALID_PENDINGID;
        pLineDev->dwPendingType = INVALID_PENDINGOP;

         // Deallocate the call from this line
        pLineDev->htCall = NULL;
        pLineDev->dwCall = 0;
        *lphdCall = NULL;
    };

    DEBUGMSG(ZONE_FUNCTION|ZONE_CALLS,
             (TEXT("-TSPI_lineMakeCall, dwRet x%X\r\n"),
              dwRet));
    return dwRet;
    
}

LONG TSPIAPI
TSPI_lineNegotiateTSPIVersion(
    DWORD dwDeviceID,
    DWORD dwLowVersion,
    DWORD dwHighVersion,
    LPDWORD lpdwTSPIVersion
    )
{
    DEBUGMSG(ZONE_FUNCTION,
             (TEXT("+TSPI_lineNegotiateTSPIVersion\r\n")));
    

     // Check the range of the device ID
     //
    if((dwDeviceID == INITIALIZE_NEGOTIATION)
       || (GetLineDevfromID(dwDeviceID) != NULL)
        )
    {
         // Check the version range
         //
        if((dwLowVersion > SPI_VERSION) || (dwHighVersion < SPI_VERSION))
        {
            *lpdwTSPIVersion = 0;
            DEBUGMSG(ZONE_FUNCTION,
                     (TEXT("-TSPI_lineNegotiateTSPIVersion - SPI Ver x%X out of TAPI range x%X..x%X\r\n"),
                      SPI_VERSION, dwLowVersion, dwHighVersion));
            return LINEERR_INCOMPATIBLEAPIVERSION;
        }
        else
        {
            *lpdwTSPIVersion = SPI_VERSION;
            DEBUGMSG(ZONE_FUNCTION,
             (TEXT("-TSPI_lineNegotiateTSPIVersion - Ver x%X\r\n"),
              SPI_VERSION));
            return SUCCESS;
        };
    };

    DEBUGMSG(ZONE_FUNCTION,
             (TEXT("-TSPI_lineNegotiateTSPIVersion - No Device\r\n")));

     // The requested device does not exist.
    return LINEERR_NODEVICE;
}

LONG TSPIAPI
TSPI_lineOpen(
    DWORD dwDeviceID,
    HTAPILINE htLine,
    LPHDRVLINE lphdLine,
    DWORD dwTSPIVersion,
    LINEEVENT lineEventProc)
{
    PTLINEDEV pLineDev;
    
    DEBUGMSG(ZONE_FUNCTION,
             (TEXT("+TSPI_lineOpen DevID x%X, HTapiLine x%X, SPI Ver x%X\r\n"),
              dwDeviceID, htLine, dwTSPIVersion));


     // Validate the device ID
    if ((pLineDev = GetLineDevfromID(dwDeviceID)) == NULL)
    {
        DEBUGMSG(ZONE_ERROR,
                 (TEXT("TSPI_lineOpen, could not find device for dwId x%X\r\n"),
                  dwDeviceID));
        return LINEERR_NODEVICE;
    }

     // Update the line device
    *lphdLine           = (HDRVLINE)pLineDev;
    pLineDev->lpfnEvent = lineEventProc;
    pLineDev->htLine    = htLine;

    DEBUGMSG(ZONE_FUNCTION,
             (TEXT("-TSPI_lineOpen\r\n")));
    return SUCCESS;
}

LONG TSPIAPI
TSPI_lineSetDevConfig(
    DWORD dwDeviceID,
    LPVOID const lpDeviceConfig,
    DWORD dwSize,
    LPCWSTR lpszDeviceClass
    )
{
    PTLINEDEV    pLineDev;
    PDEVCFG  pDevCfg;
    
    DEBUGMSG(ZONE_FUNCTION,
             (TEXT("+TSPI_lineSetDevConfig\r\n")));

     // Validate the requested device class
     //
    if (!ValidateDevCfgClass(lpszDeviceClass))
    {
        DEBUGMSG(ZONE_FUNCTION|ZONE_ERROR,
                 (TEXT("-TSPI_lineSetDevConfig : LINEERR_INVALDEVICECLASS\r\n")));
        return LINEERR_INVALDEVICECLASS;
    }
    
     // Validate the buffer
     //
    if (lpDeviceConfig == NULL)
    {
        DEBUGMSG(ZONE_FUNCTION|ZONE_ERROR,
                 (TEXT("-TSPI_lineSetDevConfig : LINEERR_INVALPOINTER\r\n")));
        return LINEERR_INVALPOINTER;
    }
    
     // Validate the device ID
     //
    if ((pLineDev = GetLineDevfromID(dwDeviceID)) == NULL)
    {
        DEBUGMSG(ZONE_FUNCTION|ZONE_ERROR,
                 (TEXT("-TSPI_lineSetDevConfig : LINEERR_NODEVICE\r\n")));
        return LINEERR_NODEVICE;
    }
    
     // Verify the structure size
     //
    pDevCfg = (PDEVCFG)lpDeviceConfig;
    if (dwSize != sizeof(DEVCFG))
    {
        DEBUGMSG(ZONE_FUNCTION|ZONE_ERROR,
                 (TEXT("-TSPI_lineSetDevConfig : LINEERR_INVALPARAM\r\n")));
        return LINEERR_INVALPARAM;
    }
    
     // Get the new settings
    pLineDev->DevCfg = *pDevCfg;
    
    DEBUGMSG(ZONE_FUNCTION,
             (TEXT("-TSPI_lineSetDevConfig\r\n")));
        
    return SUCCESS;
}

LONG TSPIAPI
TSPI_lineSetStatusMessages(
    HDRVLINE hdLine,
    DWORD dwLineStates,
    DWORD dwAddressStates
    )
{
    DEBUGMSG(ZONE_FUNCTION,
             (TEXT("+TSPI_lineSetStatusMessages\r\n")));

     // Record these settings and filter the notifications, based
     // on these settings.
    
    DEBUGMSG(ZONE_FUNCTION,
             (TEXT("-TSPI_lineSetStatusMessages\r\n")));
    return SUCCESS;
}

//
// LONG TSPIAPI TSPI_providerCreateLineDevice()
//
// Dynamically creates a new device. This entry point will be
// called by devloader whenever it adds a new device that lists
// Samptspi.dll as the service provider.  
//
LONG TSPIAPI
TSPI_providerCreateLineDevice(
    HKEY    hActiveKey,    // Registry key for this active device
    LPCWSTR lpszDevPath,   // Registry path for this device
    LPCWSTR lpszDeviceName // Device name
    )
{
    PTLINEDEV ptLineDev;
    DWORD   dwRet;
    
    DEBUGMSG(ZONE_FUNCTION,
             (TEXT("+TSPI_providerCreateLineDevice %s.\r\n"),
              lpszDeviceName));

    dwRet = (DWORD) -1;     // Assume failure

    // It is possible that this device already exists (for example,
    // if it is a PC card that was removed and reinserted.)
    // So, scan the current device list to look for it, and use
    // the existing entry if it is found. Otherwise, create a device.
    ptLineDev = createLineDev( hActiveKey, lpszDevPath, lpszDeviceName );
    
    if( NULL != ptLineDev )
    {
        dwRet = ptLineDev->dwDeviceID;
    }
    
    DEBUGMSG(ZONE_FUNCTION,
             (TEXT("-TSPI_providerCreateLineDevice, return x%X.\r\n"),
              dwRet));
    return dwRet;
}

//
// LONG TSPIAPI TSPI_providerDeleteLineDevice()
//
// Removes a device from the system.
//
// NOTE: Devload actually does not know which devices were added,
// so it is possible that this device is not one for which
// a lineDevice entry was created. Note that a device is never removed.
// It is only marked as disconnected. It might get
// reintroduced to the system later.
//
LONG TSPIAPI TSPI_providerDeleteLineDevice(
    DWORD Identifier    // dwDeviceID associated with this device
    )
{
    DWORD   dwRet;
    PTLINEDEV ptLineDev;
    
    DEBUGMSG(ZONE_FUNCTION|ZONE_INIT,
             (TEXT("+TSPI_providerDeleteLineDevice, x%X.\r\n"),
              Identifier));

     // Get the name of the device, and check its validity
    if ( (DWORD)-1 == Identifier )
        return LINEERR_BADDEVICEID;
     
    dwRet = LINEERR_OPERATIONFAILED;     // Assume failure

     // See if such a device is actually in the system
    if( ptLineDev = GetLineDevfromID(Identifier) )
    {
        // OK, send a DEVSTATE_DISCONNECTED to TAPI.
        DEBUGMSG(ZONE_FUNCTION|ZONE_INIT,
                 (TEXT("\tSend disconnect for ptLine x%X, htLine x%X\r\n"),
                  ptLineDev, ptLineDev->htLine));
        
        TspiGlobals.fnLineEventProc( ptLineDev->htLine,
                                     0,
                                     LINE_LINEDEVSTATE,
                                     LINEDEVSTATE_DISCONNECTED,
                                     0,
                                     0 );
        
        // And this Settings key is no longer valid.
        RegCloseKey( ptLineDev->hSettingsKey );

        // And mark this as disconnected, so that GetDevCaps can return this info.
        ptLineDev->wDeviceAvail = 0;
        
    }
    else
    {
        // Could not find the specified device
        DEBUGMSG(ZONE_FUNCTION|ZONE_INIT,
                 (TEXT("-TSPI_providerDeleteLineDevice, invalid device id x%X.\r\n"),
                  Identifier));
    }
    
    DEBUGMSG(ZONE_FUNCTION|ZONE_INIT,
             (TEXT("-TSPI_providerDeleteLineDevice.\r\n")));
    return dwRet;
}

BOOL
DllEntry (HANDLE  hinstDLL,
        DWORD   Op,
        LPVOID  lpvReserved)
{
   switch (Op) {
        case DLL_PROCESS_ATTACH :
            DEBUGREGISTER(hinstDLL);
            DEBUGMSG (ZONE_FUNCTION, (TEXT("TSPI:DllEntry(ProcessAttach)\r\n")));

             // TSPIGlobals, and so forth.
            TSPIDLL_Load( );
            TspiGlobals.hInstance = hinstDLL;   // Instance handle needed to
                                                // load dialog resources
            break;
            
        case DLL_PROCESS_DETACH :
        case DLL_THREAD_DETACH :
        case DLL_THREAD_ATTACH :
        default :
            break;
   }
   return TRUE;
}


// **********************************************************************
// Now a vtbl must be provided that can be used to access the functions.
// **********************************************************************

TSPI_PROCS tspi_procs;

LONG TSPIAPI TSPI_lineGetProcTable(
    LPTSPI_PROCS *lplpTspiProcs
    )
{
    PDWORD pdw;
    LONG i;

    DEBUGMSG(ZONE_FUNCTION,
             (TEXT("+TSPI_providerGetVtbl, ptr = x%X\r\n"),
              &tspi_procs));

    if (tspi_procs.TSPI_lineClose != TSPI_lineClose) {

    for (pdw = (PDWORD)&tspi_procs, i = 0;
         i < sizeof(TSPI_PROCS)/sizeof(DWORD);
         pdw++,i++) {
         *pdw = (DWORD)TSPI_Unsupported;
    }

    tspi_procs.TSPI_lineClose = TSPI_lineClose;
    tspi_procs.TSPI_lineDrop = TSPI_lineDrop;
    tspi_procs.TSPI_lineGetDevCaps = TSPI_lineGetDevCaps;
    tspi_procs.TSPI_lineGetDevConfig = TSPI_lineGetDevConfig;
    tspi_procs.TSPI_lineGetID = TSPI_lineGetID;
    tspi_procs.TSPI_lineMakeCall = TSPI_lineMakeCall;
    tspi_procs.TSPI_lineNegotiateTSPIVersion = TSPI_lineNegotiateTSPIVersion;
    tspi_procs.TSPI_lineOpen = TSPI_lineOpen;
    tspi_procs.TSPI_lineSetDevConfig = TSPI_lineSetDevConfig;
    tspi_procs.TSPI_lineSetStatusMessages = TSPI_lineSetStatusMessages;
    tspi_procs.TSPI_providerInit = TSPI_providerInit;
    tspi_procs.TSPI_providerInstall = TSPI_providerInstall;
    tspi_procs.TSPI_providerShutdown = TSPI_providerShutdown;
    tspi_procs.TSPI_providerEnumDevices = TSPI_providerEnumDevices;
    tspi_procs.TSPI_providerCreateLineDevice = TSPI_providerCreateLineDevice;
    tspi_procs.TSPI_providerDeleteLineDevice = TSPI_providerDeleteLineDevice;
    tspi_procs.TSPI_lineConfigDialogEdit = TSPI_lineConfigDialogEdit;
    }
    *lplpTspiProcs = &tspi_procs;
    
    DEBUGMSG(ZONE_FUNCTION,
             (TEXT("-TSPI_providerGetVtbl, ptr = x%X\r\n"),
              &tspi_procs));
    return SUCCESS;
}  

See Also

Concepts

Sample TSP
Sample TSP Header File