Справочник функций

Ваш аккаунт

Войти через: 
Забыли пароль?
Регистрация
Информацию о новых материалах можно получать и без регистрации:

Почтовая рассылка

Подписчиков: -1
Последний выпуск: 19.06.2015

Создание потоков в пределах экземпляра класса.

63K
04 апреля 2011 года
SimSonic
16 / / 14.03.2011
Решил просто поделиться опытом, когда нужно создать поток, при этом дать ему доступ к определенному экземпляру класса, как будто члену этого класса.
Перво-наперво, определяю такой класс:
 
Код:
// .hpp
typedef class CGenericVirtualClass
{
public:
   virtual ~CGenericVirtualClass();
} *PGenericVirtualClass, *LPGenericVirtualClass;

// .cpp
CGenericVirtualClass::~CGenericVirtualClass() {};

И все свои основные рабочие классы в будущем я наследую от него. Что это даёт?:
1. Все наследуемые классы имеют таблицу виртуальных методов, с первой записью деструктора. Если где-то я буду подменять один объект на другой, я уверен, что при освобождении вызовется правильный деструктор;
2. Это необходимо для определения типа метода-члена класса. Делаю это:
 
Код:
// .hpp
typedef void (CGenericVirtualClass::*PGenericThreadProc) (void *pOption, DWORD & rRetResult);
typedef void (CGenericVirtualClass::*LPGenericThreadProc)(void *pOption, DWORD & rRetResult);

В будущем любой класс, в котором мне нужно создать поток, я просто наследую от CGenericVirtualClass, и прописываю в нём метод-поток
void мойкласс::имяметода(void *, DWORD &);
Теперь нужно произвести его запуск. Для этого пишу функцию, и несколько её помощников:
Код:
// .hpp
// Макрос для удобства написания коды вызова
#define GENERIC_THREAD(x) ((LPGenericThreadProc)&x)
// Прототип функции создания потока
bool CreateGenericThread(
    LPGenericVirtualClass pObject,     // Обязательный указатель на объект
    LPGenericThreadProc   pEntryPoint, // Обязательный указатель на его метод
    void    *pOption          = NULL,  // Опциональные аргументы
    LPHANDLE pRetThreadHandle = NULL); // Возвращаемый HANDLE потока

// .cpp
// Структура, передаваемая во внутренностях моего запускателя :-)
typedef struct SGenericVirtualStarterInfo
{
    LPGenericVirtualClass pObject; // Объект, для которого запускаем метод
    LPGenericThreadProc   pThread; // Указатель на точку входа (метод)
    void                 *pOption; // Опциональные аргументы потоку
} *PGenericVirtualStarterInfo, *LPGenericVirtualStarterInfo;
// Внутренности моего запускателя :-)
static DWORD WINAPI GenericVirtualThreadStarterFn(LPGenericVirtualStarterInfo pInfo)
{
    // Возвращаемое потоком значение
    DWORD Result = 0;
    if(pInfo)
    {
        // Дубликат в стек, исключающий утечку памяти при использовании ExitThread(...);
        SGenericVirtualStarterInfo Info = * pInfo;
        delete pInfo;
        // Вызов метода в экземпляре класса
        if(Info.pObject && Info.pThread)
            (Info.pObject->*(Info.pThread))(Info.pOption, Result);
    };
    return Result;
};
// Сама функция запуска. Вернет true, если поток удачно создан, иначе false.
bool CreateGenericThread(
    LPGenericVirtualClass pObject, LPGenericThreadProc pEntryPoint,
    void *pOption, LPHANDLE pRetThreadHandle)
{
    // Если я не получаю указатель на объект и указатель на метод, выйти
    if(!(pObject && pEntryPoint))
        return false;
    // Создаю временный блок с данными для передачи новому потоку
    LPGenericVirtualStarterInfo pInfo = new SGenericVirtualStarterInfo;
    if(pInfo == NULL)
        return false;
    // Заполняю поля этого блока
    pInfo->pObject = pObject;
    pInfo->pThread = pEntryPoint;
    pInfo->pOption = pOption;
    // Пытаюсь создать новый поток
    HANDLE hThread = CreateThread(NULL, 0,
        (LPTHREAD_START_ROUTINE)&GenericVirtualThreadStarterFn,
        (LPVOID)pInfo, 0, NULL);
    // Если извне просят HANDLE, верну его
    if(pRetThreadHandle)
        *pRetThreadHandle = hThread;
    // Если поток удачно создан, возвращаю true
    if(hThread)
        return true;
    // Иначе долой выделенную память и верну false
    delete pInfo;
    return false;
};

Всё готово.
Далее, показываю на примере, как использовать написанный код. Дополнительно в этом примере показывается, как дожидаться завершения потока в деструкторе.
Код:
// .hpp
class CMyDemo : public CGenericVirtualClass
{
protected:
    struct
    {
        HANDLE hThread;
        bool bThreadWork;
    } Sync;
    void ThreadFn(void * pOption, DWORD & rRetResult);
public:
    CMyDemo();
    virtual ~CMyDemo();
};

// .cpp
CMyDemo::CMyDemo() // Конструктор
{
    Sync.hThread = NULL;
    Sync.bThreadWork = true;
    CreateGenericThread(this, GENERIC_THREAD(CMyDemo::ThreadFn), NULL, &Sync.hThread);
};
CMyDemo::~CMyDemo() // Деструктор
{
    // Подать какой-то сигнал в поток, что пора завершаться. В моём (!) примере это будет так:
    Sync.bThreadWork = false;
    // Ожидаю завершения
    if(Sync.hThread)
    {
        WaitForSingleObject(Sync.hThread, INFINITE);
        CloseHandle(Sync.hThread);
        Sync.hThread = NULL;
    };
};
void CMyDemo::ThreadFn(void * pOption, DWORD & rRetResult) // Поток в классе
{
    while(Sync.bThreadWork)
    {
        Sleep(1000);
        MessageBeep(MB_OK);
    };
};

// .cpp
int main() // Пример приложения, использующего демонстрационный класс
{
    // Создаю объект
    CMyDemo Demo;
    // Ничего не делаю 5 секунд
    Sleep(5000);
    // Удачи!
    return 0;
};

Отдельно стоит заметить, что поток вовсе не обязательно создавать в конструкторе и удалять в деструкторе, и количество потоков также может быть произвольным.
Не бойтесь использовать своё воображение, и про логику/адекватность не забывайте, конечно.
Надеюсь, что это будет кому-нибудь полезно. Спасибо за внимание.
Реклама на сайте | Обмен ссылками | Ссылки | Экспорт (RSS) | Контакты
Добавить статью | Добавить исходник | Добавить хостинг-провайдера | Добавить сайт в каталог