]> git.saurik.com Git - wxWidgets.git/blob - src/common/sckipc.cpp
VC6 compilation fix after last change (thanks buildbot)
[wxWidgets.git] / src / common / sckipc.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/common/sckipc.cpp
3 // Purpose: Interprocess communication implementation (wxSocket version)
4 // Author: Julian Smart
5 // Modified by: Guilhem Lavaux (big rewrite) May 1997, 1998
6 // Guillermo Rodriguez (updated for wxSocket v2) Jan 2000
7 // (callbacks deprecated) Mar 2000
8 // Vadim Zeitlin (added support for Unix sockets) Apr 2002
9 // (use buffering, many fixes/cleanup) Oct 2008
10 // Created: 1993
11 // RCS-ID: $Id$
12 // Copyright: (c) Julian Smart 1993
13 // (c) Guilhem Lavaux 1997, 1998
14 // (c) 2000 Guillermo Rodriguez <guille@iies.es>
15 // Licence: wxWindows licence
16 /////////////////////////////////////////////////////////////////////////////
17
18 // ==========================================================================
19 // declarations
20 // ==========================================================================
21
22 // --------------------------------------------------------------------------
23 // headers
24 // --------------------------------------------------------------------------
25
26 // For compilers that support precompilation, includes "wx.h".
27 #include "wx/wxprec.h"
28
29 #ifdef __BORLANDC__
30 #pragma hdrstop
31 #endif
32
33 #if wxUSE_SOCKETS && wxUSE_IPC && wxUSE_STREAMS
34
35 #include "wx/sckipc.h"
36
37 #ifndef WX_PRECOMP
38 #include "wx/log.h"
39 #include "wx/event.h"
40 #include "wx/module.h"
41 #endif
42
43 #include <stdlib.h>
44 #include <stdio.h>
45 #include <errno.h>
46
47 #include "wx/socket.h"
48
49 // --------------------------------------------------------------------------
50 // macros and constants
51 // --------------------------------------------------------------------------
52
53 namespace
54 {
55
56 // Message codes
57 enum IPCCode
58 {
59 IPC_EXECUTE = 1,
60 IPC_REQUEST,
61 IPC_POKE,
62 IPC_ADVISE_START,
63 IPC_ADVISE_REQUEST,
64 IPC_ADVISE,
65 IPC_ADVISE_STOP,
66 IPC_REQUEST_REPLY,
67 IPC_FAIL,
68 IPC_CONNECT,
69 IPC_DISCONNECT
70 };
71
72 } // anonymous namespace
73
74 // headers needed for umask()
75 #ifdef __UNIX_LIKE__
76 #include <sys/types.h>
77 #include <sys/stat.h>
78 #endif // __UNIX_LIKE__
79
80 // ----------------------------------------------------------------------------
81 // private functions
82 // ----------------------------------------------------------------------------
83
84 // get the address object for the given server name, the caller must delete it
85 static wxSockAddress *
86 GetAddressFromName(const wxString& serverName,
87 const wxString& host = wxEmptyString)
88 {
89 // we always use INET sockets under non-Unix systems
90 #if defined(__UNIX__) && !defined(__WINDOWS__) && !defined(__WINE__)
91 // under Unix, if the server name looks like a path, create a AF_UNIX
92 // socket instead of AF_INET one
93 if ( serverName.Find(_T('/')) != wxNOT_FOUND )
94 {
95 wxUNIXaddress *addr = new wxUNIXaddress;
96 addr->Filename(serverName);
97
98 return addr;
99 }
100 #endif // Unix/!Unix
101 {
102 wxIPV4address *addr = new wxIPV4address;
103 addr->Service(serverName);
104 if ( !host.empty() )
105 {
106 addr->Hostname(host);
107 }
108
109 return addr;
110 }
111 }
112
113 // --------------------------------------------------------------------------
114 // wxTCPEventHandler stuff (private class)
115 // --------------------------------------------------------------------------
116
117 class wxTCPEventHandler : public wxEvtHandler
118 {
119 public:
120 wxTCPEventHandler() : wxEvtHandler() {}
121
122 void Client_OnRequest(wxSocketEvent& event);
123 void Server_OnRequest(wxSocketEvent& event);
124
125 DECLARE_EVENT_TABLE()
126 DECLARE_NO_COPY_CLASS(wxTCPEventHandler)
127 };
128
129 enum
130 {
131 _CLIENT_ONREQUEST_ID = 1000,
132 _SERVER_ONREQUEST_ID
133 };
134
135 static wxTCPEventHandler *gs_handler = NULL;
136
137 // --------------------------------------------------------------------------
138 // wxIPCSocketStreams
139 // --------------------------------------------------------------------------
140
141 #define USE_BUFFER
142
143 // this class contains the various (related) streams used by wxTCPConnection
144 // and also provides a way to read from the socket stream directly
145 //
146 // for writing to the stream use the IPCOutput class below
147 class wxIPCSocketStreams
148 {
149 public:
150 // ctor initializes all the streams on top of the given socket
151 //
152 // note that we use a bigger than default buffer size which matches the
153 // typical Ethernet MTU
154 wxIPCSocketStreams(wxSocketBase& sock)
155 : m_socketStream(sock),
156 #ifdef USE_BUFFER
157 m_bufferedOut(m_socketStream, 1500),
158 #else
159 m_bufferedOut(m_socketStream),
160 #endif
161 m_dataIn(m_socketStream),
162 m_dataOut(m_bufferedOut)
163 {
164 }
165
166 // expose the IO methods needed by IPC code (notice that writing is only
167 // done via IPCOutput)
168
169 // flush output
170 void Flush()
171 {
172 #ifdef USE_BUFFER
173 m_bufferedOut.Sync();
174 #endif
175 }
176
177 // simple wrappers around the functions with the same name in
178 // wxDataInputStream
179 wxUint8 Read8()
180 {
181 Flush();
182 return m_dataIn.Read8();
183 }
184
185 wxUint32 Read32()
186 {
187 Flush();
188 return m_dataIn.Read32();
189 }
190
191 wxString ReadString()
192 {
193 Flush();
194 return m_dataIn.ReadString();
195 }
196
197 // read arbitrary (size-prepended) data
198 //
199 // connection parameter is needed to call its GetBufferAtLeast() method
200 void *ReadData(wxConnectionBase *conn, size_t *size)
201 {
202 Flush();
203
204 wxCHECK_MSG( conn, NULL, "NULL connection parameter" );
205 wxCHECK_MSG( size, NULL, "NULL size parameter" );
206
207 *size = Read32();
208
209 void * const data = conn->GetBufferAtLeast(*size);
210 wxCHECK_MSG( data, NULL, "IPC buffer allocation failed" );
211
212 m_socketStream.Read(data, *size);
213
214 return data;
215 }
216
217 // same as above but for data preceded by the format
218 void *
219 ReadFormatData(wxConnectionBase *conn, wxIPCFormat *format, size_t *size)
220 {
221 wxCHECK_MSG( format, NULL, "NULL format parameter" );
222
223 *format = static_cast<wxIPCFormat>(Read8());
224
225 return ReadData(conn, size);
226 }
227
228
229 // these methods are only used by IPCOutput and not directly
230 wxDataOutputStream& GetDataOut() { return m_dataOut; }
231 wxOutputStream& GetUnformattedOut() { return m_bufferedOut; }
232
233 private:
234 // this is the low-level underlying stream using the connection socket
235 wxSocketStream m_socketStream;
236
237 // the buffered stream is used to avoid writing all pieces of an IPC
238 // request to the socket one by one but to instead do it all at once when
239 // we're done with it
240 #ifdef USE_BUFFER
241 wxBufferedOutputStream m_bufferedOut;
242 #else
243 wxOutputStream& m_bufferedOut;
244 #endif
245
246 // finally the data streams are used to be able to write typed data into
247 // the above streams easily
248 wxDataInputStream m_dataIn;
249 wxDataOutputStream m_dataOut;
250
251 DECLARE_NO_COPY_CLASS(wxIPCSocketStreams)
252 };
253
254 namespace
255 {
256
257 // an object of this class should be instantiated on the stack to write to the
258 // underlying socket stream
259 //
260 // this class is intentionally separated from wxIPCSocketStreams to ensure that
261 // Flush() is always called
262 class IPCOutput
263 {
264 public:
265 // construct an object associated with the given streams (which must have
266 // life time greater than ours as we keep a reference to it)
267 IPCOutput(wxIPCSocketStreams *streams)
268 : m_streams(*streams)
269 {
270 wxASSERT_MSG( streams, "NULL streams pointer" );
271 }
272
273 // dtor calls Flush() really sending the IPC data to the network
274 ~IPCOutput() { m_streams.Flush(); }
275
276
277 // write a byte
278 void Write8(wxUint8 i)
279 {
280 m_streams.GetDataOut().Write8(i);
281 }
282
283 // write the reply code and a string
284 void Write(IPCCode code, const wxString& str)
285 {
286 Write8(code);
287 m_streams.GetDataOut().WriteString(str);
288 }
289
290 // write the reply code, a string and a format in this order
291 void Write(IPCCode code, const wxString& str, wxIPCFormat format)
292 {
293 Write(code, str);
294 Write8(format);
295 }
296
297 // write arbitrary data
298 void WriteData(const void *data, size_t size)
299 {
300 m_streams.GetDataOut().Write32(size);
301 m_streams.GetUnformattedOut().Write(data, size);
302 }
303
304
305 private:
306 wxIPCSocketStreams& m_streams;
307
308 DECLARE_NO_COPY_CLASS(IPCOutput)
309 };
310
311 } // anonymous namespace
312
313 // ==========================================================================
314 // implementation
315 // ==========================================================================
316
317 IMPLEMENT_DYNAMIC_CLASS(wxTCPServer, wxServerBase)
318 IMPLEMENT_DYNAMIC_CLASS(wxTCPClient, wxClientBase)
319 IMPLEMENT_CLASS(wxTCPConnection, wxConnectionBase)
320
321 // --------------------------------------------------------------------------
322 // wxTCPClient
323 // --------------------------------------------------------------------------
324
325 wxTCPClient::wxTCPClient()
326 : wxClientBase()
327 {
328 }
329
330 bool wxTCPClient::ValidHost(const wxString& host)
331 {
332 wxIPV4address addr;
333
334 return addr.Hostname(host);
335 }
336
337 wxConnectionBase *wxTCPClient::MakeConnection(const wxString& host,
338 const wxString& serverName,
339 const wxString& topic)
340 {
341 wxSockAddress *addr = GetAddressFromName(serverName, host);
342 if ( !addr )
343 return NULL;
344
345 wxSocketClient * const client = new wxSocketClient(wxSOCKET_WAITALL);
346 wxIPCSocketStreams * const streams = new wxIPCSocketStreams(*client);
347
348 bool ok = client->Connect(*addr);
349 delete addr;
350
351 if ( ok )
352 {
353 // Send topic name, and enquire whether this has succeeded
354 IPCOutput(streams).Write(IPC_CONNECT, topic);
355
356 unsigned char msg = streams->Read8();
357
358 // OK! Confirmation.
359 if (msg == IPC_CONNECT)
360 {
361 wxTCPConnection *
362 connection = (wxTCPConnection *)OnMakeConnection ();
363
364 if (connection)
365 {
366 if (connection->IsKindOf(CLASSINFO(wxTCPConnection)))
367 {
368 connection->m_topic = topic;
369 connection->m_sock = client;
370 connection->m_streams = streams;
371 client->SetEventHandler(*gs_handler, _CLIENT_ONREQUEST_ID);
372 client->SetClientData(connection);
373 client->SetNotify(wxSOCKET_INPUT_FLAG | wxSOCKET_LOST_FLAG);
374 client->Notify(true);
375 return connection;
376 }
377 else
378 {
379 delete connection;
380 // and fall through to delete everything else
381 }
382 }
383 }
384 }
385
386 // Something went wrong, delete everything
387 delete streams;
388 client->Destroy();
389
390 return NULL;
391 }
392
393 wxConnectionBase *wxTCPClient::OnMakeConnection()
394 {
395 return new wxTCPConnection();
396 }
397
398 // --------------------------------------------------------------------------
399 // wxTCPServer
400 // --------------------------------------------------------------------------
401
402 wxTCPServer::wxTCPServer()
403 : wxServerBase()
404 {
405 m_server = NULL;
406 }
407
408 bool wxTCPServer::Create(const wxString& serverName)
409 {
410 // Destroy previous server, if any
411 if (m_server)
412 {
413 m_server->SetClientData(NULL);
414 m_server->Destroy();
415 m_server = NULL;
416 }
417
418 wxSockAddress *addr = GetAddressFromName(serverName);
419 if ( !addr )
420 return false;
421
422 #ifdef __UNIX_LIKE__
423 mode_t umaskOld;
424 if ( addr->Type() == wxSockAddress::UNIX )
425 {
426 // ensure that the file doesn't exist as otherwise calling socket()
427 // would fail
428 int rc = remove(serverName.fn_str());
429 if ( rc < 0 && errno != ENOENT )
430 {
431 delete addr;
432
433 return false;
434 }
435
436 // also set the umask to prevent the others from reading our file
437 umaskOld = umask(077);
438 }
439 else
440 {
441 // unused anyhow but shut down the compiler warnings
442 umaskOld = 0;
443 }
444 #endif // __UNIX_LIKE__
445
446 // Create a socket listening on the specified port (reusing it to allow
447 // restarting the server listening on the same port as was used by the
448 // previous instance of this server)
449 m_server = new wxSocketServer(*addr, wxSOCKET_WAITALL | wxSOCKET_REUSEADDR);
450
451 #ifdef __UNIX_LIKE__
452 if ( addr->Type() == wxSockAddress::UNIX )
453 {
454 // restore the umask
455 umask(umaskOld);
456
457 // save the file name to remove it later
458 m_filename = serverName;
459 }
460 #endif // __UNIX_LIKE__
461
462 delete addr;
463
464 if (!m_server->Ok())
465 {
466 m_server->Destroy();
467 m_server = NULL;
468
469 return false;
470 }
471
472 m_server->SetEventHandler(*gs_handler, _SERVER_ONREQUEST_ID);
473 m_server->SetClientData(this);
474 m_server->SetNotify(wxSOCKET_CONNECTION_FLAG);
475 m_server->Notify(true);
476
477 return true;
478 }
479
480 wxTCPServer::~wxTCPServer()
481 {
482 if ( m_server )
483 {
484 m_server->SetClientData(NULL);
485 m_server->Destroy();
486 }
487
488 #ifdef __UNIX_LIKE__
489 if ( !m_filename.empty() )
490 {
491 if ( remove(m_filename.fn_str()) != 0 )
492 {
493 wxLogDebug(_T("Stale AF_UNIX file '%s' left."), m_filename.c_str());
494 }
495 }
496 #endif // __UNIX_LIKE__
497 }
498
499 wxConnectionBase *
500 wxTCPServer::OnAcceptConnection(const wxString& WXUNUSED(topic))
501 {
502 return new wxTCPConnection();
503 }
504
505 // --------------------------------------------------------------------------
506 // wxTCPConnection
507 // --------------------------------------------------------------------------
508
509 void wxTCPConnection::Init()
510 {
511 m_sock = NULL;
512 m_streams = NULL;
513 }
514
515 wxTCPConnection::~wxTCPConnection()
516 {
517 Disconnect();
518
519 if ( m_sock )
520 {
521 m_sock->SetClientData(NULL);
522 m_sock->Destroy();
523 }
524
525 delete m_streams;
526 }
527
528 void wxTCPConnection::Compress(bool WXUNUSED(on))
529 {
530 // TODO
531 }
532
533 // Calls that CLIENT can make.
534 bool wxTCPConnection::Disconnect()
535 {
536 if ( !GetConnected() )
537 return true;
538
539 // Send the disconnect message to the peer.
540 IPCOutput(m_streams).Write8(IPC_DISCONNECT);
541
542 if ( m_sock )
543 {
544 m_sock->Notify(false);
545 m_sock->Close();
546 }
547
548 SetConnected(false);
549
550 return true;
551 }
552
553 bool wxTCPConnection::DoExecute(const void *data,
554 size_t size,
555 wxIPCFormat format)
556 {
557 if ( !m_sock->IsConnected() )
558 return false;
559
560 // Prepare EXECUTE message
561 IPCOutput out(m_streams);
562 out.Write8(IPC_EXECUTE);
563 out.Write8(format);
564
565 out.WriteData(data, size);
566
567 return true;
568 }
569
570 const void *wxTCPConnection::Request(const wxString& item,
571 size_t *size,
572 wxIPCFormat format)
573 {
574 if ( !m_sock->IsConnected() )
575 return NULL;
576
577 IPCOutput(m_streams).Write(IPC_REQUEST, item, format);
578
579 int ret = m_streams->Read8();
580 if ( ret == IPC_FAIL )
581 return NULL;
582
583 return m_streams->ReadData(this, size);
584 }
585
586 bool wxTCPConnection::DoPoke(const wxString& item,
587 const void *data,
588 size_t size,
589 wxIPCFormat format)
590 {
591 if ( !m_sock->IsConnected() )
592 return false;
593
594 IPCOutput out(m_streams);
595 out.Write(IPC_POKE, item, format);
596 out.WriteData(data, size);
597
598 return true;
599 }
600
601 bool wxTCPConnection::StartAdvise(const wxString& item)
602 {
603 if ( !m_sock->IsConnected() )
604 return false;
605
606 IPCOutput(m_streams).Write(IPC_ADVISE_START, item);
607
608 int ret = m_streams->Read8();
609 if (ret != IPC_FAIL)
610 return true;
611 else
612 return false;
613 }
614
615 bool wxTCPConnection::StopAdvise (const wxString& item)
616 {
617 if ( !m_sock->IsConnected() )
618 return false;
619
620 IPCOutput(m_streams).Write(IPC_ADVISE_STOP, item);
621
622 int ret = m_streams->Read8();
623
624 if (ret != IPC_FAIL)
625 return true;
626 else
627 return false;
628 }
629
630 // Calls that SERVER can make
631 bool wxTCPConnection::DoAdvise(const wxString& item,
632 const void *data,
633 size_t size,
634 wxIPCFormat format)
635 {
636 if ( !m_sock->IsConnected() )
637 return false;
638
639 IPCOutput out(m_streams);
640 out.Write(IPC_ADVISE, item, format);
641 out.WriteData(data, size);
642
643 return true;
644 }
645
646 // --------------------------------------------------------------------------
647 // wxTCPEventHandler (private class)
648 // --------------------------------------------------------------------------
649
650 BEGIN_EVENT_TABLE(wxTCPEventHandler, wxEvtHandler)
651 EVT_SOCKET(_CLIENT_ONREQUEST_ID, wxTCPEventHandler::Client_OnRequest)
652 EVT_SOCKET(_SERVER_ONREQUEST_ID, wxTCPEventHandler::Server_OnRequest)
653 END_EVENT_TABLE()
654
655 void wxTCPEventHandler::Client_OnRequest(wxSocketEvent &event)
656 {
657 wxSocketBase *sock = event.GetSocket();
658 if (!sock)
659 return ;
660
661 wxSocketNotify evt = event.GetSocketEvent();
662 wxTCPConnection *connection = (wxTCPConnection *)(sock->GetClientData());
663
664 // This socket is being deleted; skip this event
665 if (!connection)
666 return;
667
668 // We lost the connection: destroy everything
669 if (evt == wxSOCKET_LOST)
670 {
671 sock->Notify(false);
672 sock->Close();
673 connection->OnDisconnect();
674 return;
675 }
676
677 // Receive message number.
678 wxIPCSocketStreams * const streams = connection->m_streams;
679
680 const wxString topic = connection->m_topic;
681 wxString item;
682
683 const int msg = streams->Read8();
684 switch ( msg )
685 {
686 case IPC_EXECUTE:
687 {
688 wxIPCFormat format;
689 size_t size;
690 void * const
691 data = streams->ReadFormatData(connection, &format, &size);
692
693 connection->OnExecute(topic, data, size, format);
694 }
695 break;
696
697 case IPC_ADVISE:
698 {
699 item = streams->ReadString();
700
701 wxIPCFormat format;
702 size_t size;
703 void * const
704 data = streams->ReadFormatData(connection, &format, &size);
705
706 connection->OnAdvise(topic, item, data, size, format);
707 }
708 break;
709
710 case IPC_ADVISE_START:
711 {
712 item = streams->ReadString();
713
714 IPCOutput(streams).Write8(connection->OnStartAdvise(topic, item)
715 ? IPC_ADVISE_START
716 : IPC_FAIL);
717 }
718 break;
719
720 case IPC_ADVISE_STOP:
721 {
722 item = streams->ReadString();
723
724 IPCOutput(streams).Write8(connection->OnStopAdvise(topic, item)
725 ? IPC_ADVISE_STOP
726 : IPC_FAIL);
727 }
728 break;
729
730 case IPC_POKE:
731 {
732 item = streams->ReadString();
733 wxIPCFormat format = (wxIPCFormat)streams->Read8();
734
735 size_t size;
736 void * const data = streams->ReadData(connection, &size);
737
738 connection->OnPoke(topic, item, data, size, format);
739 }
740 break;
741
742 case IPC_REQUEST:
743 {
744 item = streams->ReadString();
745
746 wxIPCFormat format = (wxIPCFormat)streams->Read8();
747
748 size_t user_size = wxNO_LEN;
749 const void *user_data = connection->OnRequest(topic,
750 item,
751 &user_size,
752 format);
753
754 if ( !user_data )
755 {
756 IPCOutput(streams).Write8(IPC_FAIL);
757 break;
758 }
759
760 IPCOutput out(streams);
761 out.Write8(IPC_REQUEST_REPLY);
762
763 if ( user_size == wxNO_LEN )
764 {
765 switch ( format )
766 {
767 case wxIPC_TEXT:
768 case wxIPC_UTF8TEXT:
769 user_size = strlen((const char *)user_data) + 1; // includes final NUL
770 break;
771 case wxIPC_UNICODETEXT:
772 user_size = (wcslen((const wchar_t *)user_data) + 1) * sizeof(wchar_t); // includes final NUL
773 break;
774 default:
775 user_size = 0;
776 }
777 }
778
779 out.WriteData(user_data, user_size);
780 }
781 break;
782
783 case IPC_DISCONNECT:
784 sock->Notify(false);
785 sock->Close();
786 connection->SetConnected(false);
787 connection->OnDisconnect();
788 break;
789
790 default:
791 wxLogDebug("Unknown message code %d received.", msg);
792 IPCOutput(streams).Write8(IPC_FAIL);
793 break;
794 }
795 }
796
797 void wxTCPEventHandler::Server_OnRequest(wxSocketEvent &event)
798 {
799 wxSocketServer *server = (wxSocketServer *) event.GetSocket();
800 if (!server)
801 return ;
802 wxTCPServer *ipcserv = (wxTCPServer *) server->GetClientData();
803
804 // This socket is being deleted; skip this event
805 if (!ipcserv)
806 return;
807
808 if (event.GetSocketEvent() != wxSOCKET_CONNECTION)
809 return;
810
811 // Accept the connection, getting a new socket
812 wxSocketBase *sock = server->Accept();
813 if (!sock)
814 return ;
815 if (!sock->Ok())
816 {
817 sock->Destroy();
818 return;
819 }
820
821 wxIPCSocketStreams *streams = new wxIPCSocketStreams(*sock);
822
823 {
824 IPCOutput out(streams);
825
826 const int msg = streams->Read8();
827 if ( msg == IPC_CONNECT )
828 {
829 const wxString topic = streams->ReadString();
830
831 wxTCPConnection *new_connection =
832 (wxTCPConnection *)ipcserv->OnAcceptConnection (topic);
833
834 if (new_connection)
835 {
836 if (new_connection->IsKindOf(CLASSINFO(wxTCPConnection)))
837 {
838 // Acknowledge success
839 out.Write8(IPC_CONNECT);
840
841 new_connection->m_sock = sock;
842 new_connection->m_streams = streams;
843 new_connection->m_topic = topic;
844 sock->SetEventHandler(*gs_handler, _CLIENT_ONREQUEST_ID);
845 sock->SetClientData(new_connection);
846 sock->SetNotify(wxSOCKET_INPUT_FLAG | wxSOCKET_LOST_FLAG);
847 sock->Notify(true);
848 return;
849 }
850 else
851 {
852 delete new_connection;
853 // and fall through to delete everything else
854 }
855 }
856 }
857
858 // Something went wrong, send failure message and delete everything
859 out.Write8(IPC_FAIL);
860 } // IPCOutput object is destroyed here, before destroying stream
861
862 delete streams;
863 sock->Destroy();
864 }
865
866 // --------------------------------------------------------------------------
867 // wxTCPEventHandlerModule (private class)
868 // --------------------------------------------------------------------------
869
870 class wxTCPEventHandlerModule: public wxModule
871 {
872 public:
873 virtual bool OnInit() { gs_handler = new wxTCPEventHandler; return true; }
874 virtual void OnExit() { wxDELETE(gs_handler); }
875
876 DECLARE_DYNAMIC_CLASS(wxTCPEventHandlerModule)
877 };
878
879 IMPLEMENT_DYNAMIC_CLASS(wxTCPEventHandlerModule, wxModule)
880
881 #endif // wxUSE_SOCKETS && wxUSE_IPC && wxUSE_STREAMS