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
This commit is contained in:
Roger Meier 2012-02-10 19:53:20 +00:00
parent afb9f970a4
commit 86e8986587
8 changed files with 567 additions and 20 deletions

View File

@ -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,"

View File

@ -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)

View File

@ -0,0 +1,155 @@
#include "TQIODeviceTransport.h"
#include <transport/TBufferTransports.h>
#include <QAbstractSocket>
#include <iostream>
namespace apache { namespace thrift { namespace transport {
using boost::shared_ptr;
TQIODeviceTransport::TQIODeviceTransport(shared_ptr<QIODevice> 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<char *>(buf), len);
if (readSize < 0) {
QAbstractSocket* socket;
if ((socket = qobject_cast<QAbstractSocket* >(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<const char*>(buf), len);
if (written < 0) {
QAbstractSocket* socket;
if ((socket = qobject_cast<QAbstractSocket*>(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<QAbstractSocket*>(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

View File

@ -0,0 +1,48 @@
#ifndef _THRIFT_ASYNC_TQIODEVICE_TRANSPORT_H_
#define _THRIFT_ASYNC_TQIODEVICE_TRANSPORT_H_ 1
#include <QIODevice>
#include <boost/shared_ptr.hpp>
#include <transport/TVirtualTransport.h>
namespace apache { namespace thrift { namespace protocol {
class TProtocol;
}}} // apache::thrift::protocol
namespace apache { namespace thrift { namespace transport {
class TQIODeviceTransport : public apache::thrift::transport::TVirtualTransport<TQIODeviceTransport> {
public:
explicit TQIODeviceTransport(boost::shared_ptr<QIODevice> 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<QIODevice> dev_;
};
}}} // apache::thrift::transport
#endif // #ifndef _THRIFT_ASYNC_TQIODEVICE_TRANSPORT_H_

View File

@ -0,0 +1,140 @@
#include "TQTcpServer.h"
#include <protocol/TProtocol.h>
#include <async/TAsyncProcessor.h>
#include <QTcpSocket>
#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<QTcpSocket> connection_;
shared_ptr<TTransport> transport_;
shared_ptr<TProtocol> iprot_;
shared_ptr<TProtocol> oprot_;
explicit ConnectionContext(shared_ptr<QTcpSocket> connection,
shared_ptr<TTransport> transport,
shared_ptr<TProtocol> iprot,
shared_ptr<TProtocol> oprot)
: connection_(connection)
, transport_(transport)
, iprot_(iprot)
, oprot_(oprot)
{}
};
TQTcpServer::TQTcpServer(shared_ptr<QTcpServer> server,
shared_ptr<TAsyncProcessor> processor,
shared_ptr<TProtocolFactory> 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<QTcpSocket> connection(server_->nextPendingConnection());
shared_ptr<TTransport> transport;
shared_ptr<TProtocol> iprot;
shared_ptr<TProtocol> oprot;
try {
transport = shared_ptr<TTransport>(new TQIODeviceTransport(connection));
iprot = shared_ptr<TProtocol>(pfact_->getProtocol(transport));
oprot = shared_ptr<TProtocol>(pfact_->getProtocol(transport));
} catch(...) {
qWarning("[TQTcpServer] Failed to initialize transports/protocols");
continue;
}
ctxMap_[connection.get()] =
shared_ptr<ConnectionContext>(
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<QTcpSocket*>(sender()));
Q_ASSERT(connection);
if (ctxMap_.find(connection) == ctxMap_.end())
{
qWarning("[TQTcpServer] Got data on an unknown QTcpSocket");
return;
}
shared_ptr<ConnectionContext> 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<QTcpSocket*>(sender()));
Q_ASSERT(connection);
if (ctxMap_.find(connection) == ctxMap_.end())
{
qWarning("[TQTcpServer] Unknown QTcpSocket closed");
return;
}
ctxMap_.erase(connection);
}
void TQTcpServer::finish(shared_ptr<ConnectionContext> ctx, bool healthy)
{
if (!healthy)
{
qWarning("[TQTcpServer] Processor failed to process data successfully");
ctxMap_.erase(ctx->connection_.get());
}
}
}}} // apache::thrift::async

View File

@ -0,0 +1,48 @@
#ifndef _THRIFT_TASYNC_QTCP_SERVER_H_
#define _THRIFT_TASYNC_QTCP_SERVER_H_
#include <QObject>
#include <QTcpServer>
#include <boost/shared_ptr.hpp>
#include <tr1/functional>
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<QTcpServer> server,
boost::shared_ptr<TAsyncProcessor> processor,
boost::shared_ptr<apache::thrift::protocol::TProtocolFactory> 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<ConnectionContext> ctx, bool healthy);
boost::shared_ptr<QTcpServer> server_;
boost::shared_ptr<TAsyncProcessor> processor_;
boost::shared_ptr<apache::thrift::protocol::TProtocolFactory> pfact_;
std::map<QT_PREPEND_NAMESPACE(QTcpSocket)*, boost::shared_ptr<ConnectionContext> > ctxMap_;
};
}}} // apache::thrift::async
#endif // #ifndef _THRIFT_TASYNC_QTCP_SERVER_H_

View File

@ -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 <QObject>."
#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<void*>(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

30
lib/cpp/thrift-qt.pc.in Normal file
View File

@ -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