From: Roger Meier Date: Fri, 10 Feb 2012 19:53:20 +0000 (+0000) Subject: THRIFT-1348 C++ Qt bindings X-Git-Tag: 0.9.1~448 X-Git-Url: https://source.supwisdom.com/gerrit/gitweb?a=commitdiff_plain;h=86e89865872cf051e8272e5e33ca669fa68f4f85;p=common%2Fthrift.git THRIFT-1348 C++ Qt bindings Patch: Doug Rosvick and Vitali Lovich git-svn-id: https://svn.apache.org/repos/asf/thrift/trunk@1242900 13f79535-47bb-0310-9956-ffa450edef68 --- diff --git a/configure.ac b/configure.ac index db1591f8..27ef8d42 100755 --- a/configure.ac +++ b/configure.ac @@ -118,10 +118,17 @@ if test "$with_cpp" = "yes"; then AX_LIB_ZLIB([1.2.3]) have_zlib=$success + + PKG_CHECK_MODULES([QT], [QtCore >= 4.3, QtNetwork >= 4.3], have_qt=yes, have_qt=no) + if test "$have_qt" = "yes"; then + AC_PATH_PROGS([QT_MOC], [moc-qt4 moc]) + have_qt=$success + fi fi AM_CONDITIONAL([WITH_CPP], [test "$have_cpp" = "yes"]) AM_CONDITIONAL([AMX_HAVE_LIBEVENT], [test "$have_libevent" = "yes"]) AM_CONDITIONAL([AMX_HAVE_ZLIB], [test "$have_zlib" = "yes"]) +AM_CONDITIONAL([AMX_HAVE_QT], [test "$have_qt" = "yes"]) AX_THRIFT_LIB(c_glib, [C (GLib)], no) if test "$with_c_glib" = "yes"; then @@ -477,6 +484,7 @@ AC_CONFIG_FILES([ lib/cpp/test/Makefile lib/cpp/thrift-nb.pc lib/cpp/thrift-z.pc + lib/cpp/thrift-qt.pc lib/cpp/thrift.pc lib/c_glib/Makefile lib/c_glib/thrift_c_glib.pc @@ -510,7 +518,7 @@ AC_OUTPUT echo echo "$PACKAGE $VERSION" echo -echo "Building code generators ..... :$thrift_generators" +echo "Building code generators ..... : $thrift_generators" echo echo "Building C++ Library ......... : $have_cpp" echo "Building C (GLib) Library .... : $have_c_glib" @@ -525,51 +533,62 @@ echo "Building Erlang Library ...... : $have_erlang" echo "Building Go Library .......... : $have_go" if test "$have_cpp" = "yes" ; then echo - echo "Building TZlibTransport ...... : $have_zlib" - echo "Building TNonblockingServer .. : $have_libevent" + echo "C++ Library:" + echo " Build TZlibTransport ...... : $have_zlib" + echo " Build TNonblockingServer .. : $have_libevent" + echo " Build TQTcpServer (Qt) .... : $have_qt" fi if test "$have_java" = "yes" ; then echo - echo "Using javac .................. : $JAVAC" - echo "Using java ................... : $JAVA" - echo "Using ant .................... : $ANT" + echo "Java Library:" + echo " Using javac ............... : $JAVAC" + echo " Using java ................ : $JAVA" + echo " Using ant ................. : $ANT" fi if test "$have_csharp" = "yes" ; then echo - echo "Using .NET 3.5 ............... : $net_3_5" + echo "C# Library:" + echo " Using .NET 3.5 ............ : $net_3_5" fi if test "$have_python" = "yes" ; then echo - echo "Using Python ................. : $PYTHON" + echo "Python Library:" + echo " Using Python .............. : $PYTHON" fi if test "$have_php" = "yes" ; then echo - echo "Using php-config ............. : $PHP_CONFIG" + echo "PHP Library:" + echo " Using php-config .......... : $PHP_CONFIG" fi if test "$have_ruby" = "yes" ; then echo - echo "Using Ruby ................... : $RUBY" + echo "Ruby Library:" + echo " Using Ruby ................ : $RUBY" fi if test "$have_haskell" = "yes" ; then echo - echo "Using Haskell ................ : $RUNHASKELL" - echo "Using Cabal .................. : $CABAL" + echo "Haskell Library:" + echo " Using Haskell ............. : $RUNHASKELL" + echo " Using Cabal ............... : $CABAL" fi if test "$have_perl" = "yes" ; then echo - echo "Using Perl ................... : $PERL" + echo "Perl Library:" + echo " Using Perl ................ : $PERL" fi if test "$have_erlang" = "yes" ; then echo - echo "Using erlc ................... : $ERLC" + echo "Erlang Library:" + echo " Using erlc ................ : $ERLC" fi if test "$have_go" = "yes" ; then echo - echo "Using GOROOT.................. : $GOROOT" - echo "Using GOBIN................... : $GOBIN" - echo "Using GOARCH.................. : $GOARCH" - echo "Using GO Compiler............. : $GO_C" - echo "Using GO Linker............... : $GO_L" + echo "Go Library:" + echo " Using GOROOT............... : $GOROOT" + echo " Using GOBIN................ : $GOBIN" + echo " Using GOARCH............... : $GOARCH" + echo " Using GO Compiler.......... : $GO_C" + echo " Using GO Linker............ : $GO_L" fi echo echo "If something is missing that you think should be present," diff --git a/lib/cpp/Makefile.am b/lib/cpp/Makefile.am index f8093ba2..188b3716 100644 --- a/lib/cpp/Makefile.am +++ b/lib/cpp/Makefile.am @@ -17,6 +17,9 @@ # under the License. # +moc_%.cpp: %.h + $(QT_MOC) $(QT_CFLAGS) $< -o $@ + SUBDIRS = . if WITH_TESTS @@ -39,6 +42,10 @@ if AMX_HAVE_ZLIB lib_LTLIBRARIES += libthriftz.la pkgconfig_DATA += thrift-z.pc endif +if AMX_HAVE_QT +lib_LTLIBRARIES += libthriftqt.la +pkgconfig_DATA += thrift-qt.pc +endif AM_CXXFLAGS = -Wall AM_CPPFLAGS = $(BOOST_CPPFLAGS) -I$(srcdir)/src @@ -94,18 +101,27 @@ libthriftnb_la_SOURCES = src/server/TNonblockingServer.cpp \ libthriftz_la_SOURCES = src/transport/TZlibTransport.cpp +libthriftqt_la_MOC = src/qt/moc_TQTcpServer.cpp +libthriftqt_la_SOURCES = $(libthriftqt_la_MOC) \ + src/qt/TQIODeviceTransport.cpp \ + src/qt/TQTcpServer.cpp +CLEANFILES = $(libthriftqt_la_MOC) + # Flags for the various libraries libthriftnb_la_CPPFLAGS = $(AM_CPPFLAGS) $(LIBEVENT_CPPFLAGS) libthriftz_la_CPPFLAGS = $(AM_CPPFLAGS) $(ZLIB_CPPFLAGS) +libthriftqt_la_CPPFLAGS = $(AM_CPPFLAGS) $(QT_CFLAGS) libthriftnb_la_CXXFLAGS = $(AM_CXXFLAGS) libthriftz_la_CXXFLAGS = $(AM_CXXFLAGS) +libthriftqt_la_CXXFLAGS = $(AM_CXXFLAGS) libthriftnb_la_LDFLAGS = -release $(VERSION) $(BOOST_LDFLAGS) libthriftz_la_LDFLAGS = -release $(VERSION) $(BOOST_LDFLAGS) +libthriftqt_la_LDFLAGS = -release $(VERSION) $(BOOST_LDFLAGS) $(QT_LIBS) include_thriftdir = $(includedir)/thrift include_thrift_HEADERS = \ $(top_builddir)/config.h \ - src/TDispatchProcessor.h \ + src/TDispatchProcessor.h \ src/Thrift.h \ src/TReflectionLocal.h \ src/TProcessor.h \ @@ -186,6 +202,11 @@ include_async_HEADERS = \ src/async/TEvhttpClientChannel.h \ src/async/TEvhttpServer.h +include_qtdir = $(include_thriftdir)/qt +include_qt_HEADERS = \ + src/qt/TQIODeviceTransport.h \ + src/qt/TQTcpServer.h + noinst_PROGRAMS = concurrency_test @@ -212,4 +233,5 @@ EXTRA_DIST = \ thrift-nb.pc.in \ thrift.pc.in \ thrift-z.pc.in \ + thrift-qt.pc.in \ $(WINDOWS_DIST) diff --git a/lib/cpp/src/qt/TQIODeviceTransport.cpp b/lib/cpp/src/qt/TQIODeviceTransport.cpp new file mode 100644 index 00000000..c5b53a8f --- /dev/null +++ b/lib/cpp/src/qt/TQIODeviceTransport.cpp @@ -0,0 +1,155 @@ +#include "TQIODeviceTransport.h" + +#include + +#include + +#include + +namespace apache { namespace thrift { namespace transport { + using boost::shared_ptr; + + TQIODeviceTransport::TQIODeviceTransport(shared_ptr dev) + : dev_(dev) + { + } + + TQIODeviceTransport::~TQIODeviceTransport() + { + dev_->close(); + } + + void TQIODeviceTransport::open() + { + if (!isOpen()) { + throw TTransportException(TTransportException::NOT_OPEN, + "open(): underlying QIODevice isn't open"); + } + } + + bool TQIODeviceTransport::isOpen() + { + return dev_->isOpen(); + } + + bool TQIODeviceTransport::peek() + { + return dev_->bytesAvailable() > 0; + } + + void TQIODeviceTransport::close() + { + dev_->close(); + } + + uint32_t TQIODeviceTransport::readAll(uint8_t* buf, uint32_t len) { + uint32_t requestLen = len; + while (len) { + uint32_t readSize; + try { + readSize = read(buf, len); + } catch (...) { + if (len != requestLen) { + // something read already + return requestLen - len; + } + // error but nothing read yet + throw; + } + if (readSize == 0) { + // dev_->waitForReadyRead(50); + } else { + buf += readSize; + len -= readSize; + } + } + return requestLen; + } + + uint32_t TQIODeviceTransport::read(uint8_t* buf, uint32_t len) + { + uint32_t actualSize; + qint64 readSize; + + if (!dev_->isOpen()) { + throw TTransportException(TTransportException::NOT_OPEN, + "read(): underlying QIODevice is not open"); + } + + actualSize = (uint32_t)std::min((qint64)len, dev_->bytesAvailable()); + readSize = dev_->read(reinterpret_cast(buf), len); + + if (readSize < 0) { + QAbstractSocket* socket; + if ((socket = qobject_cast(dev_.get()))) { + throw TTransportException(TTransportException::UNKNOWN, + "Failed to read() from QAbstractSocket", + socket->error()); + } + throw TTransportException(TTransportException::UNKNOWN, + "Failed to read from from QIODevice"); + } + + return (uint32_t)readSize; + } + + void TQIODeviceTransport::write(const uint8_t* buf, uint32_t len) + { + while (len) { + uint32_t written = write_partial(buf, len); + len -= written; + // dev_->waitForBytesWritten(50); + } + } + + uint32_t TQIODeviceTransport::write_partial(const uint8_t* buf, uint32_t len) + { + qint64 written; + + if (!dev_->isOpen()) { + throw TTransportException(TTransportException::NOT_OPEN, + "write_partial(): underlying QIODevice is not open"); + } + + written = dev_->write(reinterpret_cast(buf), len); + if (written < 0) { + QAbstractSocket* socket; + if ((socket = qobject_cast(dev_.get()))) { + throw TTransportException(TTransportException::UNKNOWN, + "write_partial(): failed to write to QAbstractSocket", socket->error()); + } + + throw TTransportException(TTransportException::UNKNOWN, + "write_partial(): failed to write to underlying QIODevice"); + } + + return (uint32_t)written; + } + + void TQIODeviceTransport::flush() + { + if (!dev_->isOpen()) { + throw TTransportException(TTransportException::NOT_OPEN, + "flush(): underlying QIODevice is not open"); + } + + QAbstractSocket* socket; + + if ((socket = qobject_cast(dev_.get()))) { + socket->flush(); + } else { + dev_->waitForBytesWritten(1); + } + } + + uint8_t* TQIODeviceTransport::borrow(uint8_t* buf, uint32_t* len) + { + return NULL; + } + + void TQIODeviceTransport::consume(uint32_t len) + { + throw TTransportException(TTransportException::UNKNOWN); + } +}}} // apache::thrift::transport + diff --git a/lib/cpp/src/qt/TQIODeviceTransport.h b/lib/cpp/src/qt/TQIODeviceTransport.h new file mode 100644 index 00000000..8ce7c782 --- /dev/null +++ b/lib/cpp/src/qt/TQIODeviceTransport.h @@ -0,0 +1,48 @@ +#ifndef _THRIFT_ASYNC_TQIODEVICE_TRANSPORT_H_ +#define _THRIFT_ASYNC_TQIODEVICE_TRANSPORT_H_ 1 + +#include + +#include + +#include + +namespace apache { namespace thrift { namespace protocol { +class TProtocol; +}}} // apache::thrift::protocol + +namespace apache { namespace thrift { namespace transport { + + class TQIODeviceTransport : public apache::thrift::transport::TVirtualTransport { + public: + explicit TQIODeviceTransport(boost::shared_ptr dev); + ~TQIODeviceTransport(); + + void open(); + + bool isOpen(); + + bool peek(); + + void close(); + + uint32_t readAll(uint8_t *buf, uint32_t len); + + uint32_t read(uint8_t* buf, uint32_t len); + + void write(const uint8_t* buf, uint32_t len); + + uint32_t write_partial(const uint8_t* buf, uint32_t len); + + void flush(); + + uint8_t* borrow(uint8_t* buf, uint32_t* len); + void consume(uint32_t len); + + private: + boost::shared_ptr dev_; + }; +}}} // apache::thrift::transport + +#endif // #ifndef _THRIFT_ASYNC_TQIODEVICE_TRANSPORT_H_ + diff --git a/lib/cpp/src/qt/TQTcpServer.cpp b/lib/cpp/src/qt/TQTcpServer.cpp new file mode 100644 index 00000000..65ad68dc --- /dev/null +++ b/lib/cpp/src/qt/TQTcpServer.cpp @@ -0,0 +1,140 @@ +#include "TQTcpServer.h" + +#include +#include + +#include + +#include "TQIODeviceTransport.h" + +using boost::shared_ptr; +using apache::thrift::protocol::TProtocol; +using apache::thrift::protocol::TProtocolFactory; +using apache::thrift::transport::TTransport; +using apache::thrift::transport::TTransportException; +using apache::thrift::transport::TQIODeviceTransport; +using std::tr1::function; +using std::tr1::bind; + +QT_USE_NAMESPACE + +namespace apache { namespace thrift { namespace async { + +struct TQTcpServer::ConnectionContext { + shared_ptr connection_; + shared_ptr transport_; + shared_ptr iprot_; + shared_ptr oprot_; + + explicit ConnectionContext(shared_ptr connection, + shared_ptr transport, + shared_ptr iprot, + shared_ptr oprot) + : connection_(connection) + , transport_(transport) + , iprot_(iprot) + , oprot_(oprot) + {} +}; + +TQTcpServer::TQTcpServer(shared_ptr server, + shared_ptr processor, + shared_ptr pfact, + QObject* parent) + : QObject(parent) + , server_(server) + , processor_(processor) + , pfact_(pfact) +{ + connect(server.get(), SIGNAL(newConnection()), SLOT(processIncoming())); +} + +TQTcpServer::~TQTcpServer() +{ +} + +void TQTcpServer::processIncoming() +{ + while (server_->hasPendingConnections()) { + // take ownership of the QTcpSocket; technically it could be deleted + // when the QTcpServer is destroyed, but any real app should delete this + // class before deleting the QTcpServer that we are using + shared_ptr connection(server_->nextPendingConnection()); + + shared_ptr transport; + shared_ptr iprot; + shared_ptr oprot; + + try { + transport = shared_ptr(new TQIODeviceTransport(connection)); + iprot = shared_ptr(pfact_->getProtocol(transport)); + oprot = shared_ptr(pfact_->getProtocol(transport)); + } catch(...) { + qWarning("[TQTcpServer] Failed to initialize transports/protocols"); + continue; + } + + ctxMap_[connection.get()] = + shared_ptr( + new ConnectionContext(connection, transport, iprot, oprot)); + + connect(connection.get(), SIGNAL(readyRead()), SLOT(beginDecode())); + + // need to use QueuedConnection since we will be deleting the socket in the slot + connect(connection.get(), SIGNAL(disconnected()), SLOT(socketClosed()), + Qt::QueuedConnection); + } +} + +void TQTcpServer::beginDecode() +{ + QTcpSocket* connection(qobject_cast(sender())); + Q_ASSERT(connection); + + if (ctxMap_.find(connection) == ctxMap_.end()) + { + qWarning("[TQTcpServer] Got data on an unknown QTcpSocket"); + return; + } + + shared_ptr ctx = ctxMap_[connection]; + + try { + processor_->process( + bind(&TQTcpServer::finish, this, + ctx, std::tr1::placeholders::_1), + ctx->iprot_, ctx->oprot_); + } catch(const TTransportException& ex) { + qWarning("[TQTcpServer] TTransportException during processing: '%s'", + ex.what()); + ctxMap_.erase(connection); + } catch(...) { + qWarning("[TQTcpServer] Unknown processor exception"); + ctxMap_.erase(connection); + } +} + +void TQTcpServer::socketClosed() +{ + QTcpSocket* connection(qobject_cast(sender())); + Q_ASSERT(connection); + + if (ctxMap_.find(connection) == ctxMap_.end()) + { + qWarning("[TQTcpServer] Unknown QTcpSocket closed"); + return; + } + + ctxMap_.erase(connection); +} + +void TQTcpServer::finish(shared_ptr ctx, bool healthy) +{ + if (!healthy) + { + qWarning("[TQTcpServer] Processor failed to process data successfully"); + ctxMap_.erase(ctx->connection_.get()); + } +} + +}}} // apache::thrift::async diff --git a/lib/cpp/src/qt/TQTcpServer.h b/lib/cpp/src/qt/TQTcpServer.h new file mode 100644 index 00000000..82bec399 --- /dev/null +++ b/lib/cpp/src/qt/TQTcpServer.h @@ -0,0 +1,48 @@ +#ifndef _THRIFT_TASYNC_QTCP_SERVER_H_ +#define _THRIFT_TASYNC_QTCP_SERVER_H_ + +#include +#include + +#include + +#include + +namespace apache { namespace thrift { namespace protocol { +class TProtocol; +class TProtocolFactory; +}}} // apache::thrift::protocol + +namespace apache { namespace thrift { namespace async { + +class TAsyncProcessor; + +class TQTcpServer : public QObject { + Q_OBJECT + public: + TQTcpServer(boost::shared_ptr server, + boost::shared_ptr processor, + boost::shared_ptr protocolFactory, + QT_PREPEND_NAMESPACE(QObject)* parent = NULL); + virtual ~TQTcpServer(); + + private Q_SLOTS: + void processIncoming(); + void beginDecode(); + void socketClosed(); + + private: + class ConnectionContext; + + void finish(boost::shared_ptr ctx, bool healthy); + + boost::shared_ptr server_; + boost::shared_ptr processor_; + boost::shared_ptr pfact_; + + std::map > ctxMap_; +}; + +}}} // apache::thrift::async + +#endif // #ifndef _THRIFT_TASYNC_QTCP_SERVER_H_ diff --git a/lib/cpp/src/qt/moc_TQTcpServer.cpp b/lib/cpp/src/qt/moc_TQTcpServer.cpp new file mode 100644 index 00000000..0e94da92 --- /dev/null +++ b/lib/cpp/src/qt/moc_TQTcpServer.cpp @@ -0,0 +1,85 @@ +/**************************************************************************** +** Meta object code from reading C++ file 'TQTcpServer.h' +** +** Created: Thu Feb 9 20:47:09 2012 +** by: The Qt Meta Object Compiler version 62 (Qt 4.6.3) +** +** WARNING! All changes made in this file will be lost! +*****************************************************************************/ + +#include "TQTcpServer.h" +#if !defined(Q_MOC_OUTPUT_REVISION) +#error "The header file 'TQTcpServer.h' doesn't include ." +#elif Q_MOC_OUTPUT_REVISION != 62 +#error "This file was generated using the moc from 4.6.3. It" +#error "cannot be used with the include files from this version of Qt." +#error "(The moc has changed too much.)" +#endif + +QT_BEGIN_MOC_NAMESPACE +static const uint qt_meta_data_apache__thrift__async__TQTcpServer[] = { + + // content: + 4, // revision + 0, // classname + 0, 0, // classinfo + 3, 14, // methods + 0, 0, // properties + 0, 0, // enums/sets + 0, 0, // constructors + 0, // flags + 0, // signalCount + + // slots: signature, parameters, type, tag, flags + 36, 35, 35, 35, 0x08, + 54, 35, 35, 35, 0x08, + 68, 35, 35, 35, 0x08, + + 0 // eod +}; + +static const char qt_meta_stringdata_apache__thrift__async__TQTcpServer[] = { + "apache::thrift::async::TQTcpServer\0\0" + "processIncoming()\0beginDecode()\0" + "socketClosed()\0" +}; + +const QMetaObject apache::thrift::async::TQTcpServer::staticMetaObject = { + { &QObject::staticMetaObject, qt_meta_stringdata_apache__thrift__async__TQTcpServer, + qt_meta_data_apache__thrift__async__TQTcpServer, 0 } +}; + +#ifdef Q_NO_DATA_RELOCATION +const QMetaObject &apache::thrift::async::TQTcpServer::getStaticMetaObject() { return staticMetaObject; } +#endif //Q_NO_DATA_RELOCATION + +const QMetaObject *apache::thrift::async::TQTcpServer::metaObject() const +{ + return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; +} + +void *apache::thrift::async::TQTcpServer::qt_metacast(const char *_clname) +{ + if (!_clname) return 0; + if (!strcmp(_clname, qt_meta_stringdata_apache__thrift__async__TQTcpServer)) + return static_cast(const_cast< TQTcpServer*>(this)); + return QObject::qt_metacast(_clname); +} + +int apache::thrift::async::TQTcpServer::qt_metacall(QMetaObject::Call _c, int _id, void **_a) +{ + _id = QObject::qt_metacall(_c, _id, _a); + if (_id < 0) + return _id; + if (_c == QMetaObject::InvokeMetaMethod) { + switch (_id) { + case 0: processIncoming(); break; + case 1: beginDecode(); break; + case 2: socketClosed(); break; + default: ; + } + _id -= 3; + } + return _id; +} +QT_END_MOC_NAMESPACE diff --git a/lib/cpp/thrift-qt.pc.in b/lib/cpp/thrift-qt.pc.in new file mode 100644 index 00000000..846e3623 --- /dev/null +++ b/lib/cpp/thrift-qt.pc.in @@ -0,0 +1,30 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: Thrift +Description: Thrift Qt API +Version: @VERSION@ +Requires: thrift = @VERSION@ +Libs: -L${libdir} -lthriftqt +Cflags: -I${includedir}/thrift