Reputation: 7799
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
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