A VBX for UDP

Frank presents a Visual Basic custom control (VBX) for User Datagram Protocol (UDP), a network protocol that relies on TCP/IP sockets.


September 01, 1995
URL:http://www.drdobbs.com/web-development/a-vbx-for-udp/184409628

Figure 1


Copyright © 1995, Dr. Dobb's Journal

Figure 1


Copyright © 1995, Dr. Dobb's Journal

SEP95: A VBX for UDP

A VBX for UDP

A custom control for network development

Frank E. Redmond III

Frank is the director of software development for a distributed-information-systems company in Michigan. He can be contacted via CompuServe at 76352,343.


A variety of communication protocols are available for building network applications: IPX/SPX, NetBIOS, and Named Pipes, to name a few. On a network-related project I was recently involved in, however, we decided to implement TCP/IP, primarily because it is independent of both network topology and platform.

Our job was to facilitate communication over a LAN between several applications; therefore, nearly 70 percent of the code I wrote was communications related. Initially, our applications were to be deployed on Windows-based machines, but since we eventually intend to support platforms such as Macintosh and UNIX, our network communications protocol had to be as platform independent as possible; hence, our decision to support TCP/IP.

To create the user interface on the front end, I turned to Visual Basic (VB), which sped up the development process and allowed me to encapsulate the network-related code into a VB Custom Control (VBX). In this article, I'll present that VBX, along with an overview of TCP/IP protocols. Additionally, I'll briefly describe how to port VBXs to OLE Custom Controls (OCXs) to ensure compatibility with future versions of Visual Basic and other development environments.

TCP/IP Overview

Transmission Control Protocol (TCP) and Internet Protocol (IP) are the two most important protocols of the Internet Protocol Suite. TCP/IP is topology independent: It works with bus, ring, and star network topologies, spanning LANs and WANs. Since TCP/IP is freely available for independent implementation, it is also network-operating-system and platform independent. In addition, TCP/IP is independent of the physical network hardware: It can be used with Ethernet, token ring, and others.

Of the two, IP is more important because it's used by all other TCP/IP protocols. IP is responsible for moving data from computer to computer using an "IP address," a unique 32-bit number assigned to each computer. Other higher-level protocols are used to move data between programs on different computers by way of a "port," a unique 16-bit number assigned to each program. Combined, an IP address and a program port number constitute a TCP/IP socket, which uniquely identifies every program on every computer. The two most popular protocols that rely on TCP/IP sockets are TCP and User Datagram Protocol (UDP).

TCP is considered "connection oriented," because the two communicating machines exchange a handshaking dialogue before data transmission begins. TCP uses a checksum to validate received data. If the checksums don't match, the sender automatically resends the data, without programmer intervention; thus TCP guarantees that data is delivered, and delivered in order. TCP is best used for stream-oriented data.

UDP is considered "connectionless." It requires less overhead since there's no handshaking. However, UDP provides unreliable data delivery: Data may be duplicated, arrive out of order, or not arrive at all. Though unreliable, UDP is very efficient and allows you to utilize your own ACK/NAK. UDP is best suited for record-oriented data where all of the information can be sent in one packet.

TCP/IP supports Windows-based machines via the Windows Sockets (WinSock) API, which lets you write applications to the WinSock specification. Applications will then run on any Winsock-compatible TCP/IP protocol stack. The WinSock API is in the form of a DLL--WINSOCK.DLL for 16-bit applications and WSOCK32.DLL for 32-bit apps. You simply write to the WinSock API and link your applications with the appropriate library.

WinSock API functions fall into one of three categories: Windows Sockets functions, Windows Sockets database functions, and Windows-specific functions. Windows Sockets functions are a subset of the Berkeley sockets routines; see Table 1. Windows Sockets database functions convert the human-readable host and client names into a computer-usable format; see Table 2. Windows-specific functions are extensions to support the event-driven architecture of Windows; see Table 3. (For more information, see Network Interrupts, by Ralf Brown and Jim Kyle, Addison-Wesley, 1994.)

Control Details

As previously mentioned, our LAN-based control project called for extensive network communication. To help with code reuse, I implemented the network routines as a VBX. To use the TCP protocol, one socket must be established for every client that the host will communicate with. If a host is connected to 100 clients using TCP, then the host will need to establish 100 sockets, as opposed to establishing one UDP socket on the host and communicating with all 100 clients. In light of this (and because a limited number of TCP sockets can be opened simultaneously for communication), I wrote a UDP-based VBX that is invisible at run time. This VBX establishes and terminates the socket, and asynchronously sends and receives data.

In relation to the WinSock API, you first initialize the underlying WinSock DLL and confirm that the version of the DLL is compatible with the VBX's requirements; see Example 1. This is done in the VBINITCC routine, which is called each time an application loads the VBX.

Next, the control's Connected property is set to True, and a socket is established in response. The Connected property can only be set at run time, and before it is set, no read/write activity can take place.

You then call WSAAsyncSelect to set up an asynchronous event notifier that generates a user-defined message (WM_USER_ASYNC_SELECT) whenever data arrives or a request to send data is made; see Example 2.

For the VBX, a request to send data is made by setting the control's Send property to True; this is possible only at run time. The control responds by placing a WM_USER_ASYNC_SELECT message in its own queue via PostMessage. Finally, WSACleanup is called to release all resources allocated by WSAStartup. There must be one call to WSACleanup for every call to WSAStartup. In the case of the VBX, WSACleanup is called during the VBTERMCC routine, which in turn is called each time an application unloads the VBX; see Example 3. The rest of the code is pretty straightforward C; see the file datagram.c in Listing One. The complete VBX (source and binary) is available electronically; see "Availability," page 3. For more information on WinSock programming, see Programming WinSock, by Arthur Dumas (Sams, 1995).

VBX-to-OCX Porting

To be compatible with future versions of Visual Basic and other development environments, I ported the UDP VBX to an OLE Custom Control (OCX). The OLE Control Development Kit (CDK) made porting the UDP control a two-step process:

  1. Use the OLE CDK to create a working skeleton.
  2. Appropriately place the code that is specific to the UDP VBX in the newly formed OCX skeleton.
One problem I encountered stemmed from the invisible-at-run-time option. If this is checked in the ControlWizard, the resulting control does not have a window at run time. This is problematic because the second parameter required to set up the asynchronous event notifier is the hwnd of the control window that will receive the user-defined event. To achieve the invisible-at-run-time effect, the control is hidden at run time with a call to ShowWindow; see
Example 4.

Conclusion

To illustrate the use of the UDP control, I've written a simple chat program that's available electronically. As Figure 1 illustrates, the program allows two users to communicate over a TCP/IP connection. To send a message, the user types a message in the Messages Out box and presses the Send button. Messages received will automatically appear in the Messages In box. The program is simple, thanks to the UDP control. Table 4 is a list of the properties supported by the UDP control, while Example 5 is a typical UDP event. These, as well as the sample code for the chat program, can be used as a reference for programming with the UDP control.

Figure 1: Sample chat program.

Table 1: Windows Sockets functions.

Function     Description

accept      Accept an incoming connection.
bind        Bind a name to a socket.
closesocket Close a socket.
connect     Initiate a connection.
getsockname Get the name bound to a socket.
getsockopt  Get the settings for a socket.
htonl       Convert u_long to network byte-order.
htons       Convert u_short to network byte-order.
inet_addr   Convert dotted-decimal IP address into 32-bit number.
inet_ntoa   Convert 32-bit number into dotted decimal IP address.
ioctlsocket I/O control of a socket.
listen      Listen for connections to this socket.
ntohl       Convert u_long to host byte-order.
ntohs       Convert u_short to host byte-order.
recv        Receive data on a connected socket.
recvfrom    Receive data on a socket, along with IP address
                     and port number.
select      Synchronous I/O multiplexing.
send        Send data over a connected socket.
sendto      Send data to a specific socket.
setsockopt  Set socket options.
shutdown    Shut down.
socket      Create an endpoint for communication.
Table 2: Windows Sockets database functions.
Function           Description

gethostbyaddr     Return host name, given IP address.
gethostbyname     Return IP address, given host name.
gethostname       Return host name.
getprotobyname    Return protocol name and number, given a protocol name.
getprotobynumber  Return protocol name and number, given a protocol number.
getservbyname     Return service name and port, given name and protocol.
getservbyport     Return service name and port, given port and protocol.
Table 3: Windows Sockets Windows-specific functions.
Function                 Description

WSAAsyncGetHostByAddr   Asynchronous version of gethostbyaddr.
WSAAsyncGetHostByName   Asynchronous version of gethostbyname.
WSAAsyncGetProtoByName  Asynchronous version of getprotobyname.
WSAAsyncGetProtoByNumberAsynchronous version of getprotobynumber.
WSAAsyncGetServByName   Asynchronous version of getservbyname.
WSAAsyncGetServByPort   Asynchronous version of getservbyport.
WSAAsyncSelect          Asynchronous version of select.
WSACancelAsyncRequest   Cancel outstanding WSAAsyncGet call.
WSACancelBlockingCall   Cancel outstanding blocking call.
WSACleanup              Release resources allocated by WSAStartup.
WSAGetLastError         Return details of last API error.
WSAIsBlocking           Determine if there is an outstanding blocking call.
WSASetBlockingHook      "Hook" underlying blocking mechanism.
WSASetLastError         Set error code to be returned by WSAGetLastError.
WSAStartup              Initialize underlying DLL.
WSAUnhookBlockingHook   Restore original blocking mechanism.
Table 4: UDP control properties and usage chart.
Property        Description

Connected      Establishes control's send/receive services;
                        undefinable at design time.
Disconnected   Terminates control's send/receive services;
                        undefinable at design time.
ErrorCode      Displays code representing error status of last UDP operation.
MaxBufferSize  Size in bytes of the largest packet allowed; read-only.
MyAddress      TCP/IP address (in dotted decimal notation) of the
                        hosting computer; read-only.
MyPort         Port number that the control will respond to.
Send           Toggled to True to send data; undefinable at design time.
ToAddress      Destination TCP/IP address (in dotted decimal notation)
                        identifying the computer that data will be sent to.
ToData         Data to be sent.
ToPort         Destination port identifying the application that data
                        will be sent to.
                           
Example 1: Confirming DLL version.
//initialize underlying WinSock DLL
if (WSAStartup(VersionRequested,&wsaData)!=0) return FALSE;
             //make sure that the VBX supports this version of WinSock
if ((LOBYTE(wsaData.wVersion)!=LOBYTE(VersionRequested))||
             (HIBYTE(wsaData.wVersion)!=HIBYTE(VersionRequested)))
    {
      WSACleanup();
      return FALSE;
    }
Example 2: Setting up an asynchronous event notifier.
if (WSAAsyncSelect(DATAGRAMDEREF(hctl)->mappsocket, hwnd,
             WM_USER_ASYNC_SELECT, FD_READ|FD_WRITE)==SOCKET_ERROR)
DATAGRAMDEREF(hctl)->mErrorCode=WSAGetLastError();
Example 3: Releasing resources.
void FAR PASCAL _export VBTERMCC(void)
{               
   //terminate the usage of the WinSock DLL
   WSACleanup();
}//VBTERMCC
Example 4: Hiding control at run time.
void CUdpCtrl::OnDraw(CDC* pdc, const CRect& rcBounds, const
Rect& rcInvalid)
{
   CBitmap         bitmap;
   BITMAP          bmp;
   CPictureHolder  picHolder;
   CRect           rcSrcBounds;
   if (AmbientUserMode()==MODE_DESIGN)     
   {
       //load bitmap
bitmap.LoadBitmap(IDB_UDP); bitmap.GetObject(sizeof(BITMAP),&bmp);
   rcSrcBounds.right=bmp.bmWidth;
   rcSrcBounds.bottom=bmp.bmHeight;
//create picture and render picHolder.CreateFromBitmap((HBITMAP)
//   bitmap.m_hObject,NULL,FALSE); picHolder.Render(pdc,rcBounds,rcSrcBounds);
   }
   else ShowWindow(SW_HIDE);
}
Example 5: Definition of UDP control DataIn event.
Sub UDP1_DataIn(FromAddress As String,
           FromPort As Integer, FromData As String)

Listing One

#include "datagram.h"
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#define ERR_None                0L
#define ERR_MethodNotSupported  421
#define ERR_ReadOnlyProperty    20000   //should be 383 but I created 
                                        //my own text for the message
#define ERR_NOTCONNECTED        20001
#define HOST_NAME_LEN           50
#define WM_USER_ASYNC_SELECT    (WM_USER+201)
#define MAKEWORD(a, b)     ((WORD)(((BYTE)(a)) | ((WORD)((BYTE)(b))) << 8))
HANDLE                  hmodDLL;
WSADATA                 wsaData;
BOOL                    initialized=FALSE;
WORD                    VersionRequested=MAKEWORD(1,1); 
                                               //compatible with WinSock 1.1
unsigned int            MaxUDPBufferSize;
char                    hostAddress[16];
int FAR PASCAL WEP(int);
int FAR PASCAL LibMain(hModule, wDataSeg, cbHeapSize, lpszCmdLine)
HANDLE  hModule;
WORD    wDataSeg;
WORD    cbHeapSize;
LPSTR   lpszCmdLine;
{
    hmodDLL = hModule;
    return 1;
}//LibMain
BOOL FAR PASCAL _export VBINITCC(USHORT usVersion,BOOL fRuntime)
{
    // this function is automatically called whenever an instance of the 
    // control loaded into memory
    char        hostName[HOST_NAME_LEN];
    PHOSTENT    phostent;
    IN_ADDR     in;
    //Run-Time-Only
    //uncomment the following lines to make a run-time only control
/*
    if (!fRuntime)
        return FALSE;
*/
    if (WSAStartup(VersionRequested,&wsaData)!=0) return FALSE; 
    //make sure that an appropriate version of WinSock is supported
    if ((LOBYTE(wsaData.wVersion)!=LOBYTE(VersionRequested))||
        (HIBYTE(wsaData.wVersion)!=HIBYTE(VersionRequested)))
    {
        WSACleanup();
        return FALSE;
    }
    if (!initialized)
    {
        //only need to get the host name one time
        gethostname(hostName,HOST_NAME_LEN);
        phostent=gethostbyname(hostName);
        memcpy(&in,phostent->h_addr,4);
        memcpy(hostAddress,inet_ntoa(in),16);
        //only need to get the max buffer size one time
        MaxUDPBufferSize=(unsigned int)wsaData.iMaxUdpDg;
        //the follwing is done because the calls to recv and sendto 
        //take an integer as their second parameter, not an unsigned int
        if (MaxUDPBufferSize>INT_MAX)
            MaxUDPBufferSize=INT_MAX;
        initialized=TRUE;
    }
    // Register control(s)
    return VBRegisterModel(hmodDLL, &modelDATAGRAM);
}//VBINITCC
int FAR PASCAL WEP(int nShutdownFlag)
{
    return 1;
}//End WEP
void FAR PASCAL _export VBTERMCC(void)
{          
    // this function is automatically called whenever an instance of the 
    // control is unloaded from memory
    WSACleanup();
    return;
}//VBTERMCC
LONG FAR PASCAL _export DataGramCtlProc(HCTL hctl,HWND hwnd,USHORT msg,
    USHORT wp,LONG lp)
{
    HSZ             tempVBString;      
    LPSTR           lpstr=NULL;
    LPSTR           destAddr=NULL;
    char            fromAddr[16];
    char            *tempIn;
    SOCKADDR_IN     addr;
    int             addrLen=sizeof(addr);
    int             fromPort;
    unsigned int    nBytesSent,nBytesRecv;
    unsigned int    stringLen;
    EVENT_PARAMS    params;
    IN_ADDR         inFrom;
    switch (msg)
    {
    case WM_NCCREATE:          
        //default the properties
        DATAGRAMDEREF(hctl)->mToPort=0;
        tempVBString=VBCreateHsz((_segment)hctl,(LPSTR)hostAddress);
        DATAGRAMDEREF(hctl)->mMyAddress=tempVBString;
        DATAGRAMDEREF(hctl)->mMyPort=0;
        DATAGRAMDEREF(hctl)->mMaxBufferSize=(long int)MaxUDPBufferSize;
        DATAGRAMDEREF(hctl)->mErrorCode=0;                    
        DATAGRAMDEREF(hctl)->mSend=FALSE;
        DATAGRAMDEREF(hctl)->mConnected=FALSE;
        DATAGRAMDEREF(hctl)->mDisconnected=TRUE;
        DATAGRAMDEREF(hctl)->mappsocket=INVALID_SOCKET;
        break;
    case WM_NCDESTROY:
        //free string memory allocated with VBCreateHsz
        if (DATAGRAMDEREF(hctl)->mMyAddress)
        {
            VBDestroyHsz(DATAGRAMDEREF(hctl)->mMyAddress);
            DATAGRAMDEREF(hctl)->mMyAddress=NULL;
        }                              
        //close any opened socket
        if (DATAGRAMDEREF(hctl)->mappsocket!=INVALID_SOCKET)
        {
            if (closesocket(DATAGRAMDEREF(hctl)->mappsocket)==SOCKET_ERROR)
                DATAGRAMDEREF(hctl)->mErrorCode=WSAGetLastError();
            DATAGRAMDEREF(hctl)->mappsocket=INVALID_SOCKET;
        }   
        break;        
    case VBM_METHOD:
        //there are no methods supported
        return VBSetErrorMessage(ERR_MethodNotSupported,
         "Method not applicable for this object.");
    case WM_USER_ASYNC_SELECT:
        if (WSAGETSELECTERROR(lp)!=0)
            return ERR_None;
        switch(WSAGETSELECTEVENT(lp))
        {
            case FD_READ:
                if (!DATAGRAMDEREF(hctl)->mSend)
                {
                    tempIn=(char *)calloc(MaxUDPBufferSize,sizeof(char));
                    //read the data
                    nBytesRecv=recvfrom(DATAGRAMDEREF(hctl)->mappsocket,
                        tempIn,MaxUDPBufferSize,0,(LPSOCKADDR)&addr,&addrLen);
                    if (nBytesRecv==SOCKET_ERROR)
                        DATAGRAMDEREF(hctl)->mErrorCode=WSAGetLastError();
                    else
                    {
                        //get the from address
                        memcpy(&inFrom,&addr.sin_addr.s_addr,4);
                        memcpy(fromAddr,inet_ntoa(inFrom),16);
                        //get the from port
                        fromPort=ntohs(addr.sin_port);
                        //set the event parameters
                        params.FromPort=&fromPort;
                        params.FromAddr = VBCreateHlstr(fromAddr, 
                                                           lstrlen(fromAddr));
                        params.FromData = VBCreateHlstr(tempIn, 
                                                            lstrlen(tempIn));
                        //fire the event
                        VBFireEvent(hctl,IEVENT_DATAGRAM_DATAIN,¶ms);
                        //free string memory allocated with VBCreateHlstr
                        VBDestroyHlstr(params.FromData);
                        VBDestroyHlstr(params.FromAddr);
                    }            
                    if (tempIn)
                        free(tempIn);
                }
                break;
            case FD_WRITE:
                //has there been a notification to send data
                if (DATAGRAMDEREF(hctl)->mSend)
                {
                    //get the destination port
                    addr.sin_family=AF_INET;
                    addr.sin_port=htons(DATAGRAMDEREF(hctl)->mToPort);
                    //get the destination address
                    destAddr=VBLockHsz(DATAGRAMDEREF(hctl)->mToAddress);
                    addr.sin_addr.s_addr=inet_addr(destAddr);
                    VBUnlockHsz(DATAGRAMDEREF(hctl)->mToAddress);
                    if (DATAGRAMDEREF(hctl)->mToData)
                    {                                     
                        lpstr=VBLockHsz(DATAGRAMDEREF(hctl)->mToData);
                        stringLen=lstrlen(lpstr);
                        //send the data
                        nBytesSent=sendto(DATAGRAMDEREF(hctl)->mappsocket,
                            lpstr,stringLen,0,(LPSOCKADDR)&addr,sizeof(addr));
                        if (nBytesSent==SOCKET_ERROR)
                            DATAGRAMDEREF(hctl)->mErrorCode=WSAGetLastError();
                        //unlock it 
                        VBUnlockHsz(DATAGRAMDEREF(hctl)->mToData);
                    }   
                    //reset the send flag
                    DATAGRAMDEREF(hctl)->mSend=FALSE;
                }
                break;
            default:
                break;
        }
        return ERR_None;
    case VBM_SETPROPERTY:
        //called whenever a property is set
        switch(wp)
        {
        case IPROP_DATAGRAM_TOADDRESS:
            return ERR_None;
        case IPROP_DATAGRAM_TOPORT:
            return ERR_None;
        case IPROP_DATAGRAM_TODATA:
            return ERR_None;
        case IPROP_DATAGRAM_MYADDRESS:
            //read-only
            //clear it out
            if (DATAGRAMDEREF(hctl)->mMyAddress)
                VBDestroyHsz(DATAGRAMDEREF(hctl)->mMyAddress);
            //then set it to what the default is
            tempVBString=VBCreateHsz((_segment)hctl,(LPSTR)hostAddress);
            DATAGRAMDEREF(hctl)->mMyAddress=tempVBString;
            return VBSetErrorMessage(ERR_ReadOnlyProperty,
             "Property is read-only.");
        case IPROP_DATAGRAM_MYPORT:
            return ERR_None;
        case IPROP_DATAGRAM_MAXBUFFERSIZE:
            //read-only
            DATAGRAMDEREF(hctl)->mMaxBufferSize=(long int)MaxUDPBufferSize;
            return VBSetErrorMessage(ERR_ReadOnlyProperty,
                "Property is read-only.");
        case IPROP_DATAGRAM_ERRORCODE:
            return ERR_None;
        case IPROP_DATAGRAM_SEND:
            if (VBGetMode()==MODE_DESIGN)      
                //cannot set this property at design-time
                DATAGRAMDEREF(hctl)->mSend=FALSE;
            else
                //place a FD_WRITE message in the application message que
                PostMessage(hwnd,WM_USER_ASYNC_SELECT,
                              DATAGRAMDEREF(hctl)->mappsocket, 
                              WSAMAKESELECTREPLY(FD_WRITE,0));
            return ERR_None;
        case IPROP_DATAGRAM_CONNECTED:
            if (VBGetMode()==MODE_DESIGN)      
                //cannot set this property as design-time
                DATAGRAMDEREF(hctl)->mConnected=FALSE;
            else
            {
                if (DATAGRAMDEREF(hctl)->mConnected)
                {
                  addr.sin_family=AF_INET;
                  addr.sin_port=htons(DATAGRAMDEREF(hctl)->mMyPort);
                  addr.sin_addr.s_addr=htonl(INADDR_ANY);             
                  DATAGRAMDEREF(hctl)->mappsocket=socket(AF_INET,SOCK_DGRAM,0);
                  if (DATAGRAMDEREF(hctl)->mappsocket==INVALID_SOCKET)
                      DATAGRAMDEREF(hctl)->mErrorCode=WSAGetLastError();
                    else
                    {
                        if (bind(DATAGRAMDEREF(hctl)->mappsocket,
                            (LPSOCKADDR)&addr,sizeof(addr))==SOCKET_ERROR)
                            DATAGRAMDEREF(hctl)->mErrorCode=WSAGetLastError();
                        else
                        {
                           //find out the port number that may have been
                           //automatically assigned if myPort was 0
                           getsockname(DATAGRAMDEREF(hctl)->mappsocket,
                                (LPSOCKADDR)&addr,&addrLen);
                           DATAGRAMDEREF(hctl)->mMyPort=ntohs(addr.sin_port);
                           //setup the asynchronous read/write handler
                           if (WSAAsyncSelect(DATAGRAMDEREF(hctl)->mappsocket,
                             hwnd,WM_USER_ASYNC_SELECT,
                             FD_READ|FD_WRITE)==SOCKET_ERROR)
                             DATAGRAMDEREF(hctl)->mErrorCode=WSAGetLastError();
                        }
                        //set disconnected to false
                        DATAGRAMDEREF(hctl)->mDisconnected=FALSE;
                    }
                }
                else
                    //cannot toggle to false
                    DATAGRAMDEREF(hctl)->mConnected=TRUE;
            }
            return ERR_None;
        case IPROP_DATAGRAM_DISCONNECTED:
            if (VBGetMode()==MODE_DESIGN)      
                //cannot set this property as design-time
                DATAGRAMDEREF(hctl)->mDisconnected=TRUE;
            else
            {
                if (DATAGRAMDEREF(hctl)->mDisconnected)
                {
                    //close any opened socket
                    if (DATAGRAMDEREF(hctl)->mappsocket!=INVALID_SOCKET)
                    {
                        if (closesocket(DATAGRAMDEREF(hctl)->mappsocket)==
                                                                  SOCKET_ERROR)
                            DATAGRAMDEREF(hctl)->mErrorCode=WSAGetLastError();
                        DATAGRAMDEREF(hctl)->mappsocket=INVALID_SOCKET;
                    }   
                    //set connected to false
                    DATAGRAMDEREF(hctl)->mConnected=FALSE;
                }           
                else
                    //cannot toggle to false
                    DATAGRAMDEREF(hctl)->mDisconnected=TRUE;
            }
            return ERR_None;
        default:
            break;       
        }
        break;
    }
    return VBDefControlProc(hctl, hwnd, msg, wp, lp);
}//DataGramCtlProc

Copyright © 1995, Dr. Dobb's Journal

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.