This section of the archives stores flipcode's complete Developer Toolbox collection, featuring a variety of mini-articles and source code contributions from our readers.

 

  Socket Classes
  Submitted by



It all started when I wanted to write my own e-mail client program, just for the fun of it. After searching the net for a while (not long), I figured I needed to implement the POP3 and SMTP protocols (client side). These protocols are both layered on top of TCP/IP. The easiest way to access a TCP/IP network is the well known "Berkeley Sockets". On Windows, this takes the form of "Winsock".

You will find in this code of the day a C++ socket wrapper. It provides basic connection/binding mechanisms and I/O streams to easily transmit/receive data. This is the section I would mostly like to have feedback. You will also find wrappers for the infamous SOCKADDR structure and it's Internet variant, SOCKADDR_IN. Finally, there is also a simple and incomplete POP3 client implementation that I used to test the socket class. To test it, simply edit the file "main.cpp" to enter your server's address, username and password.

Have a nice day! Thierry

Currently browsing [socket.zip] (6,762 bytes) - [socket.h] - (6,072 bytes)

////////////////////////////////////////////////////////////////////////////////////////
//
// Socket
// Copyright (C) 2000  Thierry Tremblay
//
// http://frogengine.net-connect.net/
//
////////////////////////////////////////////////////////////////////////////////////////

#ifndef TT_SOCKET_H
#define TT_SOCKET_H

#include <iostream>



//////////////////////////////////////////////////////////////////////////////////////// // // WINSOCK_VERSION: This control which version of winsock is used // //////////////////////////////////////////////////////////////////////////////////////// #ifndef WINSOCK_VERSION #define WINSOCK_VERSION 1 #endif

#if !(WINSOCK_VERSION >=1 && WINSOCK_VERSION <= 2) #error Winsock version not supported #endif



//////////////////////////////////////////////////////////////////////////////////////// // // Headers // //////////////////////////////////////////////////////////////////////////////////////// #if WINSOCK_VERSION == 1 #include <winsock.h> #else #define INCL_WINSOCK_API_TYPEDEFS 1 #include <winsock2.h> #endif



//////////////////////////////////////////////////////////////////////////////////////// // // NetworkAddress - This is an interface to a socket address // //////////////////////////////////////////////////////////////////////////////////////// class NetworkAddress { public: virtual ~NetworkAddress() {} bool operator==( const NetworkAddress& other ) const; bool operator!=( const NetworkAddress& other ) const;

virtual operator void*() const = 0; operator sockaddr*() const { return GetSockAddr(); }

virtual NetworkAddress* Clone() const = 0; virtual int GetFamily() const = 0; virtual int GetSize() const = 0; virtual sockaddr* GetSockAddr() const = 0; };



//////////////////////////////////////////////////////////////////////////////////////// // // Socket // //////////////////////////////////////////////////////////////////////////////////////// class Socket { public:

// Construction / destruction Socket( int iAddressFamily = AF_INET, int type = SOCK_STREAM, int protocol = IPPROTO_TCP ); Socket( SOCKET socket ); virtual ~Socket(); // Connection handling bool Accept( Socket** ppSocket, NetworkAddress* pAddress = 0 ) const; bool Bind( const NetworkAddress& address ) const; bool Close(); bool Connect( const NetworkAddress& address ) const; bool Listen( int queueSize ) const; // Transmission int Receive( char* pBuffer, int lenBuffer, int flags = 0 ) const; int Send( const char* pBuffer, int lenBuffer, int flags = 0 ) const; int ReceiveFrom( char* pBuffer, int lenBuffer, NetworkAddress& address, int flags = 0 ) const; int SendTo( const char* pBuffer, int lenBuffer, const NetworkAddress& address, int flags = 0 ) const; // Timeout management int GetReceiveTimeout() const { return m_recvTimeout; } int GetSendTimeout() const { return m_sendTimeout; } void SetReceiveTimeout( int seconds ) { m_recvTimeout = seconds; } void SetSendTimeout( int seconds ) { m_sendTimeout = seconds; }

// State management int GetLastError() const { return m_lastError; } bool IsExceptionPending( int timeout = -1, int usec = 0 ) const; bool IsReadReady( int timeout = -1, int usec = 0 ) const; bool IsWriteReady( int timeout = -1, int usec = 0 ) const; bool SetBlocking( bool bBlocking );

// Address management NetworkAddress* CreateAddress() const; const NetworkAddress& GetLocalAddress() const; const NetworkAddress& GetRemoteAddress() const;

// Stream access std::istream& GetInputStream(); std::ostream& GetOutputStream(); std::iostream& GetIOStream();



private:

static bool s_bInitialized;

SOCKET m_socket; mutable int m_lastError; int m_addressFamily; int m_sendTimeout; int m_recvTimeout; mutable NetworkAddress* m_pLocalAddress; mutable NetworkAddress* m_pRemoteAddress;



//////////////////////////////////////////////////////////////////////////////////////// // StreamBuffer //////////////////////////////////////////////////////////////////////////////////////// class StreamBuffer : public std::basic_streambuf<char> { public: StreamBuffer( Socket& socket ); ~StreamBuffer();

protected: virtual int overflow( int c = EOF ); virtual int underflow(); virtual int sync();

private: int FlushOutput(); Socket& m_socket; char m_inputBuffer[512]; char m_outputBuffer[512]; };



//////////////////////////////////////////////////////////////////////////////////////// // Socket Streams //////////////////////////////////////////////////////////////////////////////////////// class ISocketStream : public std::basic_istream<char> { public: ISocketStream( Socket& socket ) : std::basic_istream<char>( new StreamBuffer( socket ) ) {} ~ISocketStream() { delete rdbuf(); } };

class OSocketStream : public std::basic_ostream<char> { public: OSocketStream( Socket& socket ) : std::basic_ostream<char>( new StreamBuffer( socket ) ) {} ~OSocketStream() { delete rdbuf(); } };

class IOSocketStream : public std::basic_iostream<char> { public: IOSocketStream( Socket& socket ) : std::basic_iostream<char>( new StreamBuffer( socket ) ) {} ~IOSocketStream() { delete rdbuf(); } };



ISocketStream* m_pIStream; OSocketStream* m_pOStream; IOSocketStream* m_pIOStream; };



#endif

Currently browsing [socket.zip] (6,762 bytes) - [internet.h] - (1,767 bytes)

////////////////////////////////////////////////////////////////////////////////////////
//
// Socket
// Copyright (C) 2000  Thierry Tremblay
//
// http://frogengine.net-connect.net/
//
////////////////////////////////////////////////////////////////////////////////////////

#ifndef TT_INTERNET_H
#define TT_INTERNET_H

#include "socket.h"



//////////////////////////////////////////////////////////////////////////////////////// // // InternetAddress // //////////////////////////////////////////////////////////////////////////////////////// class InternetAddress : public NetworkAddress, public sockaddr_in { public: InternetAddress( unsigned addr = INADDR_ANY, int port = 0 ); InternetAddress( const char* szHostName, const char* szService, const char* szProtocol = "tcp" ); InternetAddress( const char* szHostName, int port = 0 ); InternetAddress( const sockaddr_in& sa );

// Get methods bool GetHostName( char* buffer, int lenBuffer ) const; int GetPort() const { return ntohs( sin_port ); }

// Set methods void SetAddress( const char* szHostName ); void SetPort( const char* szService, const char* szProtocol = "tcp" ); void SetPort( int port ) { sin_port = htons( port ); }

// NetworkAddress interface operator void*() const { return (sockaddr_in*) this; } NetworkAddress* Clone() const { return new InternetAddress( *this ); } int GetFamily() const { return sin_family; } int GetSize() const { return sizeof(sockaddr_in); } sockaddr* GetSockAddr() const { return (sockaddr*)((sockaddr_in*)this); } };



#endif

Currently browsing [socket.zip] (6,762 bytes) - [main.cpp] - (585 bytes)

////////////////////////////////////////////////////////////////////////////////////////
//
// Socket
// Copyright (C) 2000  Thierry Tremblay
//
// http://frogengine.net-connect.net/
//
////////////////////////////////////////////////////////////////////////////////////////

#include "pop3.h"



int main( int argc, char* argv[] ) { POP3Protocol pop3;

int nbMessage, totalSize;

pop3.Open( "pop.server.com" ); pop3.Login( "username", "password" ); pop3.GetNbMessages( &nbMessage, &totalSize ); pop3.Quit(); pop3.Close();

return 0; }

Currently browsing [socket.zip] (6,762 bytes) - [pop3.cpp] - (2,184 bytes)

////////////////////////////////////////////////////////////////////////////////////////
//
// Socket
// Copyright (C) 2000  Thierry Tremblay
//
// http://frogengine.net-connect.net/
//
////////////////////////////////////////////////////////////////////////////////////////

#include "pop3.h"



//////////////////////////////////////////////////////////////////////////////////////// // // POP3Protocol // //////////////////////////////////////////////////////////////////////////////////////// POP3Protocol::POP3Protocol() : m_socket( AF_INET, SOCK_STREAM, IPPROTO_TCP ), m_stream( m_socket.GetIOStream() ) { }



POP3Protocol::~POP3Protocol() { }

bool POP3Protocol::Close() { return m_socket.Close(); }



bool POP3Protocol::GetNbMessages( int* pNbMessage, int* pTotalSize ) { m_stream << "STAT\r" << std::endl; m_stream.getline( m_buffer, sizeof(m_buffer) ); if (!LastCommandOK()) return false;

if (sscanf( m_buffer, "+OK %d %d", pNbMessage, pTotalSize ) != 2) return false;

return true; }



bool POP3Protocol::LastCommandOK() { return strncmp( m_buffer, "+OK", 3 ) == 0; }



bool POP3Protocol::Login( const char* szUser, const char* szPassword ) { m_stream << "USER " << szUser << "\r" << std::endl; m_stream.getline( m_buffer, sizeof(m_buffer) ); if (!LastCommandOK()) return false;

m_stream << "PASS " << szPassword << "\r" << std::endl; m_stream.getline( m_buffer, sizeof(m_buffer) ); if (!LastCommandOK()) return false;

return true; }



bool POP3Protocol::Open( const char* szHost, int port ) { if (!m_socket.Connect( InternetAddress( szHost, 110 )) ) return false;

m_socket.SetReceiveTimeout( 10 );

m_stream.getline( m_buffer, sizeof(m_buffer) ); if (!LastCommandOK()) { m_socket.Close(); return false; }

return true; }

bool POP3Protocol::Quit() { m_stream << "QUIT\r" << std::endl; m_stream.getline( m_buffer, sizeof(m_buffer) ); if (!LastCommandOK()) { m_socket.Close(); return false; }

return true; }

Currently browsing [socket.zip] (6,762 bytes) - [pop3.h] - (1,288 bytes)

////////////////////////////////////////////////////////////////////////////////////////
//
// Socket
// Copyright (C) 2000  Thierry Tremblay
//
// http://frogengine.net-connect.net/
//
////////////////////////////////////////////////////////////////////////////////////////

#ifndef TT_POP3_H
#define TT_POP3_H

#include "socket.h" #include "internet.h"



//////////////////////////////////////////////////////////////////////////////////////// // // POP3Protocol // // Reference: RFC 1725 // //////////////////////////////////////////////////////////////////////////////////////// class POP3Protocol { public: POP3Protocol(); ~POP3Protocol();

// Connects to a POP3 server bool Open( const char* szHost, int port = 110 ); // Closes the connection bool Close(); // Returns if last command succeeded or failed bool LastCommandOK();

// Login bool Login( const char* szUser, const char* szPassword );

// Quit bool Quit();

// Get the number of messages available and their total size bool GetNbMessages( int* pNbMessage, int* pTotalSize );

private:

char m_buffer[1024]; Socket m_socket; std::iostream& m_stream; };



#endif

Currently browsing [socket.zip] (6,762 bytes) - [socket.cpp] - (11,250 bytes)

////////////////////////////////////////////////////////////////////////////////////////
//
// Socket
// Copyright (C) 2000  Thierry Tremblay
//
// http://frogengine.net-connect.net/
//
////////////////////////////////////////////////////////////////////////////////////////

#include "socket.h"
#include "internet.h"
#include <cassert>



//////////////////////////////////////////////////////////////////////////////////////// // // Automatically link the proper library // //////////////////////////////////////////////////////////////////////////////////////// #ifdef _MSC_VER

#if WINSOCK_VERSION == 1 #pragma comment( lib, "wsock32" ) #elif WINSOCK_VERSION == 2 #pragma comment( lib, "ws2_32" ); #endif

#endif



//////////////////////////////////////////////////////////////////////////////////////// // // NetworkAddress - This is an interface to a socket address // //////////////////////////////////////////////////////////////////////////////////////// bool NetworkAddress::operator==( const NetworkAddress& other ) const { int size = GetSize(); if (size != other.GetSize()) return false;

return memcmp( *this, other, size ) == 0; }



bool NetworkAddress::operator!=( const NetworkAddress& other ) const { int size = GetSize(); if (size != other.GetSize()) return true;

return memcmp(*this, other, size) != 0; }



//////////////////////////////////////////////////////////////////////////////////////// // // Socket // //////////////////////////////////////////////////////////////////////////////////////// bool Socket::s_bInitialized = false;



Socket::Socket( int iAddressFamily, int type, int protocol ) : m_socket(INVALID_SOCKET), m_lastError(0), m_addressFamily(iAddressFamily), m_recvTimeout(-1), m_sendTimeout(-1), m_pLocalAddress(0), m_pRemoteAddress(0), m_pIStream(0), m_pOStream(0), m_pIOStream(0) { if (!s_bInitialized) { WSADATA data; WORD version;

#if WINSOCK_VERSION == 1 version = MAKEWORD(1,1); #elif WINSOCK_VERSION == 2 version = MAKEWORD(2,2); #endif

m_lastError = WSAStartup( version, &data ); if (m_lastError == 0) s_bInitialized = true; }

if (m_lastError == 0) { m_socket = socket( iAddressFamily, type, protocol ); if (m_socket == INVALID_SOCKET) { m_lastError = WSAGetLastError(); } } }



Socket::Socket( SOCKET socket ) : m_socket(socket), m_lastError(0), m_addressFamily(AF_UNSPEC), m_recvTimeout(-1), m_sendTimeout(-1), m_pLocalAddress(0), m_pRemoteAddress(0), m_pIStream(0), m_pOStream(0), m_pIOStream(0) { // Discover address family BYTE address[1024]; int lenAddress = sizeof(address);

if (getsockname( m_socket, (sockaddr*)address, &lenAddress ) == SOCKET_ERROR) { m_lastError = WSAGetLastError(); } else { m_addressFamily = ((SOCKADDR*)address)->sa_family; } }



Socket::~Socket() { if (m_pIStream) delete m_pIStream;

if (m_pOStream) delete m_pOStream;

if (m_pIOStream) delete m_pIOStream;

if (m_socket != INVALID_SOCKET) Close();

if (m_pLocalAddress) delete m_pLocalAddress;

if (m_pRemoteAddress) delete m_pRemoteAddress; }



bool Socket::Accept( Socket** ppSocket, NetworkAddress* pAddress ) const { SOCKET socket;

if (pAddress) { assert( pAddress->GetFamily() == m_addressFamily ); int size = pAddress->GetSize(); socket = accept( m_socket, *pAddress, &size ); } else { socket = accept( m_socket, 0, 0 ); } if (socket == INVALID_SOCKET) { *ppSocket = 0; m_lastError = WSAGetLastError(); return false; }

*ppSocket = new Socket( socket ); return true; }



bool Socket::Bind( const NetworkAddress& address ) const { if (bind( m_socket, address, address.GetSize() ) == SOCKET_ERROR) { m_lastError = WSAGetLastError(); return false; } return true; }



bool Socket::Close() { if (closesocket(m_socket) == SOCKET_ERROR) { m_lastError = WSAGetLastError(); return false; } m_socket = INVALID_SOCKET; return true; }



bool Socket::Connect( const NetworkAddress& address ) const { if (connect( m_socket, address, address.GetSize() ) == SOCKET_ERROR) { m_lastError = WSAGetLastError(); return false; } return true; }



NetworkAddress* Socket::CreateAddress() const { if (m_addressFamily == AF_INET) return new InternetAddress();

// Unsupported address family assert(0);

return 0; }



std::istream& Socket::GetInputStream() { if (m_pIStream==0) { m_pIStream = new ISocketStream( *this ); if (m_pOStream) m_pIStream->tie( m_pOStream ); }

return *m_pIStream; }



std::ostream& Socket::GetOutputStream() { if (m_pOStream==0) { m_pOStream = new OSocketStream( *this ); if (m_pIStream) m_pIStream->tie( m_pOStream ); }

return *m_pOStream; }



std::iostream& Socket::GetIOStream() { if (m_pIOStream==0) { m_pIOStream = new IOSocketStream( *this ); }

return *m_pIOStream; }



const NetworkAddress& Socket::GetLocalAddress() const { if (m_pLocalAddress==0) m_pLocalAddress = CreateAddress();

int lenAddress = m_pLocalAddress->GetSize(); if (getsockname( m_socket, m_pLocalAddress->GetSockAddr(), &lenAddress ) == SOCKET_ERROR) { m_lastError = WSAGetLastError(); }

return *m_pLocalAddress; }



const NetworkAddress& Socket::GetRemoteAddress() const { if (m_pRemoteAddress==0) m_pRemoteAddress = CreateAddress();

int lenAddress = m_pRemoteAddress->GetSize(); if (getpeername( m_socket, m_pRemoteAddress->GetSockAddr(), &lenAddress ) == SOCKET_ERROR) { m_lastError = WSAGetLastError(); }

return *m_pRemoteAddress; }



bool Socket::IsExceptionPending( int sec, int usec ) const { fd_set fds;

FD_ZERO( &fds ); FD_SET( m_socket, &fds );

timeval tv; tv.tv_sec = sec; tv.tv_usec = usec;

int result = select( m_socket+1, 0, 0, &fds, &tv ); if (result == 0 || result == SOCKET_ERROR) { m_lastError = WSAGetLastError(); return false; }

return true; }



bool Socket::IsReadReady( int sec, int usec ) const { fd_set fds;

FD_ZERO( &fds ); FD_SET( m_socket, &fds );

timeval tv; tv.tv_sec = sec; tv.tv_usec = usec;

int result = select( m_socket+1, &fds, 0, 0, &tv ); if (result == 0 || result == SOCKET_ERROR) { m_lastError = WSAGetLastError(); return false; }

return true; }



bool Socket::IsWriteReady( int sec, int usec ) const { fd_set fds;

FD_ZERO( &fds ); FD_SET( m_socket, &fds );

timeval tv; tv.tv_sec = sec; tv.tv_usec = usec;

int result = select( m_socket+1, 0, &fds, 0, &tv ); if (result == 0 || result == SOCKET_ERROR) { m_lastError = WSAGetLastError(); return false; }

return true; }



bool Socket::Listen( int queueSize ) const { if (listen( m_socket, queueSize ) == SOCKET_ERROR) { m_lastError = WSAGetLastError(); return false; } return true; }



int Socket::Receive( char* pBuffer, int lenBuffer, int flags ) const { if (m_recvTimeout >= 0 && !IsReadReady( m_recvTimeout )) return 0;

int nbRecv = recv( m_socket, pBuffer, lenBuffer, flags ); if (nbRecv == SOCKET_ERROR) { int error = WSAGetLastError(); if (error == WSAEMSGSIZE) return lenBuffer;

m_lastError = error; return 0; } return nbRecv; }



int Socket::ReceiveFrom( char* pBuffer, int lenBuffer, NetworkAddress& address, int flags ) const { if (m_recvTimeout >= 0 && !IsReadReady( m_recvTimeout )) return 0;

assert( address.GetFamily() == m_addressFamily );

int addressSize = address.GetSize(); int nbRecv = recvfrom( m_socket, pBuffer, lenBuffer, flags, address, &addressSize ); if (nbRecv == SOCKET_ERROR) { int error = WSAGetLastError(); if (error == WSAEMSGSIZE) return lenBuffer;

m_lastError = error; return 0; } return nbRecv; }



int Socket::Send( const char* pBuffer, int lenBuffer, int flags ) const { if (m_sendTimeout >= 0 && !IsWriteReady( m_sendTimeout )) return 0;

int nbSent = send( m_socket, pBuffer, lenBuffer, flags ); if (nbSent == SOCKET_ERROR) { m_lastError = WSAGetLastError(); return 0; } return nbSent; }



int Socket::SendTo( const char* pBuffer, int lenBuffer, const NetworkAddress& address, int flags ) const { if (m_sendTimeout >= 0 && !IsWriteReady( m_sendTimeout )) return 0;

int nbSent = sendto( m_socket, pBuffer, lenBuffer, flags, address, address.GetSize() ); if (nbSent == SOCKET_ERROR) { m_lastError = WSAGetLastError(); return 0; } return nbSent; }



bool Socket::SetBlocking( bool bBlocking ) { ULONG param = bBlocking ? 1 : 0;

if (ioctlsocket( m_socket, FIONBIO, ¶m ) == SOCKET_ERROR) { m_lastError = WSAGetLastError(); return false; } return true; }



//////////////////////////////////////////////////////////////////////////////////////// // // Socket::StreamBuffer // //////////////////////////////////////////////////////////////////////////////////////// Socket::StreamBuffer::StreamBuffer( Socket& socket ) : m_socket( socket ) { char* pBeginInput = m_inputBuffer; char* pEndInput = m_inputBuffer + sizeof(m_inputBuffer);

setg( pBeginInput, pEndInput, pEndInput ); setp( m_outputBuffer, m_outputBuffer + sizeof(m_outputBuffer) ); }



Socket::StreamBuffer::~StreamBuffer() { FlushOutput(); }



int Socket::StreamBuffer::FlushOutput() { // Return 0 if nothing to flush or success // Return EOF if couldnt flush if (pptr() < pbase()) return 0;

if (!m_socket.Send( pbase(), pptr() - pbase() )) return EOF;

setp( m_outputBuffer, m_outputBuffer + sizeof(m_outputBuffer) );

return 0; }



int Socket::StreamBuffer::overflow( int c ) { if (c == EOF) return FlushOutput();

*pptr() = c; pbump(1);

if (c == '\n' || pptr() >= epptr()) { if (FlushOutput() == EOF) return EOF; }

return c; }



int Socket::StreamBuffer::sync() { return FlushOutput(); }



int Socket::StreamBuffer::underflow() { if (gptr() < egptr()) return *(unsigned char*)gptr();

int nbRead = m_socket.Receive( m_inputBuffer, sizeof(m_inputBuffer) ); if (nbRead == 0) return EOF;

setg( eback(), eback(), eback() + nbRead );

return *(unsigned char*)gptr(); }

Currently browsing [socket.zip] (6,762 bytes) - [internet.cpp] - (2,315 bytes)

////////////////////////////////////////////////////////////////////////////////////////
//
// Socket
// Copyright (C) 2000  Thierry Tremblay
//
// http://frogengine.net-connect.net/
//
////////////////////////////////////////////////////////////////////////////////////////

#include "internet.h"



//////////////////////////////////////////////////////////////////////////////////////// // // InternetAddress // //////////////////////////////////////////////////////////////////////////////////////// InternetAddress::InternetAddress( unsigned addr, int port ) { sin_family = AF_INET; sin_addr.s_addr = htonl( addr ); sin_port = htons( port ); }



InternetAddress::InternetAddress( const char* szHostName, const char* szService, const char* szProtocol ) { SetAddress( szHostName ); SetPort( szService, szProtocol ); }



InternetAddress::InternetAddress( const char* szHostName, int port ) { SetAddress( szHostName ); sin_port = htons( port ); }



InternetAddress::InternetAddress( const sockaddr_in& sa ) { *(sockaddr_in*)this = sa; }



bool InternetAddress::GetHostName( char* buffer, int lenBuffer ) const { if (sin_addr.s_addr == INADDR_ANY) { if (gethostname( buffer, lenBuffer ) == SOCKET_ERROR) { buffer[0] = 0; return false; } } else { const HOSTENT* pHost = gethostbyaddr( (const char*)&sin_addr, sizeof(sin_addr), AF_INET); if (pHost == 0 || pHost->h_name == 0) { buffer[0] = 0; return false; }

strncpy( buffer, pHost->h_name, lenBuffer ); }

return true; }



void InternetAddress::SetAddress( const char* szHostName ) { sin_family = AF_INET; sin_addr.s_addr = inet_addr( szHostName ); if (sin_addr.s_addr == INADDR_NONE) { const HOSTENT* pHost = gethostbyname( szHostName ); if (pHost != 0) { sin_addr = *(IN_ADDR*)pHost->h_addr_list[0]; } } }



void InternetAddress::SetPort( const char* szService, const char* szProtocol ) { sin_port = 0;

const SERVENT* pService = getservbyname( szService, szProtocol ); if (pService != 0) { sin_port = pService->s_port; } }

The zip file viewer built into the Developer Toolbox made use of the zlib library, as well as the zlibdll source additions.

 

Copyright 1999-2008 (C) FLIPCODE.COM and/or the original content author(s). All rights reserved.
Please read our Terms, Conditions, and Privacy information.