PUTTY TELNET CLIENT ACTIVEX CONTROL
This is an ActiveX client derived from the PuTTY Telnet client.
INTRODUCTION
For my product GoToTerminal, I wanted to add support for Telnet servers. GoToTerminal is an ActiveX/AJAX based online service, so I needed a Windows Telnet client in the form of an ActiveX control. As in the case of my VNC ActiveX client, I soon discovered that I had to code the control myself, because there was any free and ready to use alternative. So I searched in Google for an open-source Win32 Telnet client to convert to an ActiveX control and, in a matter of minutes, I found and just fell in love with Putty.
However, contrarily to the Ultr@VNC client used as the base for the other control, Putty’s implementation presented several problems that had to be faced and solved from the conversion perspective. For example, the original code is written entirely in C, with heavy use of global and function static variables to maintain the state of the client, whereas the original Ultr@VNC client is written in a strongly object oriented C++ code, with classes representing the client, the connections and so on. Moreover, the original Telnet client supported only one user session at a time (and, more important, per Win32 process), whereas GoToTerminal can host and create multiple ActiveX objects in the same Internet Explorer process representing multiple different connections. So I had to devise new and more complex techniques to convert the original single-connection-per-process code of Putty into an ActiveX object usable in GoToTerminal. The solutions I devised to resolve this problem are described below. The entire conversion took 5 days. This is a screenshot of the GoToTerminal Control Panel, using my Putty ActiveX Client:
OBJECT MODEL
These are the properties and methods exposed by the control:
[id(1)] HRESULT Connect();
[id(2)] HRESULT Disconnect();
[propget, id(3)] HRESULT Connected([out, retval] BOOL *pVal);
[propget, id(4)] HRESULT Server([out, retval] BSTR *pVal);
[propput, id(4)] HRESULT Server([in] BSTR newVal);
[id(5)] HRESULT ExecuteCommand( [in] BSTR cmdText, [out,retval] BSTR *pRetVal );
[propget, id(6)] HRESULT IsInitialized([out, retval] BOOL *pVal);
There are 2 events supported by the ActiveX control:
[id(1)] HRESULT OnConnected();
[id(2)] HRESULT OnDisconnected(BSTR reason);
CONVERSION DETAILS
As I said above, the conversion presented several problems. The major problem was that Putty is written in C, and that it makes large use of global and function static variables to maintain the state of the client. The solution I devised to resolve this problem was to take these global variables and to remap them to memory allocated and managed by the ActiveX control. This solution was ideal since it would have allowed to resolve the main limitation of the original Putty implementation, i.e. the fact that Putty allows only one user connection at a time, in the same Win32 process. In fact, taking the control over the allocation and storage of all the global (non-CRT) memory in the Putty’s process (i.e. all the memory NOT allocated in the stack or on the heap) would have allowed to eventually create different instances of the same global variable, thus allowing, as a consequence, to let the user connect to different Telnet clients from the same Win32 process.
The idea was to search for all the global and function static variables in the Putty’s source code, and to replace them with macros which would have invoked a function returning a dereferenced pointer. This function, taking the stringized name of the variable and its size as parameters, would have taken care of the allocation and storage of the corresponding chunk of memory.
To more easily and quickly localize all the global variables in the Putty source code, I used the program’s debug information (i.e. the .PDB file generated during the compilation). Since all global and function static variables are all mapped to the same PE memory section, tipically with name “.data” or “.idata”, I tried to obtain the map of these sections (i.e. the list of all the symbols mapped to a given section) from the .PDB file, actually with success. I remembered that such information could be easily obtained opening the .PDB file with DIA, i.e. the Microsoft Debug Interface Access Software Development Kit. Instead of coding a small C++ program calling the DIA SDK, I remembered that the example application accompanying the DIA SDK just produces a verbose text file containing just this kind of information.
Returning to the conversion problem, these are the resulting macros as defined in Putty.h, one for every data type in the original source code:
#define DEFINE_AX_GET_POINTER( name, type ) ( * (type*) AX_GET_POINTER( #name, sizeof( type ) ) )
#define DEFINE_AX_GET_POINTER_ARRAY( name, type, size ) ( (type) AX_GET_POINTER( #name, size ) )
#define DEFINE_AX_GET_POINTER_INT( name, type, init ) ( * (type*) AX_GET_POINTER_INT( #name, sizeof( type ), init ) )
#define DEFINE_AX_GET_POINTER_UINT( name, type, init ) ( * (type*) AX_GET_POINTER_UINT( #name, sizeof( type ), init ) )
So, according to this system, a global variable definition like this:
int flags;
has to be replaced in the Putty’s sources with this code:
#define flags DEFINE_AX_GET_POINTER( flags, int )
And this is the definition of the AX_GET_POINTER function, whose name is the result of the expansion of the DEFINE_AX_GET_POINTER macro above:
extern "C" VOID* AX_GET_POINTER( CHAR* name, INT size )
{
CCriticalSection::scope __access__( ( (CPuttyAxObj*) ::TlsGetValue( g_Tls.tls ) )->m_csGetPointerAccess );
// already allocated ?
VOID* pvExist = ::ReturnIfAllocated_NOLOCK( name, size );
if ( pvExist )
return pvExist;
// allocate.
return ::ForceAllocate_NOLOCK( name, size );
}
and the two functions called above, ReturnIfAllocated_NOLOCK and ForceAllocate_NOLOCK:
static VOID* ReturnIfAllocated_NOLOCK( CHAR* name, INT size )
{
// already allocated ?
std::vector< SMemRegionInfo >* pvec = & ( (CPuttyAxObj*) ::TlsGetValue( g_Tls.tls ) )->m_vsMemRegionInfo;
for( int i=0; i<pvec->size (); i ++ )
{
SMemRegionInfo*?? p = & (*pvec)[ i ];
if ( ::strcmp( p->pszName, name ) == 0 )
{
if ( p->iMemSize != size )
throw;
else
return p->pvMem;
}
}
return NULL;
}
static VOID* ForceAllocate_NOLOCK( CHAR* name, INT size )
{
// allocate.
std::vector< SMemRegionInfo >* pvec = & ( (CPuttyAxObj*) ::TlsGetValue( g_Tls.tls ) )->m_vsMemRegionInfo;
VOID* mem = ::malloc( size );
if ( mem == NULL )
return NULL;
::memset( mem, 0, size );
pvec->push_back( SMemRegionInfo( name, mem, size ) );
return mem;
}
As you can easily see from the code above, m_vsMemRegionInfo is the repository (of type vector< SMemRegionInfo >) containing the allocation information about all the memory symbols associated with a specific instance of the ActiveX control (CPuttyAxObj). The pointer to the corresponding CPuttyAxObj object is obtained from a Thread Local Storage slot, whose value is set and restored each time the execution reaches the Window Procedure of the Telnet client, thus allowing the reentrance in the Window Procedure itself. This is the definition of the Window Procedure associated with the Telnet client window:
extern "C" LRESULT CALLBACK WndProc_Ax(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
CPuttyAxObj* pAx = (CPuttyAxObj*) ::GetWindowLong( hwnd, GWL_USERDATA );
PVOID pvTlsOld = NULL;
if ( pAx )
{
pvTlsOld = ::TlsGetValue( g_Tls.tls );
::TlsSetValue( g_Tls.tls, pAx );
}
LRESULT lr = ::WndProc_Putty(hwnd, msg, wparam, lparam);
if ( pAx )
{
::TlsSetValue( g_Tls.tls, pvTlsOld );
}
return lr;
}
LICENSE
My ActiveX Putty client is licensed under the terms of the GNU General Public License (GPL).
DOWNLOAD
You can download the source code of the Putty ActiveX client from here.
Also take a look at my Ultr@VNC ActiveX Control.
|