PDA

View Full Version : Creating A Thread W/ PThreads within an Object



HalNineThousand
03-26-2008, 03:27 PM
I have a program that's been working fine, but now I'm changing it to use objects (I don't want to go into reasoning, but even if I don't change it, I want to know how to do this).

I have been using one routine, which will be public, to call another and start the thread:


void * HDListen::listenthread(void *generic_pointer) {//protected
readinfile();
ioport.closeport();
return NULL;
}

void HDListen::listentoradio() {//public
pthread_create(&listenThread, NULL, listenthread, NULL);
return;
}


So far it's worked fine, but now that I'm putting these functions into an object, I'm using prototypes in the header and have this:



class HDListen {
//Lots of variables that don't relate to this question...
pthread_t listenThread;
LinuxPort ioport;

public:
HDListen(LinuxPort);
void listentoradio();

protected:
void *listenthread(void*);
};



It seems no matte how I tweak the prototype of listenthread(), I can't get it right so it's accepted in the header file as okay and so it matches what's in the .cpp file. Why isn't it working and what do I need to change.

And, while I'm on this issue, is it possible to make this work with passing a value to this routine or returning one -- or just not having it even return NULL?

Thanks!

brewbuck
03-26-2008, 03:46 PM
It seems no matte how I tweak the prototype of listenthread(), I can't get it right so it's accepted in the header file as okay and so it matches what's in the .cpp file. Why isn't it working and what do I need to change.

You're trying to pass the address of a NON-STATIC member function. That's never going to work, because non-static functions can only be invoked through actual objects. Pthreads has no clue about this C++ stuff :) Instead, dispatch the thread like this:



// Helper function to bounce into the HDListen object
void *StartHDListener(void *ctx)
{
HDListen *hdl = static_cast<HDListen *>(ctx);
return hdl->listenthread();
}

...

pthread_create(&listenThread, NULL, StartHDListener, MyListener);


Where MyListener is a pointer to a real HDListen object. Depending on where you are doing this, you might just pass "this" (and in your case, that's what you'd do, since the thread is being created from the HDListen object itself). Notice that this no longer requires a void * argument to the listenthread() member function.

HalNineThousand
03-26-2008, 04:04 PM
You're trying to pass the address of a NON-STATIC member function. That's never going to work, because non-static functions can only be invoked through actual objects. Pthreads has no clue about this C++ stuff :)

In a way that's a relief to hear, since it means I'm not just messing up something a simple typo!


Instead, dispatch the thread like this:



// Helper function to bounce into the HDListen object
void *StartHDListener(void *ctx)
{
HDListen *hdl = static_cast<HDListen *>(ctx);
return hdl->listenthread();
}

...

pthread_create(&listenThread, NULL, StartHDListener, MyListener);



Am I basically just getting a pointer to the HDListen object, then using that to get a pointer to the function I'm starting with?

Could I just have that in line, like this, inside the function that creates the thread (since both the calling function and the function in the thread are in the same object):


HDListen *hdl = static_cast<this *>(ctx);
pthread_create(&listenThread, NULL, hdl->listenthread(), MyListener);


So what is ctx in this case? Just a pointer at nothing in particular?


Where MyListener is a pointer to a real HDListen object. Depending on where you are doing this, you might just pass "this" (and in your case, that's what you'd do, since the thread is being created from the HDListen object itself). Notice that this no longer requires a void * argument to the listenthread() member function.

Paraphrasing to be sure I've got it: I can use "this" as the 4th parameter since the function I'm calling to start the thread is in the same object as the one calling it, right?

brewbuck
03-26-2008, 04:15 PM
Am I basically just getting a pointer to the HDListen object, then using that to get a pointer to the function I'm starting with?

You're getting a HDListen pointer because you NEED one in order to invoke the listenthread() method. The function is a member of an object, therefore the object has to exist in order to call it.


Could I just have that in line, like this, inside the function that creates the thread (since both the calling function and the function in the thread are in the same object):



HDListen *hdl = static_cast<this *>(ctx);
pthread_create(&listenThread, NULL, hdl->listenthread(), MyListener);



Frankly, that code's pretty much meaningless. You can't do that :)


Paraphrasing to be sure I've got it: I can use "this" as the 4th parameter since the function I'm calling to start the thread is in the same object as the one calling it, right?

Basically yes. The object which creates the thread is the same object you want to call listenthread() on in the new thread. So you just pass "this" as the argument to the thread entry point. Make sure you understand what's happening here. You pass a bare function (not a member function) as the thread entry point. The argument to the entry point is a pointer to the HDListen object which is going to run. You convert the pointer to the right type, then call listenthread() through the pointer.

HalNineThousand
03-26-2008, 04:18 PM
I've tried compiling this with these three routines for the thread issue:



void HDListen::listenthread() {//protected
readinfile();
ioport.closeport();
return;
}

void *HDListen::StartHDListener(void *ctx) {//protected
HDListen *hdl = static_cast<HDListen *>(ctx);
return hdl->listenthread();
}

void HDListen::listentoradio() {//public
pthread_create(&listenThread, NULL, StartHDListener, this);
return;
}


I have this in the header:



class HDListen {
pthread_t listenThread;
LinuxPort ioport;

public:
HDListen(LinuxPort);
void listentoradio();

protected:
void listenthread();
void *StartHDListener(void*);
};


When I compile it, here's what I get:

[hal@wizard:HD Radio Controller]$ g++ -c -lpthread -D_REENTRANT hdlisten.cpp
hdlisten.cpp: In member function ‘void* HDListen::StartHDListener(void*)’:
hdlisten.cpp:398: error: void value not ignored as it ought to be
hdlisten.cpp: In member function ‘void HDListen::listentoradio()’:
hdlisten.cpp:402: error: argument of type ‘void* (HDListen::)(void*)’ does not match ‘void* (*)(void*)’

Line 398 is the last line of StartHDListener(), with the return function, and line 402 is the line with the pthread_create() function. Should the return type be something other than void?

vart
03-26-2008, 04:25 PM
return hdl->listenthread();
listenthread(); is void function, but you are trying to return its return valus (which is absent) as if it is void*

brewbuck
03-26-2008, 04:38 PM
Line 398 is the last line of StartHDListener(), with the return function, and line 402 is the line with the pthread_create() function. Should the return type be something other than void?

The StartHDListener() function has to be a plain old function, not a class member function. Define it outside the class.

Your original code had the listenthread() member returning a void *, but you changed it. Either change it back (and return NULL and the end of listenthread() ), or alter the StartHDListener() function to this:



void *StartHDListener(void *ctx)
{
HDListen *hdl = static_cast<HDListen *>(ctx);
hdl->listenthread();
return 0;
}


One or the other.

HalNineThousand
03-26-2008, 07:53 PM
Basically yes. The object which creates the thread is the same object you want to call listenthread() on in the new thread. So you just pass "this" as the argument to the thread entry point. Make sure you understand what's happening here. You pass a bare function (not a member function) as the thread entry point. The argument to the entry point is a pointer to the HDListen object which is going to run. You convert the pointer to the right type, then call listenthread() through the pointer.

Okay, first I wanted to make sure it worked, then I went back over the code and this explanation a few times to make sure I followed what's going on.

I've heard for years how many people just don't get pointers and how people like that are never going to do well with C or C++. Now I see why. I think the concept of a pointer itself is quite simple, but what amazes me is how so much is done with pointers instead of specifying the variable.

Since StartHDListener is not in the class itself, are there going to be any problems with multiple instances of HDListen calling the same thread in the same instance? I wouldn't think so since the object itself is passed as a parameter, but I just want to be sure.

Thanks, by the way, for your help and patience!

brewbuck
03-26-2008, 09:35 PM
I've heard for years how many people just don't get pointers and how people like that are never going to do well with C or C++. Now I see why. I think the concept of a pointer itself is quite simple, but what amazes me is how so much is done with pointers instead of specifying the variable.

Not just pointers, but any method of referring to another variable indirectly, such as a reference or handle... They are powerful, necessary constructs, at least in this language!


Since StartHDListener is not in the class itself, are there going to be any problems with multiple instances of HDListen calling the same thread in the same instance? I wouldn't think so since the object itself is passed as a parameter, but I just want to be sure.

If I understand the question, no, there are no problems like that.

HalNineThousand
03-27-2008, 11:12 AM
You're trying to pass the address of a NON-STATIC member function. That's never going to work, because non-static functions can only be invoked through actual objects. Pthreads has no clue about this C++ stuff :)

After re-reading this thread and noticing this, then it leads to another question. In this case I was trying to pass a pointer to a function and once that function was in an object, it wouldn't work. In one section of code I have:


typedef void (*Func0)();
typedef map< string, Func0 > FuncMap0;
...
cmdfunc0["on"] = hd_on;
cmdfunc0["off"] = hd_off;


From what I understand, once that code is in an object, even if I'll be calling the functions from within that same object, I can no longer call them by reference.

Is that correct? Is there an easy alternative, or do I need to put a big switch or if..else statements in to call the functions?

brewbuck
03-27-2008, 11:40 AM
From what I understand, once that code is in an object, even if I'll be calling the functions from within that same object, I can no longer call them by reference.

Correct -- you will have to store something other than just bare function pointers in the map -- both a pointer-to-object and a pointer-to-member, so that you can call the member through the object pointer.

This might get into syntax you haven't seen before:



class HDListen_callback
{
public:
HDListen_callback(HDListen *hdl_p, void (HDListen::*func_p)())
: hdl(hdl_p), func(func_p) {}

void operator()() { (hdl->*func)(); }

private:
HDListen *hdl;
void (HDListen::*func)();
};

...

// Somewhere inside an HDListen object...

typedef HDListen_callback Func0;
typedef std::map<std::string, Func0> FuncMap0;
FuncMap0 cmdfunc0;
cmdfunc0["on"] = HDListen_callback(this, &HDListen::hd_on);
cmdfunc0["off"] = HDListen_callback(this, &HDListen::hd_off);

...

cmdfunc0["on"](); // call it

HalNineThousand
03-27-2008, 11:59 AM
Yes, it does get into syntax I haven't seen before. Thanks! I'll put it into my program and I'm sure it'll take a bit for me to figure out how it works, but having a guide will let me understand it.

Much appreciated!

sethjackson
03-28-2008, 02:57 PM
The StartHDListener() function has to be a plain old function, not a class member function. Define it outside the class.


You could also use a static member function. That works too.



class HDListen
{
private:

void listenthread();

static void* StartHDListener(void* arg);
};

void* HDListen::StartHDListener(void* arg)
{
HDListen* hdl = static_cast<HDListen* >(arg);

hdl->listenthread();

return 0;
}