Denis Steinman
Denis Steinman

Reputation: 7799

NSThread initWithTarget:selector:object:]: target does not implement selector ((null))'

I try to implement a custom Cocoa main loop for my app using metal-cpp (the source code is also available here) wrapper.

The first step is to write a something like:

[NSThread detachNewThreadSelector:@selector(doNothing:)
                         toTarget:stubTarget
                         withObject:nil];

To run a stub selector (the solution is based on GLFW source code).

The metal-cpp doesn't contain NSThread class, so I've implemented it:

metal-cpp/Foundation/NSThread.hpp:

#pragma once

#include <functional>

#include "NSObject.hpp"

namespace NS
{

class Runnable
{
public:
    virtual ~Runnable() {}
    virtual void run() {}
};

class Thread: public NS::Referencing< Thread >
{
public:
    static void detachNewThread( const Runnable* pRunnable );
};

_NS_INLINE void NS::Thread::detachNewThread( const Runnable* pRunnable )
{
    NS::Value* pWrapper = NS::Value::value( pRunnable );

    typedef void (*DispatchFunction)( NS::Value*, SEL, void* );
    
    DispatchFunction run = []( Value* pSelf, SEL, void* unused )
    {
        auto pDel = reinterpret_cast< NS::Runnable* >( pSelf->pointerValue() );
        pDel->run();
    };

    return Object::sendMessage<void>(_NS_PRIVATE_CLS(NSThread), _NS_PRIVATE_SEL(detachNewThreadSelector_toTarget_withObject_), run, pWrapper, nullptr );
}

}

Also I've registered new _NS_PRIVATE_SEL variants in metal-cpp/Foundation/NSPrivate.hpp:

_NS_PRIVATE_DEF_SEL(detachNewThreadSelector_toTarget_withObject_,
    "detachNewThreadSelector:toTarget:withObject:");

Finally, I've included NSThread into the main header metal-cpp/Foundation/Foundation.hpp.

The source code of NSThread was written researching a source code of NSApplication and Apple references. I don't know Objective-C and its code is a little magic for me.

Later, I call NSThread::detachNewThread() in my app:

class MainLoopTarget: public NS::Runnable
{
    public:
        void run() override
        {
            std::cout << "run()" << std::endl;
        }
};

auto target = new MainLoopTarget();
NS::AutoreleasePool* autorelease_pool = NS::AutoreleasePool::alloc()->init();

NS::Thread::detachNewThread(target);

autorelease_pool->release();
delete target;

And it crashes with errors:

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[NSThread initWithTarget:selector:object:]: target does not implement selector ((null))'
*** First throw call stack:
(
        0   CoreFoundation                      0x00000001856aeccc __exceptionPreprocess + 176
        1   libobjc.A.dylib                     0x0000000185196788 objc_exception_throw + 60
        2   Foundation                          0x00000001867641d0 -[NSThread setQualityOfService:] + 0
        3   Foundation                          0x00000001867c1118 +[NSThread detachNewThreadSelector:toTarget:withObject:] + 60
        4   libb3engine.dylib                   0x000000010506bb88 _ZN2b38platform6darwin9DarwinApp5startEv + 336
        5   B3                                  0x0000000104b7dd4c main + 252
        6   dyld                                0x00000001851d20e0 start + 2360
)
libc++abi: terminating due to uncaught exception of type NSException

I understand there is a problem with the class method's parameters but my skills in Objective-C are not enough to get where and what.

Upvotes: 1

Views: 47

Answers (1)

Denis Steinman
Denis Steinman

Reputation: 7799

Well, I've got my mistake. Right implementation is below:

In metal-cpp/Foundation/NSPrivate.hpp:

_NS_PRIVATE_DEF_SEL(detachNewThreadSelector_toTarget_withObject_,
    "detachNewThreadSelector:toTarget:withObject:");
// Register our selector
_NS_PRIVATE_DEF_SEL(run_,
    "run:");

In metal-cpp/Foundation/NSThread.hpp:

_NS_INLINE void NS::Thread::detachNewThread( const Runnable* pRunnable )
{
    NS::Value* pWrapper = NS::Value::value( pRunnable );

    typedef void (*DispatchFunction)( NS::Value*, SEL, void* );
    
    DispatchFunction run = []( Value* pSelf, SEL, void* unused )
    {
        auto pDel = reinterpret_cast< NS::Runnable* >( pSelf->pointerValue() );
        pDel->run();
    };

    // Register the class method on Objective-C side
    class_addMethod( (Class)_NS_PRIVATE_CLS( NSValue ), _NS_PRIVATE_SEL( run_ ), (IMP)run, "v@:@" );
    // Here we call an Objective-C side's selector
    return Object::sendMessage<void>(_NS_PRIVATE_CLS(NSThread), _NS_PRIVATE_SEL(detachNewThreadSelector_toTarget_withObject_), _NS_PRIVATE_SEL(run_), pWrapper, nullptr );
}

Upvotes: 1

Related Questions