/****************************************************************************** * QSkinny - Copyright (C) 2016 Uwe Rathmann * This file may be used under the terms of the QSkinny License, Version 1.0 *****************************************************************************/ #include "QskMetaInvokable.h" #include "QskMetaFunction.h" #include #include #include #include #include QSK_QT_PRIVATE_BEGIN #include QSK_QT_PRIVATE_END namespace { using CallFunction = QObjectPrivate::StaticMetaCallFunction; class Function : public QskMetaFunction { public: inline Function( void* functionCall ): QskMetaFunction( static_cast< FunctionCall* >( functionCall ) ) { } static inline void ref( void* functionCall ) { if ( functionCall ) static_cast< FunctionCall* >( functionCall )->ref(); } static inline void deref( void* functionCall ) { if ( functionCall ) static_cast< FunctionCall* >( functionCall )->destroyIfLastRef(); } }; class MetaCallEvent final : public QMetaCallEvent { public: MetaCallEvent( QMetaObject::Call call, CallFunction callFunction, ushort offset, ushort index, int nargs, int* types, void* args[], QSemaphore* semaphore = nullptr ): QMetaCallEvent( offset, index, callFunction, nullptr, -1, nargs, types, args, semaphore ), m_call( call ), m_callFunction( callFunction ), m_index( index ) { } void placeMetaCall( QObject* object ) override { m_callFunction( object, m_call, m_index, args() ); } private: const QMetaObject::Call m_call; // as those members from QMetaCallEvent are not accessible CallFunction m_callFunction; const ushort m_index; }; } static inline void qskInvokeMetaCallQueued( QObject* object, QMetaObject::Call call, ushort offset, ushort index, int nargs, int* types, void* args[], QSemaphore* semaphore = nullptr ) { const auto callFunction = object->metaObject()->d.static_metacall; auto event = new MetaCallEvent( call, callFunction, offset, index, nargs, types, args, semaphore ); QCoreApplication::postEvent( object, event ); } QMetaMethod qskMetaMethod( const QObject* object, const char* methodName ) { return object ? qskMetaMethod( object->metaObject(), methodName ) : QMetaMethod(); } QMetaMethod qskMetaMethod( const QMetaObject* metaObject, const char* methodName ) { if ( metaObject == nullptr || methodName == nullptr ) return QMetaMethod(); constexpr char signalIndicator = '0' + QSIGNAL_CODE; constexpr char slotIndicator = '0' + QSLOT_CODE; int index = -1; if( methodName[0] == signalIndicator ) { auto signature = QMetaObject::normalizedSignature( methodName + 1 ); index = metaObject->indexOfSignal( signature ); } else if ( methodName[0] == slotIndicator ) { auto signature = QMetaObject::normalizedSignature( methodName + 1 ); index = metaObject->indexOfSlot( signature ); } else { auto signature = QMetaObject::normalizedSignature( methodName ); index = metaObject->indexOfMethod( signature ); } return ( index >= 0 ) ? metaObject->method( index ) : QMetaMethod(); } QMetaMethod qskNotifySignal( const QObject* object, const char* propertyName ) { return object ? qskNotifySignal( object->metaObject(), propertyName ) : QMetaMethod(); } QMetaMethod qskNotifySignal( const QMetaObject* metaObject, const char* propertyName ) { if ( metaObject == nullptr || propertyName == nullptr ) return QMetaMethod(); const int propertyIndex = metaObject->indexOfProperty( propertyName ); if ( propertyIndex ) { const auto property = metaObject->property( propertyIndex ); return property.notifySignal(); } return QMetaMethod(); } static void qskInvokeMetaCall( QObject* object, const QMetaObject* metaObject, QMetaObject::Call call, int offset, int index, void* argv[], Qt::ConnectionType connectionType ) { QPointer< QObject > receiver( object ); int invokeType = connectionType & 0x3; if ( invokeType == Qt::AutoConnection ) { invokeType = ( object && object->thread() != QThread::currentThread() ) ? Qt::QueuedConnection : Qt::DirectConnection; } switch( invokeType ) { case Qt::DirectConnection: { if ( receiver.isNull() ) { #if 1 // do we really always need an object, what about Q_GADGET ??? return; #endif } /* QMetaObject::metacall seems to be made for situations we don't have. Need to dive deeper into the Qt code to be 100% sure TODO ... */ metaObject->d.static_metacall( receiver, call, index, argv ); break; } case Qt::BlockingQueuedConnection: { if ( receiver.isNull() || ( receiver->thread() == QThread::currentThread() ) ) { // We would end up in a deadlock, better do nothing return; } QSemaphore semaphore; qskInvokeMetaCallQueued( receiver, call, offset, index, 0, nullptr, argv, &semaphore ); semaphore.acquire(); break; } case Qt::QueuedConnection: { if ( receiver == nullptr ) return; int* types = nullptr; void** arguments = nullptr; int argc = 0; if ( call == QMetaObject::InvokeMetaMethod ) { #if 1 // should be doable without QMetaMethod. TODO ... const auto method = metaObject->method( offset + index ); #endif argc = method.parameterCount() + 1; types = static_cast< int* >( malloc( argc * sizeof( int ) ) ); arguments = static_cast< void** >( malloc( argc * sizeof( void* ) ) ); /* The first one is the return type, one that is always invalid for Queued Connections. */ types[0] = QMetaType::UnknownType; arguments[0] = nullptr; for ( int i = 1; i < argc; i++ ) { if ( argv[i] == nullptr ) { Q_ASSERT( argv[i] != nullptr ); receiver = nullptr; break; } types[i] = method.parameterType( i - 1 ); arguments[i] = QMetaType::create( types[i], argv[i] ); } } else { // should be doable without QMetaMethod. TODO ... const auto property = metaObject->property( offset + index ); argc = 1; types = static_cast< int* >( malloc( argc * sizeof( int ) ) ); arguments = static_cast< void** >( malloc( argc * sizeof( void* ) ) ); types[0] = property.userType(); arguments[0] = QMetaType::create( types[0], argv[0] ); } if ( receiver.isNull() ) { // object might have died in the meantime free( types ); free( arguments ); return; } qskInvokeMetaCallQueued( object, call, offset, index, argc, types, arguments ); break; } } } void qskInvokeMetaPropertyWrite( QObject* context, const QMetaProperty& property, void* args[], Qt::ConnectionType connectionType ) { qskInvokeMetaPropertyWrite( context, property.enclosingMetaObject(), property.propertyIndex(), args, connectionType ); } void qskInvokeMetaPropertyWrite( QObject* context, const QMetaObject* metaObject, int propertyIndex, void* args[], Qt::ConnectionType connectionType ) { // check for is writable ??? if ( metaObject && ( propertyIndex >= 0 ) && ( propertyIndex < metaObject->propertyCount() ) ) { const auto offset = metaObject->propertyOffset(); const auto index = propertyIndex - offset; qskInvokeMetaCall( context, metaObject, QMetaObject::WriteProperty, offset, index, args + 1, connectionType ); } } void qskInvokeMetaMethod( QObject* object, const QMetaMethod& method, void* args[], Qt::ConnectionType connectionType ) { qskInvokeMetaMethod( object, method.enclosingMetaObject(), method.methodIndex(), args, connectionType ); } void qskInvokeMetaMethod( QObject* object, const QMetaObject* metaObject, int methodIndex, void* argv[], Qt::ConnectionType connectionType ) { if ( metaObject && ( methodIndex >= 0 ) && ( methodIndex < metaObject->methodCount() ) ) { const auto offset = metaObject->methodOffset(); const auto index = methodIndex - offset; qskInvokeMetaCall( object, metaObject, QMetaObject::InvokeMetaMethod, offset, index, argv, connectionType ); } } QskMetaInvokable::QskMetaInvokable( const QMetaMethod& method ): m_metaData { method.enclosingMetaObject(), method.methodIndex() }, m_type( MetaMethod ) { } QskMetaInvokable::QskMetaInvokable( const QObject* object, const char* methodName ): QskMetaInvokable( qskMetaMethod( object, methodName ) ) { } QskMetaInvokable::QskMetaInvokable( const QMetaObject* metaObject, const char* methodName ): QskMetaInvokable( qskMetaMethod( metaObject, methodName ) ) { } QskMetaInvokable::QskMetaInvokable( const QMetaProperty& property ): m_metaData { property.enclosingMetaObject(), property.propertyIndex() }, m_type( MetaProperty ) { } QskMetaInvokable::QskMetaInvokable( const QskMetaFunction& function ): m_functionData { function.functionCall() }, m_type( MetaFunction ) { Function::ref( m_functionData.functionCall ); } QskMetaInvokable::QskMetaInvokable( const QskMetaInvokable& other ): m_type( other.m_type ) { switch( m_type ) { case MetaMethod: case MetaProperty: { m_metaData.metaObject = other.m_metaData.metaObject; m_metaData.index = other.m_metaData.index; break; } case MetaFunction: { m_functionData.functionCall = other.m_functionData.functionCall; Function::ref( m_functionData.functionCall ); break; } default: break; } } QskMetaInvokable::~QskMetaInvokable() { if ( m_type == MetaFunction ) Function::deref( m_functionData.functionCall ); } QskMetaInvokable& QskMetaInvokable::operator=( const QskMetaInvokable& other ) { switch( other.m_type ) { case MetaMethod: case MetaProperty: { if ( m_type == MetaFunction ) Function::deref( m_functionData.functionCall ); m_metaData.metaObject = other.m_metaData.metaObject; m_metaData.index = other.m_metaData.index; break; } case MetaFunction: { if ( m_type == MetaFunction ) Function::deref( m_functionData.functionCall ); m_functionData.functionCall = other.m_functionData.functionCall; Function::ref( m_functionData.functionCall ); break; } default: if ( m_type == MetaFunction ) Function::deref( m_functionData.functionCall ); } m_type = other.m_type; return *this; } bool QskMetaInvokable::operator==( const QskMetaInvokable& other ) const { if ( m_type != other.m_type ) return false; switch( m_type ) { case MetaMethod: case MetaProperty: { return ( m_metaData.metaObject == other.m_metaData.metaObject ) && ( m_metaData.index == other.m_metaData.index ); } case MetaFunction: { return m_functionData.functionCall == other.m_functionData.functionCall; } default: { return true; } } } bool QskMetaInvokable::isNull() const { switch( m_type ) { case MetaMethod: case MetaProperty: { const auto& d = m_metaData; if ( d.metaObject == nullptr || d.index < 0 ) return true; const int count = ( m_type == MetaMethod ) ? d.metaObject->methodCount() : d.metaObject->propertyCount(); return d.index >= count; } case MetaFunction: { return m_functionData.functionCall == nullptr; } default: return true; } } void QskMetaInvokable::reset() { if ( m_type == MetaFunction ) Function::deref( m_functionData.functionCall ); m_type = Invalid; } int QskMetaInvokable::parameterCount() const { switch( m_type ) { case MetaMethod: { // should be doable without QMetaMethod. TODO ... const auto method = QskMetaInvokable::method(); return method.parameterCount(); } case MetaProperty: { return 1; } case MetaFunction: { return function().parameterCount(); } default: break; } return 0; } int QskMetaInvokable::parameterType( int index ) const { switch( m_type ) { case MetaMethod: { const auto method = QskMetaInvokable::method(); return method.parameterType( index ); } case MetaProperty: { const auto property = QskMetaInvokable::property(); return property.userType(); } case MetaFunction: { auto types = function().parameterTypes(); return types[index]; } default: { return QMetaType::UnknownType; } } } int QskMetaInvokable::returnType() const { switch( m_type ) { case MetaMethod: { return method().returnType(); } case MetaFunction: { return function().returnType(); } case MetaProperty: { return QMetaType::Void; } default: { return QMetaType::Void; } } } QByteArray QskMetaInvokable::name() const { switch( m_type ) { case MetaMethod: { return method().name(); } case MetaProperty: { return property().name(); } case MetaFunction: { // what to do here ??? return QByteArray(); } default: { return QByteArray(); } } } QMetaMethod QskMetaInvokable::method() const { if ( m_type == MetaMethod && m_metaData.metaObject ) return m_metaData.metaObject->method( m_metaData.index ); return QMetaMethod(); } QMetaProperty QskMetaInvokable::property() const { if ( m_type == MetaProperty && m_metaData.metaObject ) return m_metaData.metaObject->property( m_metaData.index ); return QMetaProperty(); } QskMetaFunction QskMetaInvokable::function() const { if ( m_type == MetaFunction && m_functionData.functionCall ) { Function function( m_functionData.functionCall ); return *static_cast< QskMetaFunction* >( &function ); } return QskMetaFunction(); } void QskMetaInvokable::invoke( QObject* object, void* args[], Qt::ConnectionType connectionType ) { if ( isNull() ) return; switch( m_type ) { case MetaMethod: { qskInvokeMetaMethod( object, m_metaData.metaObject, m_metaData.index, args, connectionType ); break; } case MetaProperty: { qskInvokeMetaPropertyWrite( object, m_metaData.metaObject, m_metaData.index, args, connectionType ); break; } case MetaFunction: { if ( m_functionData.functionCall ) { Function function( m_functionData.functionCall ); function.invoke( object, args, connectionType ); } break; } default: break; } }