Interfacing D with C++ : start new thread

Interfacing D with C++ is sometime tricky. In this scenario, I will show how to start a thread from C++ that execute C++ code, but in a valid D environment, so that C++ code can interact with D code safely.

This work as been done to port SFML2 to D2 language. But the technique is useful for anybody who want to start safely thread in a environment with code in both C++ and D.

The main function

First of all, we need a C++ main. This seems easy, but is actually not so straightforward, because D runtime already define that function (and you’ll get an error at link time). So lets define a D main, that start our program in C++ :

1
2
3
4
5
module main;
extern(C++) int CXXmain();
int main(string[] argv) {
    return CXXmain();
}

Now we can start our C++ code using CXXmain function. We doesn’t pass any command line parameter in this example, however, this is something we should consider to add in the future.

The C++ thread class

To encapsulate the thread functionality (that is different on each system) we will use a class designed to abstract what is a thread. To not reinvent the well, this example will use a modified version of SFML‘s thread class. This thread class use ThreadImpl, another class, to provide an system dependent implementation of threads. Let’s write our own version of ThreadImpl, implementing what we need to start a thread using D runtime.

The header :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#ifndef SFML_THREADIMPL_HPP
#define SFML_THREADIMPL_HPP

#include <SFML/System/NonCopyable.hpp>

namespace sf
{
class Thread;

namespace priv
{
////////////////////////////////////////////////////////////
/// \brief D implementation of threads
////////////////////////////////////////////////////////////
class ThreadImpl : NonCopyable {
public :

////////////////////////////////////////////////////////////
/// \brief Default constructor, launch the thread
///
/// \param owner The Thread instance to run
///
////////////////////////////////////////////////////////////
ThreadImpl(Thread* owner);

////////////////////////////////////////////////////////////
/// \brief Wait until the thread finishes
///
////////////////////////////////////////////////////////////
void Wait();

////////////////////////////////////////////////////////////
/// \brief Terminate the thread
///
////////////////////////////////////////////////////////////
void Terminate();

private :

////////////////////////////////////////////////////////////
/// \brief Global entry point for all threads
///
/// \param userData User-defined data (contains the Thread instance)
///
/// \return Os specific error code
///
////////////////////////////////////////////////////////////
static void* EntryPoint(void* userData);

////////////////////////////////////////////////////////////
// Member data
////////////////////////////////////////////////////////////
void*    tid;   ///< Tid instance
};

} // namespace priv

} // namespace sf

#endif // SFML_THREADIMPL_HPP

And the implementation :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <SFML/System/D/ThreadImpl.hpp>

#include <SFML/System/Thread.hpp>

// Thoses are not supposed to be used on C++ side. Anyway, they require to be in the global scope to be linked with D.
void* __dsfml_start_thread(void* (*entryPoint)(void*), void* userData);
void __dsfml_wait_thread(void* tid);
void __dsfml_terminate_thread(void* tid);

namespace sf
{
namespace priv
{
////////////////////////////////////////////////////////////
ThreadImpl::ThreadImpl(Thread* owner) {
    tid = __dsfml_start_thread(&ThreadImpl::EntryPoint, owner);
}

////////////////////////////////////////////////////////////
void ThreadImpl::Wait() {
    __dsfml_wait_thread(tid);
}

////////////////////////////////////////////////////////////
void ThreadImpl::Terminate() {
    __dsfml_terminate_thread(tid);
}

////////////////////////////////////////////////////////////
void* ThreadImpl::EntryPoint(void* userData) {
    // The Thread instance is stored in the user data
    Thread* owner = static_cast<Thread*>(userData);

    // Forward to the owner
    owner->Run();

    return NULL;
}

} // namespace priv

} // namespace sf

Now we have a thread class in C++ that forward its calls to D code. We need to start a thread from D using that code. Let create our main C++ code to start teh thread :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include<SFML/System.hpp>

#include<iostream>

using namespace sf;

void someFunction() {
    std::cout << "Some function !" << std::endl;
}

int CXXmain() {
    Thread* t = new Thread(someFunction);
    t->Launch();
    return 0;
}

Start a thread the D way

Now that we have everything set up to forward thread call to D, we have to handle that calls. The most interesting function (and the only one we will implement for now) is __dsfml_start_thread. This function has 2 parameters : a function pointer to a C++ entry point for the new thread, and a pointer to user defined data.

Let’s start this new thread with the spawn method :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
module dsfml.system.thread;

import std.concurrency;

alias extern(C++) void* function(void*) EntryPoint;

extern(C++) {
    void* __dsfml_start_thread(EntryPoint entryPoint, void* userData) {
        Tid tid = spawn(function void(EntryPoint entryPoint, shared void* userData) {
            entryPoint(cast(void*) userData);
        }, entryPoint, cast(shared void*) userData);
       
        return cast(void*) [tid].ptr;
    }
   
    void __dsfml_wait_thread(void* tid) {
        // TODO: This could be implemented in the future.
    }
   
    void __dsfml_terminate_thread(void* tid) {
        // Does nothing because it is unsafe (and not provided by D API as far as my knowledge goes).
    }
}

As you can see, we have to do a lot of casts to work around the lack of shared qualifier in C++, but required by D to share data across threads. This is due to the different memory model between D and C++.

After Words

Starting a thread the D way is required because of the way memory is managed in D2. The garbage collector has to be aware of the existence of all threads. Some data structures are also built for each thread in D to allow message passing and various other things.

If we use shared with __dsfml_start_thread, the program doesn’t link anymore using gdc (and frontend v2.055). This is something that should be improved to help binding C++ and D.

In the same way, it is not possible to implement a C++ main because of D runtime. This is something to consider and a standard solution has to be provided by the D community in the future to help newcomers.

In order to compile and link theses examples, I use g++ and gdc both version 4.6. Building .o files isn’t difficult, but producing an executable at the end can be tricky. To do so, I used gdc and manually linked the C++ standard library using the switch -lstdc++ .

The modified version of SFML as been compiled in static as D2 doesn’t clearly define how to use shared libs at the moment.

I never succeeded in compiling a working executable using g++ and adding -lgphobos2 and/or -lgdruntime .

Leave a Reply

Your email address will not be published. Required fields are marked *