CNamedPipe v1.0






4.30/5 (7 votes)
Mar 10, 2000

138995

3734
A Freeware MFC class to encapsulate Named Pipes.
Welcome to CNamedPipe v1.0, A freeware MFC class to encapsulate the Named Pipe IPC method as provided in Win32.
Features |
Usage |
History |
API Reference |
Planned Enhancements |
Contacting the Author |
Features
- Simple and clean C++ interface.
- The one class supports both client and server side named pipes. Please bear in mind that currently only NT and not 95/98 support the server side to named pipes.
- The classes are fully Unicode compliant and include Unicode built options in the workspace file.
- All the rich functionality that can be accessed using the SDK calls is still available if you need it.
Usage
- To use the class in your code simply include npipe.cpp in your project and #include npipe.h in which ever of your modules needs to make calls to the class.
- Your code will need to include MFC either statically or dynamically.
- To see the class in action, have a look at the void CTestpipeDlg::OnGettime() in testpipedlg.cpp to see how you create a client connection and main() in timesvr.cpp for server side connection.
V1.0 (2 August 1998)
- Initial public release.
API Reference
The API consists of the class CNamedPipe and its public member functions
CNamedPipe
~CNamedPipe
Create
Open
operator HANDLE
Close
Attach
Detach
ConnectClient
DisconnectClient
Flush
Write
Read
Peek
Transact
IsOpen
IsBlockingPipe
IsClientPipe
IsServerPipe
IsMessagePipe
GetCurrentInstances
GetMaxCollectionCount
GetCollectionTimeout
GetOutboundBufferSize
GetInboundBufferSize
GetClientUserName
GetMaxInstances
SetMode
SetMaxCollectionCount
SetCollectionTimeout
Call
ServerAvailable
AssertValid
Dump
CNamedPipe::CNamedPipe
CNamedPipe();
Remarks
Standard default constructor. Initialises the pipe handle to a default value.
See Also
CNamedPipe::~CNamedPipe
~CNamedPipe();
Remarks
Standard default destructor. Will close any pipe handles which are still open
See Also
CNamedPipe::Create
BOOL Create(LPCTSTR lpszName, DWORD dwOpenMode, DWORD dwPipeMode, DWORD dwMaxInstances, DWORD dwOutBufferSize, DWORD dwInBufferSize, DWORD dwDefaultTimeOut, LPSECURITY_ATTRIBUTES lpSecurityAttributes = NULL);
Return Value
Non-Zero if the named pipe was successfully created otherwise FALSE. Use GetLastError to get extended error information.
Parameters
lpszName Points to the null-terminated string that uniquely identifies the pipe. It can include any character other than a backslash, including numbers and special characters. The entire pipe name string can be up to 256 characters long. Pipe names are not case sensitive.
dwOpenMode Specifies the pipe access mode, the overlapped mode, the write-through mode, and the security access mode of the pipe handle.
This parameter must specify one of the following pipe access mode flags. The same mode must be specified for each instance of the pipe:
Mode | Description |
PIPE_ACCESS_DUPLEX | The pipe is bi-directional; both server and client processes can read from and write to the pipe. This mode gives the server the equivalent of GENERIC_READ | GENERIC_WRITE access to the pipe. The client can specify GENERIC_READ or GENERIC_WRITE, or both, when it connects to the pipe using the CreateFile function. |
PIPE_ACCESS_INBOUND | The flow of data in the pipe goes from client to server only. This mode gives the server the equivalent of GENERIC_READ access to the pipe. The client must specify GENERIC_WRITE access when connecting to the pipe. |
PIPE_ACCESS_OUTBOUND | The flow of data in the pipe goes from server to client only. This mode gives the server the equivalent of GENERIC_WRITE access to the pipe. The client must specify GENERIC_READ access when connecting to the pipe. |
This parameter can also include either or both of the following flags, which enable write-through mode and overlapped mode. These modes can be different for different instances of the same pipe.
Mode | Description |
FILE_FLAG_WRITE_THROUGH | |
Write-through mode is enabled. This mode affects only write operations on byte-type pipes and, then, only when the client and server processes are on different computers. If this mode is enabled, functions writing to a named pipe do not return until the data written is transmitted across the network and is in the pipe’s buffer on the remote computer. If this mode is not enabled, the system enhances the efficiency of network operations by buffering data until a minimum number of bytes accumulate or until a maximum time elapses. | |
FILE_FLAG_OVERLAPPED | |
Overlapped mode is enabled. If this mode is enabled, functions performing read, write, and connect operations that may take a significant time to be completed can return immediately. This mode enables the thread that started the operation to perform other operations while the time-consuming operation executes in the background. For example, in overlapped mode, a thread can handle simultaneous input and output (I/O) operations on multiple instances of a pipe or perform simultaneous read and write operations on the same pipe handle. If overlapped mode is not enabled, functions performing read, write, and connect operations on the pipe handle do not return until the operation is finished. The ReadFileEx and WriteFileEx functions can only be used with a pipe handle in overlapped mode. The ReadFile, WriteFile, ConnectNamedPipe, and TransactNamedPipe functions can execute either synchronously or as overlapped operations. |
This parameter can include any combination of the following security access mode flags. These modes can be different for different instances of the same pipe. They can be specified without concern for what other dwOpenMode modes have been specified.
Mode | Description |
WRITE_DAC | The caller will have write access to the named pipe’s discretionary access control list (ACL). |
WRITE_OWNER | The caller will have write access to the named pipe’s owner. |
ACCESS_SYSTEM_SECURITY | The caller will have write access to the named pipe’s system ACL. |
dwPipeMode Specifies the type, read, and wait modes of the pipe handle.
One of the following type mode flags can be specified. The same type mode must be specified for each instance of the pipe. If you specify zero, the parameter defaults to byte-type mode.
Mode | Description |
PIPE_TYPE_BYTE | Data is written to the pipe as a stream of bytes. This mode cannot be used with PIPE_READMODE_MESSAGE. |
PIPE_TYPE_MESSAGE | Data is written to the pipe as a stream of messages. This mode can be used with either PIPE_READMODE_MESSAGE or PIPE_READMODE_BYTE. |
One of the following read mode flags can be specified. Different instances of the same pipe can specify different read modes. If you specify zero, the parameter defaults to byte-read mode.
Mode | Description |
PIPE_READMODE_BYTE | Data is read from the pipe as a stream of bytes. This mode can be used with either PIPE_TYPE_MESSAGE or PIPE_TYPE_BYTE. |
PIPE_READMODE_MESSAGE | Data is read from the pipe as a stream of messages. This mode can be only used if PIPE_TYPE_MESSAGE is also specified. |
One of the following wait mode flags can be specified. Different instances of the same pipe can specify different wait modes. If you specify zero, the parameter defaults to blocking mode.
Mode | Description |
PIPE_WAIT | Blocking mode is enabled. When the pipe handle is specified in the ReadFile, WriteFile, or ConnectNamedPipe function, the operations are not completed until there is data to read, all data is written, or a client is connected. Use of this mode can mean waiting indefinitely in some situations for a client process to perform an action. |
PIPE_NOWAIT | Nonblocking mode is enabled. In this mode, ReadFile, WriteFile, and ConnectNamedPipe always return immediately. Note that nonblocking mode is supported for compatibility with Microsoft LAN Manager version 2.0 and should not be used to achieve asynchronous I/O with named pipes. |
nMaxInstances Specifies the maximum number of instances that can be created for this pipe. The same number must be specified for all instances. Acceptable values are in the range 1 through PIPE_UNLIMITED_INSTANCES. If this parameter is PIPE_UNLIMITED_INSTANCES, the number of pipe instances that can be created is limited only by the availability of system resources.
nOutBufferSize Specifies the number of bytes to reserve for the output buffer. For a discussion on sizing named pipe buffers, see the following Remarks section.
nInBufferSize Specifies the number of bytes to reserve for the input buffer. For a discussion on sizing named pipe buffers, see the following Remarks section.
nDefaultTimeOut Specifies the default time-out value, in milliseconds, if the WaitNamedPipe function specifies NMPWAIT_USE_DEFAULT_WAIT. Each instance of a named pipe must specify the same value.
lpSecurityAttributes Pointer to a SECURITY_ATTRIBUTES structure that specifies a security descriptor for the new named pipe and determines whether child processes can inherit the returned handle. If lpSecurityAttributes is NULL, the named pipe gets a default security descriptor and the handle cannot be inherited.
Remarks
Creates a server side named pipe. Please note that currently Microsoft only provide this functionality on Windows NT (workstation or Server) and not Windows 95 or Windows 98.
CNamedPipe::Open
BOOL Open(LPCTSTR lpszServerName, LPCTSTR lpszPipeName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes = NULL, DWORD dwFlagsAndAttributes = 0);
Return Value
Non-Zero if the named pipe was successfully opened otherwise FALSE. Use GetLastError to get extended error information.
Parameters
lpszServerName Name of the machine where the server side of the named pipe resides. It can be "." to represent this machine.
lpszPipeName Name of the pipe to connect to
dwDesiredAccess Specifies the type of access to the pipe. An application can obtain read access, write access, read-write access, or device query access. This parameter can be any combination of the following values.
Value | Meaning |
0 | Specifies device query access to the object. An application can query device attributes without accessing the device. |
GENERIC_READ | Specifies read access to the object. Data can be read from the file and the file pointer can be moved. Combine with GENERIC_WRITE for read-write access. |
GENERIC_WRITE | Specifies write access to the object. Data can be written to the file and the file pointer can be moved. Combine with GENERIC_READ for read-write access. |
dwShareMode Set of bit flags that specifies how the object can be shared. If dwShareMode is 0, the object cannot be shared. Subsequent open operations on the object will fail, until the handle is closed.
To share the object, use a combination of one or more of the following values:
Value | Meaning |
FILE_SHARE_DELETE | Windows NT only: Subsequent open operations on the object will succeed only if delete access is requested. |
FILE_SHARE_READ | Subsequent open operations on the object will succeed only if read access is requested. |
FILE_SHARE_WRITE | Subsequent open operations on the object will succeed only if write access is requested. |
lpSecurityAttributes Pointer to a SECURITY_ATTRIBUTES structure that determines whether the returned handle can be inherited by child processes. If lpSecurityAttributes is NULL, the handle cannot be inherited.
dwFlagsAndAttributes Specifies the file attributes and flags for the pipe.
Any combination of the following flags is acceptable for the dwFlagsAndAttributes parameter.
Flag | Meaning |
FILE_FLAG_WRITE_THROUGH | |
Instructs the system to write through any intermediate cache and go directly to disk. Windows can still cache write operations, but cannot lazily flush them. | |
FILE_FLAG_OVERLAPPED | |
Instructs the system to initialise the object, so that operations that take a significant amount of time to process return ERROR_IO_PENDING. When the operation is finished, the specified event is set to the signalled state. | |
When you specify FILE_FLAG_OVERLAPPED, the ReadFile and WriteFile functions must specify an OVERLAPPED structure. That is, when FILE_FLAG_OVERLAPPED is specified, an application must perform overlapped reading and writing. | |
When FILE_FLAG_OVERLAPPED is specified, the system does not maintain the file pointer. The file position must be passed as part of the lpOverlapped parameter (pointing to an OVERLAPPED structure) to the ReadFile and WriteFile functions. | |
This flag also enables more than one operation to be performed simultaneously with the handle (a simultaneous read and write operation, for example). | |
FILE_FLAG_NO_BUFFERING | |
Instructs the system to open the file with no intermediate buffering or caching. |
The dwFlagsAndAttributes parameter can also contain Security Quality of Service information. When the calling application specifies the SECURITY_SQOS_PRESENT flag, the dwFlagsAndAttributes parameter can contain one or more of the following values:
Value | Meaning |
SECURITY_ANONYMOUS | Specifies to impersonate the client at the Anonymous impersonation level. |
SECURITY_IDENTIFICATION | Specifies to impersonate the client at the Identification impersonation level. |
SECURITY_IMPERSONATION | Specifies to impersonate the client at the Impersonation impersonation level. |
SECURITY_DELEGATION | Specifies to impersonate the client at the Delegation impersonation level. |
SECURITY_CONTEXT_TRACKING | Specifies that the security tracking mode is dynamic. If this flag is not specified, Security Tracking Mode is static. |
SECURITY_EFFECTIVE_ONLY | Specifies that only the enabled aspects of the
client’s security context are available to the server. If you do not specify this
flag, all aspects of the client’s security context are available. This flag allows the client to limit the groups and privileges that a server can use while impersonating the client. |
Remarks
Opens a client side connection to a named pipe. Unlike the server side creation of a named pipe, this function does not have any Win32 OS considerations.
CNamedPipe::operator HANDLE
HANDLE operator HANDLE() const;
Return Value
Returns the underlying HANDLE which this instance encapsulates or INVALID_HANDLE_VALUE if this instance is not open.
CNamedPipe::Close
BOOL Close();
Return Value
Non zero if the named pipe was closed otherwise FALSE. Use GetLastError to get extended error information.
Remarks
Closes the pipe
CNamedPipe::Attach
BOOL Attach(HANDLE hPipe);
Return Value
Non zero if the pipe was successfully attached otherwise FALSE. Use GetLastError to get extended error information.
Parameters
hPipe SDK handle of an existing pipe to attach to.
CNamedPipe::Detach
HANDLE Detach();
Return Value
The handle of the pipe which this instance was encapsulating.
Remarks
Detaches the C++ instance from the SDK pipe handle.
CNamedPipe::ConnectClient
BOOL ConnectClient(LPOVERLAPPED lpOverlapped = NULL);
Return Value
Non zero if a client pipe connection was successfully connected to otherwise FALSE. Use GetLastError to get extended error information.
Parameters
lpOverlapped pointer to an OVERLAPPED structure to use if this pipe was opened in a overlapped mode.
Remarks
Call by a server side pipe to connect a client connection. In the sockets world this corresponds very closely to an accept call.
CNamedPipe::DisconnectClient
BOOL DisconnectClient();
Return Value
Non zero if the client connection was successfully disconnected otherwise FALSE. Use GetLastError to get extended error information.
Parameters
This is the corollary function to ConnectClient and for each call to ConnectClient in your server side application there should be a corresponding call to DisconnectClient.
Remarks
TO DO
CNamedPipe::Flush
BOOL Flush();
Return Value
Non zero if the Flush succeeded otherwise FALSE. To get extended error information use GetLastError.
Remarks
Flushes any data which may be buffered in the pipe.
CNamedPipe::Write
BOOL Write(LPCVOID lpBuffer, DWORD dwNumberOfBytesToWrite,
DWORD& dwNumberOfBytesWritten, LPOVERLAPPED lpOverlapped =
NULL);
BOOL Write(LPCVOID lpBuffer, DWORD dwNumberOfBytesToWrite,
LPOVERLAPPED lpOverlapped, LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
Return Value
Non zero if the Write succeeded otherwise FALSE. To get extended error information use GetLastError.
Parameters
lpBuffer Points to the buffer containing the data to be written to the pipe.
dwNumberOfBytesToWrite Specifies the number of bytes to write to the pipe.
dwNumberOfBytesWritten Points to the number of bytes written by this function call. Write sets this value to zero before doing any work or error checking.
lpOverlapped Points to an OVERLAPPED structure. This structure is required if the pipe was opened with FILE_FLAG_OVERLAPPED.
lpCompletionRoutine Points to a completion routine to be called when the write operation has been completed and the calling thread is in an alertable wait state.
Remarks
Performs a write to the named pipe. The first version can be used in a synchronous or asynchronous manner where as the second version can be used in an asynchronous manner only.
CNamedPipe::Read
BOOL Read(LPVOID lpBuffer, DWORD dwNumberOfBytesToRead,
DWORD& dwNumberOfBytesRead, LPOVERLAPPED lpOverlapped =
NULL);
BOOL Read(LPVOID lpBuffer, DWORD dwNumberOfBytesToRead,
LPOVERLAPPED lpOverlapped, LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
Return Value
Non zero if the Read succeeded otherwise FALSE. To get extended error information use GetLastError.
Parameters
lpBuffer Points to the buffer to receive the data to be read.
dwNumberOfBytesToWrite Specifies the number of bytes to read from the pipe.
dwNumberOfBytesWritten Points to the number of bytes read by this function call. Read sets this value to zero before doing any work or error checking.
lpOverlapped Points to an OVERLAPPED structure. This structure is required if the pipe was opened with FILE_FLAG_OVERLAPPED.
lpCompletionRoutine Points to a completion routine to be called when the read operation has been completed and the calling thread is in an alertable wait state.
Remarks
Performs a read from the named pipe. The first version can be used in a synchronous or asynchronous manner where as the second version can be used in an asynchronous manner only.
CNamedPipe::Peek
BOOL Peek(LPVOID lpBuffer, DWORD dwBufferSize, DWORD& dwBytesRead, DWORD& dwTotalBytesAvail, DWORD& dwBytesLeftThisMessage);
Return Value
Non zero if the function succeeds otherwise FALSE. To get extended error information use GetLastError.
Parameters
lpBuffer Points to a buffer that receives data read from the pipe. This parameter can be NULL if no data is to be read.
dwBufferSize Specifies the size, in bytes, of the buffer specified by the lpBuffer parameter. This parameter is ignored if lpBuffer is NULL.
dwBytesRead 32-bit variable that receives the number of bytes read from the pipe.
dwTotalBytesAvail 32-bit variable that receives the total number of bytes available to be read from the pipe.
dwBytesLeftThisMessage 32-bit variable that receives the number of bytes remaining in this message. This parameter will be zero for byte-type named pipes
Remarks
Copies data from the pipe into a buffer without removing it from the pipe. It also returns information about data in the pipe.
CNamedPipe::Transact
BOOL Transact(LPVOID lpInBuffer, DWORD dwInBufferSize, LPVOID lpOutBuffer, DWORD dwOutBufferSize, DWORD& dwBytesRead, LPOVERLAPPED lpOverlapped = NULL);
Return Value
Non zero if the function succeeds otherwise FALSE. To get extended error information use GetLastError.
Parameters
lpInBuffer Points to the buffer containing the data written to the pipe.
dwInBufferSize Specifies the size, in bytes, of the write buffer.
lpOutBuffer Points to the buffer that receives the data read from the pipe.
dwOutBufferSize Specifies the size, in bytes, of the read buffer.
dwBytesRead variable that receives the number of bytes read from the pipe.
lpOverlapped Points to an OVERLAPPED structure. This structure is required if the pipe was opened with FILE_FLAG_OVERLAPPED.
Remarks
Combines into a single network operation the functions that write a message to and read a message from the specified named pipe.
CNamedPipe::IsOpen
BOOL IsOpen() const;
Return Value
Non zero if the pipe is open otherwise FALSE.
CNamedPipe::IsBlockingPipe
BOOL IsBlockingPipe(BOOL& bIsBlocking) const;
Return Value
Non zero if the function succeeds otherwise FALSE. To get extended error information use GetLastError.
Parameters
bIsBlocking Upon return will be non zero if the pipe is set up in blocking mode otherwise FALSE.
Remarks
Determines whether the pipe has been setup from blocking mode.
CNamedPipe::IsClientPipe
BOOL IsClientPipe(BOOL& bClientPipe) const;
Return Value
Non zero if the function succeeds otherwise FALSE. To get extended error information use GetLastError.
Parameters
bClientPipe Upon return will be non zero if the pipe is a client connection otherwise FALSE.
Remarks
Determines whether the pipe is a client connection.
CNamedPipe::IsServerPipe
BOOL IsServerPipe(BOOL& bServerPipe) const;
Return Value
Non zero if the function succeeds otherwise FALSE. To get extended error information use GetLastError.
Parameters
bServerPipe Upon return will be non zero if the pipe is a server side pipe otherwise FALSE.
Remarks
Determines whether the pipe is a server side pipe.
CNamedPipe::IsMessagePipe
BOOL IsMessagePipe(BOOL& bMessagePipe) const;
Return Value
Non zero if the function succeeds otherwise FALSE. To get extended error information use GetLastError.
Parameters
bMessagePipe Upon return will be non zero if the pipe is set up for message mode otherwise FALSE indicating byte mode.
Remarks
Determines whether the pipe has been setup from message or byte mode.
CNamedPipe::GetCurrentInstances
DWORD GetCurrentInstances() const;
Return Value
The number of current pipe instances.
CNamedPipe::GetMaxCollectionCount
DWORD GetMaxCollectionCount() const;
Return Value
The maximum number of bytes to be collected on the client’s computer before transmission to the server.
CNamedPipe::GetCollectionTimeout
DWORD GetCollectionTimeout() const;
Return Value
The maximum time, in milliseconds, that can pass before a remote named pipe transfers information over the network
CNamedPipe::GetOutboundBufferSize
DWORD GetOutboundBufferSize() const;
Return Value
The size, in bytes, of the buffer for outgoing data.
CNamedPipe::GetInboundBufferSize
DWORD GetInboundBufferSize() const;
Return Value
The size, in bytes, of the buffer for incoming data.
CNamedPipe::GetClientUserName
CString GetClientUserName() const;
Return Value
The string containing the user name string of the client application
CNamedPipe::GetMaxInstances
DWORD GetMaxInstances() const;
Return Value
The maximum number of pipe instances that can be created.
CNamedPipe::SetMode
DWORD SetMode(BOOL bByteMode, BOOL bBlockingMode) const;
Return Value
Non zero if the function succeeds otherwise FALSE. To get extended error information use GetLastError.
Parameters
bByteMode TRUE to set the pipe into byte mode, FALSE will set the pipe into message mode
bBlockingMode TRUE will cause all calls on the pipe which may take a long time to block, FALSE will make the pipe calls behave asynchronously.
CNamedPipe::SetMaxCollectionCount
BOOL SetMaxCollectionCount(DWORD dwCollectionCount) const;
Return Value
Non zero if the function succeeds otherwise FALSE. To get extended error information use GetLastError.
Parameters
dwCollectionCount The maximum number of bytes to be collected on the client’s computer before transmission to the server.
Remarks
Changes the sending characteristics of the pipe.
CNamedPipe::SetCollectionTimeout
BOOL SetCollectionTimeout(DWORD dwDataTimeout) const;
Return Value
Non zero if the function succeeds otherwise FALSE. To get extended error information use GetLastError.
Parameters
dwDataTimeout The maximum time, in milliseconds, that can pass before a remote named pipe transfers information over the network.
Remarks
Changes the sending characteristics of the pipe.
CNamedPipe::Call
static BOOL Call(LPCTSTR lpszServerName,
LPCTSTR lpszPipeName, LPVOID lpInBuffer, DWORD dwInBufferSize,
LPVOID lpOutBuffer,
DWORD dwOutBufferSize, DWORD& dwBytesRead, DWORD dwTimeOut);
Return Value
Non zero if the function succeeds otherwise FALSE. To get extended error information use GetLastError.
Parameters
lpszServerName Name of the machine where the server side of the named pipe resides. It can be "." to represent this machine.
lpszPipeName Name of the pipe to connect to.
lpInBuffer Points to the buffer containing the data written to the pipe.
dwInBufferSize Specifies the size, in bytes, of the write buffer.
lpOutBuffer Points to the buffer that receives the data read from the pipe.
dwOutBufferSize Specifies the size, in bytes, of the read buffer.
dwBytesRead variable that receives the number of bytes read from the pipe.
dwTimeOut Specifies the default time-out value, in milliseconds.
Remarks
Calling this function which underneath the bonnet calls CallNamedPipe is equivalent to calling the CreateFile (or WaitNamedPipe, if CreateFile cannot open the pipe immediately), TransactNamedPipe, and CloseHandle functions
CNamedPipe::ServerAvailable
static BOOL ServerAvailable(LPCTSTR lpszServerName, LPCTSTR lpszPipeName, DWORD dwTimeOut);
Return Value
If an instance of the pipe is available before the time-out interval elapses, the return value is nonzero. If an instance of the pipe is not available before the time-out interval elapses, the return value is zero. To get extended error information, call GetLastError.
Parameters
lpszServerName Name of the machine where the server side of the named pipe resides. It can be "." to represent this machine.
lpszPipeName Name of the pipe to connect to.
dwTimeout Specifies the default time-out value, in milliseconds.
Remarks
Waits until either a time-out interval elapses or an instance of the specified named pipe is available to be connected to
CNamedPipe::AssertValid
virtual void AssertValid();
Remarks
Standard MFC override which performs a validity check on this instance.
CNamedPipe::Dump
virtual void Dump(CDumpContext& dc) const;
Parameters
dc The diagnostic dump context for dumping, usually afxDump.
Remarks
Standard MFC override which performs a dump to the dump context as provided by "dc".
Planned Enhancements
- Package the code up into an OCX to allow non MFC apps to use the code.
- If you have any other suggested improvements, please let me know so that I can incorporate them into the next release.
Contacting the Author
Please send any comments or bug reports to me via email. For any updates to this article, check my site here.