Telephony Service Provider Unimodem Sample (Compact 2013)

4/26/2013

Windows Embedded Compact provides Unimodem as a sample TSP. Unimodem provides access to most standard modems. You can also create a customized TSP by using the TSPI.

A service provider driver is a separate dynamic-link library (DLL) and not part of TAPI. The Unimodem driver translates TAPI function calls into AT-based modem commands to configure and dial modems. TAPI manages the use of devices, ports, and call traffic by the application.

Unimodem supports Hayes-compatible AT-command-based modems. To implement other media types, such as ISDN, you must create a customized TSP.

Unimodem also provides direct cable connect (DCC) support. DCC lets you establish a direct serial cable connection between two computers to share the resources of the computer that is designated as the host. If the host is connected to a network, the guest computer also can access the network.

You set the registry keys for a specific modem, and Unimodem reads the registry keys and creates a line device for that modem.

For Unimodem, the memory region that is described by the dwDevSpecificOffset and dwDevSpecificSize members of the LINEGETDEVCAPS structure is a UNIMODEM_INFO structure, as defined in Unimodem.h. Also, the memory region that is described by the dwProviderInfoOffset and dwProviderInfoSize members contains the plaintext Unicode string UNIMODEM.

Note

The Windows Embedded Compact implementation of Unimodem does not support voice modems, but you can create a customized TSP that supports them.

Note

The Windows Embedded Compact implementation of Unimodem supports hardware flow control modems only. Unimodem traces the carrier detect (CD) signal to determine if a connection exists. For software flow control modems, Unimodem is unable to detect a line drop, so the connection must be detected and dropped by an upper-layer application, such as the Point-to-Point Protocol (PPP).

Editing the Device Configuration

Unimodem now supports a programmatic mechanism for editing the device configuration. Previously, the only way to edit a Unimodem device configuration block was by using the lineConfigDialogEdit function that requires user interaction. Now you can edit a Unimodem device configuration by using the lineDevSpecific function extensions, which require no user interaction. The details of this interface are defined in Unimodem.h.

The following sample application demonstrates how to use this interface.

//
// Program to demonstrate and test the lineDevSpecific interface to 
// Unimodem for setting a device
// configuration without calling lineConfigDialogEdit.
//

#include <Windows.h>
#include <Windowsx.h>
#include <Tapi.h>
#include <Tapidbg.h>
#include <Mcx.h>
#include <Unimodem.h>
#include <Netui.h>

HLINEAPP g_hLineApp;
HLINE g_hLine;
DWORD g_dwNumLines;
DWORD g_dwAPIVersion;
DWORD g_dwExtVersion;
LONG  rc;

LINEMESSAGE LineMessage;

DWORD g_NextOp;
LPVARSTRING g_DevCfg;
WCHAR g_Str[256];
LPWSTR g_DevClass = TEXT("comm/datamodem");
UNIMDM_CHG_DEVCFG g_UCD;

typedef struct _OPTABLEENTRY {
    DWORD dwOption;
    DWORD dwValue;
} OPTABLEENTRY, *POPTABLEENTRY;

/*  from Unimodem.h
#define UNIMDM_OPT_BAUDRATE     1 // Use CBR_* values from Winbase.h.
#define UNIMDM_OPT_BYTESIZE     2
#define UNIMDM_OPT_PARITY       3 // Use values from Winbase.h.
#define UNIMDM_OPT_STOPBITS     4 // Use values from Winbase.h.
#define UNIMDM_OPT_WAITBONG     5 // Seconds to wait for prompt tone.
#define UNIMDM_OPT_MDMOPTIONS   6 // Use MDM_* values from Mcx.h.
#define UNIMDM_OPT_TIMEOUT      7 // Call setup fail timer in seconds.
#define UNIMDM_OPT_TERMOPTIONS  8 // Use NETUI_LCD_TRMOPT_* values from Netui.h.
#define UNIMDM_OPT_DIALMOD      9 // Dial modifier (dwValue is a LPCWSTR).
*/

OPTABLEENTRY g_OpTable[] = {
    UNIMDM_OPT_BAUDRATE,    CBR_57600,
    UNIMDM_OPT_BAUDRATE,    987654,

    UNIMDM_OPT_BYTESIZE,    5,
    UNIMDM_OPT_BYTESIZE,    6,
    UNIMDM_OPT_BYTESIZE,    7,
    UNIMDM_OPT_BYTESIZE,    8,
    UNIMDM_OPT_BYTESIZE,    9,
    UNIMDM_OPT_BYTESIZE,    10,
    UNIMDM_OPT_BYTESIZE,    11,
    UNIMDM_OPT_BYTESIZE,    16,

    UNIMDM_OPT_PARITY,      NOPARITY,
    UNIMDM_OPT_PARITY,      ODDPARITY,
    UNIMDM_OPT_PARITY,      EVENPARITY,
    UNIMDM_OPT_PARITY,      MARKPARITY,
    UNIMDM_OPT_PARITY,      SPACEPARITY,
    UNIMDM_OPT_PARITY,      5,
    UNIMDM_OPT_PARITY,      55,

    UNIMDM_OPT_STOPBITS,    ONESTOPBIT,
    UNIMDM_OPT_STOPBITS,    ONE5STOPBITS,
    UNIMDM_OPT_STOPBITS,    TWOSTOPBITS,
    UNIMDM_OPT_STOPBITS,    3,
    UNIMDM_OPT_STOPBITS,    33,

    UNIMDM_OPT_WAITBONG,    111,
    UNIMDM_OPT_TIMEOUT,     222,

    UNIMDM_OPT_TERMOPTIONS, 0,
    UNIMDM_OPT_TERMOPTIONS, NETUI_LCD_TRMOPT_MANUAL_DIAL,
    UNIMDM_OPT_TERMOPTIONS, NETUI_LCD_TRMOPT_PRE_DIAL,
    UNIMDM_OPT_TERMOPTIONS, NETUI_LCD_TRMOPT_POST_DIAL,

    UNIMDM_OPT_DIALMOD,     (DWORD)TEXT("ABCDEFG"),
    UNIMDM_OPT_DIALMOD,     (DWORD)TEXT("1234567890,abdfgei,ABCDEFG"),

};

void
GetStringNames(
    LPWSTR * ppszOptionName,
    LPWSTR * ppszValueName
    )
{
    LPWSTR tmp;

    *ppszValueName = NULL;
    tmp = g_Str;
    *tmp = 0;

    switch (g_OpTable[g_NextOp].dwOption) {
    case UNIMDM_OPT_BAUDRATE:
        *ppszOptionName = TEXT("Baud Rate");
        break;

    case UNIMDM_OPT_BYTESIZE:
        *ppszOptionName = TEXT("Byte Size");
        break;

    case UNIMDM_OPT_PARITY:
        *ppszOptionName = TEXT("Parity");
        switch (g_OpTable[g_NextOp].dwValue) {
        case NOPARITY:      *ppszValueName = TEXT("None"); break;
        case ODDPARITY:     *ppszValueName = TEXT("Odd"); break;
        case EVENPARITY:    *ppszValueName = TEXT("None"); break;
        case MARKPARITY:    *ppszValueName = TEXT("Mark"); break;
        case SPACEPARITY:   *ppszValueName = TEXT("Space"); break;
        }
        break;

    case UNIMDM_OPT_STOPBITS:
        *ppszOptionName = TEXT("Stop Bits");
        switch (g_OpTable[g_NextOp].dwValue) {
        case ONESTOPBIT:    *ppszValueName = TEXT("1"); break;
        case ONE5STOPBITS:  *ppszValueName = TEXT("1.5"); break;
        }
        break;

    case UNIMDM_OPT_WAITBONG:
        *ppszOptionName = TEXT("Wait Bong");
        break;

    case UNIMDM_OPT_MDMOPTIONS:
        *ppszOptionName = TEXT("Modem Options");
        break;

    case UNIMDM_OPT_TIMEOUT:
        *ppszOptionName = TEXT("Fail Time-out");
        break;

    case UNIMDM_OPT_TERMOPTIONS:
        *ppszOptionName = TEXT("Terminal Options");
        if (g_OpTable[g_NextOp].dwValue & NETUI_LCD_TRMOPT_MANUAL_DIAL) {
            wcscat(tmp, TEXT("Manual Dial "));
        }
        if (g_OpTable[g_NextOp].dwValue & NETUI_LCD_TRMOPT_PRE_DIAL) {
            wcscat(tmp, TEXT("Pre-dial Terminal "));
        }
        if (g_OpTable[g_NextOp].dwValue & NETUI_LCD_TRMOPT_POST_DIAL) {
            wcscat(tmp, TEXT("Post-dial Terminal "));
        }
        if (*tmp) {
            *ppszValueName = tmp;
        }
        break;

    case UNIMDM_OPT_DIALMOD:
        *ppszOptionName = TEXT("Dial Modifier");
        *ppszValueName = (LPWSTR)g_OpTable[g_NextOp].dwValue;
        break;

    default:
        *ppszOptionName = TEXT("Unknown Option");
        break;
    }
}   // GetStringNames

LONG DoNextOperation(void)
{
    LPWSTR lpszOptionName;
    LPWSTR lpszValueName;

    g_UCD.dwCommand = UNIMDM_CMD_CHG_DEVCFG;
    g_UCD.lpszDeviceClass = g_DevClass;
    g_UCD.lpDevConfig = g_DevCfg;

    g_UCD.dwOption = g_OpTable[g_NextOp].dwOption;
    g_UCD.dwValue  = g_OpTable[g_NextOp].dwValue;

    GetStringNames(&lpszOptionName, &lpszValueName);
    if (lpszValueName) {
        NKDbgPrintfW(TEXT("DEVCFG %d: Changing %s to %s\n"), g_NextOp, lpszOptionName, lpszValueName);
    } else {
        NKDbgPrintfW(TEXT("DEVCFG %d: Changing %s to %d\n"), g_NextOp, lpszOptionName, g_UCD.dwValue);
    }

    rc = lineDevSpecific(
            g_hLine,
            0,
            NULL,
            &g_UCD,
            sizeof(g_UCD)
            );

    NKDbgPrintfW(TEXT("DEVCFG lineDevSpecific(%d) returned %s\n"), g_NextOp, TAPIErrorName(rc));
    g_NextOp++;
    return rc;
}   // DoNextOperation


void WaitForReply(void)
{
    lineGetMessage(
        g_hLineApp,
        &LineMessage,
        INFINITE
        );
    
    if (LineMessage.dwMessageID == LINE_REPLY) {
        NKDbgPrintfW(TEXT("DEVCFG: Got LINE_REPLY %s\n"), TAPIErrorName(LineMessage.dwParam2));
    }
}


//
// Find a device ID from a specific service provider.
//
// Relies on globals:
//  HLINEAPP g_hLineApp;
//  DWORD g_dwNumLines;
//  DWORD g_dwAPIVersion;
//  DWORD g_dwExtVersion;
// 
DWORD
FindTSPDevice(
    DWORD TSP
    )
{

    DWORD dwDeviceID;
    LPWSTR lpszProviderInfo;
    BYTE buf[1024];
    LPLINEDEVCAPS lpDevCaps;
    LPWSTR lpszTSPName;
    DWORD rc;

    lpszTSPName = L"UNIMODEM";
    lpDevCaps = (LPLINEDEVCAPS)buf;
    lpDevCaps->dwTotalSize = sizeof(buf);

    for (dwDeviceID = 0; dwDeviceID < g_dwNumLines; dwDeviceID++) {
        rc = lineGetDevCaps(
                g_hLineApp,
                dwDeviceID,
                g_dwAPIVersion,
                g_dwExtVersion,
                lpDevCaps
                );
        if (rc) {
            NKDbgPrintfW(TEXT("FindTSPDevice lineGetDevCaps(%d) returned 0x%x\n"), dwDeviceID, rc);
            continue;
        }
        lpszProviderInfo = (LPWSTR)((DWORD)lpDevCaps + lpDevCaps->dwProviderInfoOffset);
        if (!(wcsicmp(lpszProviderInfo, lpszTSPName))) {
            return dwDeviceID;
        }
    }
    return 0xffffffff;
}   // FindTSPDevice.

//
// Get the default DevConfig and its size.
//
BOOL GetDevConfig(DWORD dwDeviceID)
{
    DWORD dwLen;
    //
    // Find out how big its DevConfig is, and get it.
    //
    dwLen = sizeof(VARSTRING);
    g_DevCfg = LocalAlloc(LPTR, dwLen);
    if (NULL == g_DevCfg) {
        goto gdc_fail;
    }

    g_DevCfg->dwTotalSize = dwLen;
    rc = lineGetDevConfig(
            dwDeviceID,
            g_DevCfg,
            TEXT("comm/datamodem")
            );
    if (rc) {
        goto gdc_fail;
    }

    if (g_DevCfg->dwTotalSize < g_DevCfg->dwNeededSize) {
        dwLen = g_DevCfg->dwNeededSize;
        LocalFree(g_DevCfg);
        g_DevCfg = LocalAlloc(LPTR, dwLen);
        if (NULL == g_DevCfg) {
            goto gdc_fail;
        }

        g_DevCfg->dwTotalSize = dwLen;
        rc = lineGetDevConfig(
                dwDeviceID,
                g_DevCfg,
                TEXT("comm/datamodem")
                );
        if (rc) {
            goto gdc_fail;
        }
    }
    return TRUE;

gdc_fail:
    return FALSE;
}   // GetDevConfig


int
WINAPI
WinMain(
    HINSTANCE hInstance,
    HINSTANCE hPrevInstance,
    LPWSTR lpCmdLine,
    int nCmdShow
    )
{
    DWORD dwDeviceID;
    LPVARSTRING vs;

    LINEINITIALIZEEXPARAMS    LineInitializeExParams = {
        sizeof(LINEINITIALIZEEXPARAMS),    //dwTotalSize
        0,                                 //dwNeededSize
        0,                                 //dwUsedSize
        LINEINITIALIZEEXOPTION_USEEVENT,   //dwOptions
        0,                                 //Handles
        0                                  //dwCompletionKey
    };
    
    g_dwAPIVersion = TAPI_CURRENT_VERSION;
    g_dwExtVersion = 0;
       
    rc = lineInitializeEx(
            &g_hLineApp,
            hInstance,
            NULL,
            TEXT("DEVCFG"),
            &g_dwNumLines,
            &g_dwAPIVersion,
            &LineInitializeExParams
            );
    
    if (rc) {
        NKDbgPrintfW(TEXT("DEVCFG: lineInitializeEx failed %s\n"), TAPIErrorName(rc));
        rc = 1;
        goto main_exit;
    }
    
    dwDeviceID = FindTSPDevice(1);

    rc = lineOpen(
            g_hLineApp,                    //g_hLineApp
            dwDeviceID,                    //dwDeviceID
            &g_hLine,                      //lphLine
            g_dwAPIVersion,
            g_dwExtVersion,
            0,                             //dwCallbackInstance
            LINECALLPRIVILEGE_OWNER,       //dwPrivileges
            LINEMEDIAMODE_DATAMODEM,       //dwMediaModes
            NULL                           //lpCallParams
            );                
    
    if (rc) {
        NKDbgPrintfW(TEXT("DEVCFG: lineOpen failed %s\n"), TAPIErrorName(rc));
        goto main_exit;
    }


    if (!GetDevConfig(dwDeviceID)) {
        NKDbgPrintfW(TEXT("DEVCFG: GetDevConfig\n"));
        goto main_exit;
    }

    g_NextOp = 0;
    while (g_NextOp < sizeof(g_OpTable)/sizeof(OPTABLEENTRY)) {
        if (!(DoNextOperation() & 0x80000000)) {
            WaitForReply();
        }
    }


    //
    // Call lineConfigDialogEdit to verify changes.
    //
    vs = (LPVARSTRING)g_Str; // Reuse string buffer.
    vs->dwTotalSize = sizeof(g_Str);

    lineConfigDialogEdit(
        dwDeviceID,
        NULL,
        g_DevClass,
        (LPVOID)((LPBYTE)g_DevCfg + sizeof(VARSTRING)),
        g_DevCfg->dwUsedSize - sizeof(VARSTRING),
        vs
        );
    
main_exit:
    NKDbgPrintfW(TEXT("DEVCFG: calling lineShutdown\n"));
    lineShutdown(g_hLineApp);
    NKDbgPrintfW(TEXT("DEVCFG: exiting\n"));    
    return rc;
}

See Also

Other Resources

Telephony API