Ricardo Herrero
Ricardo Herrero

Reputation: 13

Trying to migrate thread creation method from C to C++ does not work

I have been looking for specific info to solve my problem but I think it's too much specific. I am working on a project which mixes c and c++ code in a way it's really confusing. At last I have had to take some C code and use it in a file which is compiled with C++. I though it will work with some changes but I have found one error that I can't solve for myself. It is about thread use.

I have declared two functions public, as I have received them, declared them in my header file drviRIOAD_1D.h, in my class drviRIOAD_1D:

void ai_pv_thread(void *p);
void aiDMA_thread(void *p);

then I have copied its code in my cpp file, drviRIOAD_1D.cpp: (I will just include the part of code with errors)

void drviRIOAD_1D::aiDMA_thread(void *p){
 ai_pv_publish= (irio_dmathread_t*) malloc(sizeof(irio_dmathread_t)*irioPvt->DMATtoHOSTNCh[ai_dma_thread->id]);

    buffersize=irioPvt->DMATtoHOSTBlockNWords[ai_dma_thread->id];

    samples_per_channel=irioPvt->DMATtoHOSTBlockNWords[ai_dma_thread->id]*8; //Bytes per block
    samples_per_channel= samples_per_channel/irioPvt->DMATtoHOSTSampleSize[ai_dma_thread->id]; //Samples per block
    samples_per_channel= samples_per_channel/irioPvt->DMATtoHOSTNCh[ai_dma_thread->id];//Samples per channel per block

    // Ring Buffers for Waveforms PVs
    ai_dma_thread->IdRing= (void**) malloc(sizeof(epicsRingBytesId)*irioPvt->DMATtoHOSTNCh[ai_dma_thread->id]);
    // Creation and Launching of threads working as consumers for EPICS PVs publishing
    aux=(float**) malloc(sizeof(float*)*irioPvt->DMATtoHOSTNCh[ai_dma_thread->id]);
    for(i=0;i<irioPvt->DMATtoHOSTNCh[ai_dma_thread->id];i++){
        aux[i]=(float*) malloc(sizeof(float)*samples_per_channel);
        if(ch_nelm[chIndex+i]!=0){
            ai_dma_thread->IdRing[i]=epicsRingBytesCreate(samples_per_channel*irioPvt->DMATtoHOSTSampleSize[ai_dma_thread->id]*4096);//!<Ring buffer to store manage the waveforms.
            ai_pv_publish[i].IdRing=&ai_dma_thread->IdRing[i];
            ai_pv_publish[i].dma_thread_name=(char *)malloc(40);
            sprintf(ai_pv_publish[i].dma_thread_name,"%sPVPublisher%02d",ai_dma_thread->dma_thread_name,i);
            ai_pv_publish[i].id=i; //channel identifier
            ai_pv_publish[i].dmanumber=ai_dma_thread->id; //dma identifier
            ai_pv_publish[i].threadends=0;
            ai_pv_publish[i].endAck=0;
            ai_pv_publish[i].asynPvt=ai_dma_thread->asynPvt;
            ai_pv_publish[i].dma_thread_id=epicsThreadCreate(ai_pv_publish[i].dma_thread_name,
                                epicsThreadPriorityHigh,epicsThreadGetStackSize(epicsThreadStackBig),
                                (EPICSTHREADFUNC)ai_pv_thread,  
                                (void *)&ai_pv_publish[i]); //Here occurs the error, it means that ai_pv_thread argument does not work properly

}

The last line gives me an error, the following:

../drviRIOAD_1D.cpp:1846: error: invalid use of member (did you forget the ‘&’ ?)

And Here is the function ai_pv_thread(void *p)

void drviRIOAD_1D::ai_pv_thread(void *p){
//There is one thread per ringbuffer (per DMA channel)

irio_dmathread_t *pv_thread;
pv_thread=(irio_dmathread_t *)p;
irioDrv_t *irioPvt = &pv_thread->asynPvt->drvPvt;
float* pv_data;
int pv_nelem=4096,aux;
while (irio_threadsrun==0) {usleep(10000);}
aux=irioPvt->DMATtoHOSTChIndex[pv_thread->dmanumber]+pv_thread->id;

printf ("ch_nelm %d\n", ch_nelm[aux]);
pv_nelem=ch_nelm[aux];


pv_data = (float*) malloc(sizeof(float)*pv_nelem);
do{
    int NbytesDecimated;
    NbytesDecimated=epicsRingBytesUsedBytes(*pv_thread->IdRing);
    if(NbytesDecimated>=(sizeof(float)*pv_nelem))
    {
        if(epicsRingBytesIsFull(*pv_thread->IdRing)){
            //TODO: Error

        }

        epicsRingBytesGet(*pv_thread->IdRing,(char*)pv_data,sizeof(float)*pv_nelem);

        CallAIInsFloat32Array(pv_thread->asynPvt,CH,
                irioPvt->DMATtoHOSTChIndex[pv_thread->dmanumber]+pv_thread->id,pv_data,pv_nelem);
    }else{
        //@todo: fix this
        usleep(10000);
    }
}while (pv_thread->threadends==0);
free(pv_data);
free(pv_thread->dma_thread_name);
pv_thread->endAck=1;
}

What I can't understand is why this gives any error, as this code was working already. I think it's because the migration, but I'm not able to solve it.

In fact I have a near example with cpp and I have been working with it but with no results. In this case, they created the thread with a static callback which is outside the class that calls the function inside the class. The difference is that the argument of the function inside the class is void, void callbackTask() and in my case is void ai_pv_thread(void *p).

The function that gives me problems is from an external API, which gives a C interface and C++ interface, maybe, is this also a problem? I think I'm not properly accesing my function ai_pv_thread, which is called without parameters, but in fact it needs a void *p. However I have tried some changes, for example add '::' (EPICSTHREADFUNC)::ai_pv_thread but in that case the compiler tells me error: '::ai_pv_thread' was not declared.

Can somebody explain me the difference between callbacks in C and C++ so I can explain this behaviour? What's the change I have to make to get it working? I can change function parameters such as static or the position of the fucntions, now they are public from class drviRIOAD_1D, but I can take them out.

This is the link to this API (Just to know the api for the function epicsThreadCreate):
epicsThreadCreate --> Create a new thread. The use made of the priority, and stackSize arguments is implementation dependent. Some implementations may ignore one or other of these, but for portability appropriate values should be given for both. The value passed as the stackSize parameter should be obtained by calling epicsThreadGetStackSize. The funptr argument specifies a function that implements the thread, and parm is the single argument passed to funptr. A thread terminates when funptr returns.

It's interface:

epicsThreadId epicsThreadCreate(const char *name,
unsigned int priority, unsigned int stackSize,
EPICSTHREADFUNC funptr,void *parm);

Upvotes: 0

Views: 173

Answers (1)

Mgetz
Mgetz

Reputation: 5138

Assuming that ai_pv_thread(void*) is a member function then what you're doing is undefined behavior. Given the error you're getting I'm inclined to think so. To start a thread using a member function you need to use a helper like thus:

static void pv_thread_start(void* in)
{
    if(!in)
    {
        return;
    }
    std::unique_ptr<data_holder> holder(static_cast<data_holder*>(in));
    holder->obj->ai_pv_thread(holder->data);
}

alternatively you can use a static member function but the syntax for that is odd and I usually avoid it.

When creating the thread you'll probably need to pass in a POD object that has both *this and the data you need like thus:

struct data_holder{
    drviRIOAD_1D* obj;
    ai_pv_publish* data;
};

and at the point you create the thread:

auto data = std::make_unique<data_holder>({this, &ai_pv_publish[i]});
epicsThreadCreate(
    ai_pv_publish[i].dma_thread_name,
    epicsThreadPriorityHigh,
    epicsThreadGetStackSize(epicsThreadStackBig),
    // this cast is probably unncessary now
    static_cast<EPICSTHREADFUNC>(pv_thread_start),
    static_cast<void*>(data.release());

Upvotes: 1

Related Questions