PDA

Visualizza la versione completa : [C++] init di una classe all'interno di un thread


gianvituzzi
14-01-2010, 20:50
Salve,

ho scritto una piccola classe per gestire le funzioni waveIn della waveForm API di windows. La classe è composta da questi due files:

waveCapture.h (http://theartofweb.net/cpp/waveCapture_H.txt)
waveCapture.cpp (http://theartofweb.net/cpp/waveCapture_CPP.txt)

mentre la classe funziona benissimo all'interno di main() ho un problema ad usarla dentro ad un thread (l'inizializzazione va a buon termine ma chiamando il metodo principale mi torna un unhanlde exception: Access violation writing location)

ecco il codice del programma thread:



#include <stdio.h>
#include <stdlib.h>
#include "waveCapture.h"

DWORD WINAPI startRecorder(const LPVOID lpParam);
DWORD WINAPI stopRecorder(void);

bool stopThreadFlag = false;

struct parametri
{
WORD uDevice;
DWORD nSamplesPerSec;
WORD wBitsPerSample;
WORD nChannels;
const char* szFilePath;
};

int main()
{
parametri *p = new (parametri);
p->uDevice = 0;
p->nSamplesPerSec = 44100;
p->wBitsPerSample = 16;
p->nChannels = WAVE_STEREO;
p->szFilePath = "C:\\Users\\giangaetano\\Desktop\\mmio\\waveCapture .WAVE";

HANDLE hthread[2];
DWORD dwID[2];

// Launch Capturing Thread: thread[0]
hthread[0] = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)&startRecorder,p,0,&dwID[0]);

printf("Started capturing...(press any key to stop)\n\n");
system("pause");

// Once any key is pressed, stop thread[0] by launching thread[1]
hthread[1] = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)&stopRecorder,NULL,0,&dwID[1]);
CloseHandle(hthread[1]);
CloseHandle(hthread[0]);

printf("\nStopped capturing!\n\n");

system("pause");
delete p;
return EXIT_SUCCESS;
}

DWORD WINAPI startRecorder(const LPVOID lpParam)
{
// Get the params
parametri p;
p = *((parametri*)lpParam);

// Init waveCapture class
waveCapture pwc;
printf("%s, %d, %d, %d\n",
waveCapture::szVersion,
pwc.__dwSamplePerSec(),
pwc.__wBitsPerSample(),
pwc.__nChannels()
);

// Start capturing
pwc.start();

// Stop capturing:
pwc.stop();

return 0;
}

DWORD WINAPI stopRecorder(void)
{
stopThreadFlag = true;
return 0;
}


C'è qualche accorgimento speciale che devo seguire se sviluppo una classe che potrebbe essere usata da dentro un thread?

grazie

shodan
14-01-2010, 22:19
Un'infinità, ma nel tuo caso potrebbe bastare mettere una WaitForSingleObjects per thread (o una WaitForMultipleObjects) qui:


hthread[1] = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)&stopRecorder,NULL,0,&dwID[1]);

WaitForMultipleObjects(2,htread);

CloseHandle(hthread[1]);
CloseHandle(hthread[0]);

gianvituzzi
14-01-2010, 22:36
non ci avevo pensato:



WaitForMultipleObjects(2, hthread, true, INFINITE);


può far benissimo in quella parte del codice, ma tuttavia non risolve il problema :-(

gianvituzzi
14-01-2010, 22:54
l'inizializzazione avviene correttamente, se non chiamassi start() non ci sarebbe errore...non so proprio cosa possa essere, ecco il codice di start() nella classe:



// Define: start() (fake)
bool waveCapture::start()
{
DWORD dwCBufferLength = getSuggestedBufferSize();
if(this->_start(0, dwCBufferLength, dwCNumBuffers))
return true;
else
return false;
}

// Define: start(WORD) (fake)
bool waveCapture::start(const WORD uDevice)
{
DWORD dwCBufferLength = getSuggestedBufferSize();
if(this->_start(uDevice, dwCBufferLength, dwCNumBuffers))
return true;
else
return false;
}

// Define: start(WORD, DWORD, DWORD) (fake)
bool waveCapture::start(const WORD uDevice, const DWORD dwBufferLength, const DWORD dwNumBuffers)
{
if(this->_start(uDevice, dwBufferLength, dwNumBuffers))
return true;
else
return false;
}

// Define: _start(WORD, DWORD, DWORD) (real)
bool waveCapture::_start(const WORD uDevice, const DWORD dwBufferLength, const DWORD dwNumBuffers)
{
// need dwNumBuffers in stop() as well,
// so I set __dwNumBuffers under the counter
__dwNumBuffers = dwNumBuffers;

// Define WAVEFORMATEX Structure (WAVEFORMATEX wf):
wf.wFormatTag = WAVE_FORMAT_PCM;
wf.wBitsPerSample = _wBitsPerSample;
wf.nChannels = _nChannels;
wf.nSamplesPerSec = _dwSamplePerSec;
wf.nBlockAlign = (wf.nChannels * wf.wBitsPerSample) / 8;
wf.nAvgBytesPerSec = (wf.nSamplesPerSec * wf.nBlockAlign);
wf.cbSize = 0;

// Create event:
hevent = CreateEvent(NULL,FALSE,FALSE,NULL);

// WaveInOpen
if(waveInOpen(&hwi,uDevice,(LPWAVEFORMATEX)&wf,(DWORD)hevent,0,CALLBACK_EVENT) != MMSYSERR_NOERROR)
{
return false;
}

// Define WAVEHDR Structure:
buff = new WAVEHDR[dwNumBuffers];
for (int i = 0; i<(int)dwNumBuffers; i++)
{
ZeroMemory(&buff[i], sizeof(buff[i]));
buff[i].lpData = (char*) malloc(dwBufferLength);
buff[i].dwBufferLength = dwBufferLength;
buff[i].dwBytesRecorded = 0;
buff[i].dwUser = 0;
buff[i].dwFlags = 0;
buff[i].dwLoops = 0;

if(waveInPrepareHeader(hwi, &buff[i], sizeof(WAVEHDR)) != MMSYSERR_NOERROR)
{
return false;
}

if(waveInAddBuffer(hwi, &buff[i], sizeof(WAVEHDR)) != MMSYSERR_NOERROR)
{
return false;
}
}

// Start capturing...
if(waveInStart(hwi) != MMSYSERR_NOERROR)
{
printf("waveInStart: ERROR!\n");
return false;
}

_dwBufferCount = 0;
dwTotalBufferLength = 0;
return true;
}

gianvituzzi
14-01-2010, 23:32
una domanda, ma devo sempre e cmq cancellare il puntatore alla class anche quando essa è chiamata dentro un thread?

non capisco come ma ora il codice seguente funziona alla perfezione! (ma se cancello il puntatore prima di ritornare alla main mi da unhandle exception!!)



#include <stdio.h>
#include <stdlib.h>
#include "waveCapture.h"

DWORD WINAPI startRecorder(const LPVOID lpParam);
DWORD WINAPI stopRecorder(void);

bool stopThreadFlag = false;

struct parametri
{
WORD uDevice;
DWORD nSamplesPerSec;
WORD wBitsPerSample;
WORD nChannels;
const char* szFilePath;
};

int main()
{
HANDLE hthread[2];
DWORD dwID[2];

parametri *p = new (parametri);
p->uDevice = 1;
p->nSamplesPerSec = 44100;
p->wBitsPerSample = 16;
p->nChannels = WAVE_STEREO;
p->szFilePath = "C:\\Users\\giangaetano\\Desktop\\mmio\\waveCapture .WAVE";

// Launch Capturing Thread: thread[0]
hthread[0] = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)&startRecorder,p,0,&dwID[0]);

printf("Started capturing...(press any key to stop)\n\n");
system("pause");

// Once any key is pressed, stop thread[0] by launching thread[1]
hthread[1] = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)&stopRecorder,NULL,0,&dwID[1]);

WaitForMultipleObjects(2, hthread, true, INFINITE);

CloseHandle(hthread[1]);
CloseHandle(hthread[0]);

printf("\nStopped capturing!\n\n");

system("pause");
return EXIT_SUCCESS;
}

DWORD WINAPI startRecorder(const LPVOID lpParam)
{

// Get the params
parametri p;
p = *((parametri*)lpParam);

// Init waveCapture class
waveCapture *pwc = new waveCapture(p.nSamplesPerSec, p.wBitsPerSample, p.nChannels);
printf("%s, %d, %d, %d\n",
waveCapture::szVersion,
pwc->__dwSamplePerSec(),
pwc->__wBitsPerSample(),
pwc->__nChannels()
);

// Start capturing
pwc->start(p.uDevice);

// Create an empty WAVE File
pwc->createWAVEFile(p.szFilePath);

// Get the suggested buffer size (bytes)
DWORD bufflen = pwc->getSuggestedBufferSize();

// Alloc buffer
char* pWAVBuffer = new char[bufflen];

// Loop
while(1)
{
if(!stopThreadFlag)
{
// Read WAVE Chunk
DWORD dwBytesRec = pwc->readBuffer(pWAVBuffer);

// Save WAVE Chunk
pwc->saveWAVEChunk(pWAVBuffer);

} else {
break;
}
}

// Stop Capturing
pwc->stop();

// Add the RIFF header to the WAVE File and close it
pwc->closeWAVEFile();

// Delete the buffer:
delete pWAVBuffer;

// Free memory
//delete pwc;

return 0;
}

DWORD WINAPI stopRecorder(void)
{
stopThreadFlag = true;
return 0;
}


mah!

gianvituzzi
14-01-2010, 23:54
forse era VC++ impazzito, ora va tutto bene!!!

http://theartofweb.net/cpp/waveCapture_Thread.txt

shodan
15-01-2010, 00:38
Spiacente, ma ci credo poco. Il codice (se non l'hai modificato nel frattempo trovando l'inghippo) NON funziona. E l'errore non è dovuto al multithreading (come pensavo in un primo momento.) ma ad un uso errato delle funzioni e delle allocazioni di memoria.
Eseguendo il codice in singlethreading ottengo comunque un errore di accesso alla memoria e il problema sono due righe:

in waveCapture.h
WAVEHDR* buff;

nella funzione _start
buff = new WAVEHDR*[dwNumBuffers];

Non puoi allocare un array dinamico in quel modo.

Con WAVEHDR* buff; prepari il puntatore per una singola WAVEHDR
Per allocare un array devi usare un doppio puntatore.


WAVEHDR** buff;
buff = new WAVEHDR*[dwNumBuffers];

e poi inizializzare ogni singolo campo:


buff[i] = new WAVEHDR;

(ovviamente dovrai accedere ai campi con
buff[i]->
etc.

Poi c'è un errore più fine. Quando il distruttore distrugge buff, la registrazione è ancora in corso e questo corrompe ulteriormente la memoria ( se tu avessi deallocato anche buff[i].lpdata te ne saresti accorto subito).
La documentazione di waveInPrepareHeader infatti dice:


You must call this function before freeing the buffer. After passing a buffer to the device driver with the waveInAddBuffer function, you must wait until the driver is finished with the buffer before calling waveInUnprepareHeader. Unpreparing a buffer that has not been prepared has no effect, and the function returns zero.


Io modificherei il codice come segue per evitare problemi in futuro:
in waveCapture.h
WAVEHDR** buff;

in waveCapture.cpp


// in _start
// Define WAVEHDR Structure:
buff = new WAVEHDR*[dwNumBuffers];
for (int i = 0; i<(int)dwNumBuffers; i++)
{
buff[i] = new WAVEHDR;
ZeroMemory(buff[i],sizeof(WAVEHDR));

// in _stop

MMRESULT h;
for (int u = 0; u<(int)__dwNumBuffers; u++)
{
do {

h = waveInUnprepareHeader(hwi, buff[u], sizeof(WAVEHDR));
switch (h) {
case MMSYSERR_INVALHANDLE:
printf("invalid handle\n");
break;
case MMSYSERR_NODRIVER:
printf("no driver\n");
break;
case MMSYSERR_NOMEM:
printf("no mem\n");
break;
case WAVERR_STILLPLAYING:
printf("still playing\n");
break;
case MMSYSERR_NOERROR:
printf("ok\n");
break;
}
} while (h != MMSYSERR_NOERROR);
}

// nel distruttore

for (int u = 0; u<(int)__dwNumBuffers; u++)
{
free( buff[u]->lpData ); // visto allochi con malloc.
}
delete[] buff;

gianvituzzi
15-01-2010, 01:15
sono esterefatto per come hai risolto il problema e per il tuo interessamento...in effetti mi ero lasciato ingannare dall'apparente funzionamento in singlethreading e non sapevo più dove sbattere la testa...e sopratutto continuavo ad usare in modo errato la WAVDHR, per non parlare poi della deallocazione etc...

devo veder ancora passare molta acqua sotto ai mulini prima che diventi un bravo programmatore

grazie ancora :unz:

shodan
15-01-2010, 12:37
C'è ancora un altro grosso problema di cui non te ne accorgi in singlethreading o con un unico thread (ne devi avere almeno due concorrenti) e riguarda la funzione _start e il distruttore.
E' un errore molto subdolo e per risolverlo occorrerebbe riprogettare tutta la classe.
Per il momento, quindi, ti consiglio di lasciar perdere il multithreading e concentrarti sul linguaggio in se.

In ogni caso evita la CreateThread in C/C++ e usa invece _beginthreadex.
http://forum.html.it/forum/showthread.php?postid=12792064#post12792064

gianvituzzi
15-01-2010, 12:47
per multithreading intendi un thread che usa la classe e altri che fanno altre cose? perchè non si può cmq avere più di un azione di capturing nello stesso momento...

Loading