Форум программистов «Весельчак У»
  *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

  • Рекомендуем проверить настройки временной зоны в вашем профиле (страница "Внешний вид форума", пункт "Часовой пояс:").
  • У нас больше нет рассылок. Если вам приходят письма от наших бывших рассылок mail.ru и subscribe.ru, то знайте, что это не мы рассылаем.
   Начало  
Наши сайты
Помощь Поиск Календарь Почта Войти Регистрация  
 
Страниц: [1]   Вниз
  Печать  
Автор Тема: Обьект - поток  (Прочитано 13476 раз)
0 Пользователей и 1 Гость смотрят эту тему.
Tумблер
Гость
« : 04-04-2005 11:24 » 

Hi, all !

 как-то раз попытался с смастерить класс-поток в VC используя только WIN-API.
Со статическими обьектами - членами класса получилось довольно
быстро.
 Однако понятно, что это не полноценный класс.
Хочется класс а-ля TThread от Борланд.

 При попытке сделать полностю динамический обьект, проблема
определения адреса поточной процедуры была таки решена ассемблерной вставкой. Улыбаюсь
 Поток заработал, но он "не видит" членов класса. С глобальными переменными
приложения проблем нет.
 Не посоветует ли многоуважаемый ALL что-либо для выхода из кризиса?
 Отлично
Записан
ysv_
Помогающий

ua
Offline Offline

« Ответ #1 : 04-04-2005 17:48 » 

Приблизительно это выглядит так:
class CThread
{
  static void _cdecl threadProc(void* thisPtr);
  public:
    CThread()
    {
      _beginthreadex(..., ..., &threadProc, reinterpret_cast<void *>(this), ..., ...);
      ...
    }
    ...
}

void _cdecl CThread::threadProc(void* thisPtr)
{
  CThread *thread=reinterpret_cast<CThread *>(thisPtr);
  ...
  // через thread получаем доступ к нестатическим членам класса
  ...
}
Записан
Tумблер
Гость
« Ответ #2 : 05-04-2005 11:01 » 

 Спасибо !
Действительно, все дело оказалось в "зыс".

 Замечу, что как раз это:
   static void _cdecl threadProc(void* thisPtr);
  меня и не устраивает. А именно "static".

   Без него можно обойтись, но компилер не дает преобразовать тип указателя
 threadProc для подстановки в _beginthreadex.
 Пришлось применить вставку на ассемблере, что не радует. Жаль
Записан
npak
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #3 : 05-04-2005 11:28 » 

Тумблер,

как вы собираетесь передавать в beginthreadex нестатический метод? Всякий нестатический метод представляет собой пару сущностей <объект, функция> и НЕ ЯВЛЯЕТСЯ просто указателем на функцию.  Вызов метода всегда сопровождается передачей соответствующего объекта.  Только статические методы не привязаны к объектам, поэтому можно брать указатели на статические методы и вызывать их как обычные функции. 

То, что передаётся в beginthreadex или другие функции, написанные на языке Си, должно подчиняться правилам вызова языка Си. Методы объектов этим правилам не соответствуют.

Записан

UniTesK -- индустриальная технология надежного тестирования.

http://www.unitesk.com/ru/
Tумблер
Гость
« Ответ #4 : 05-04-2005 12:15 » 

>>как вы собираетесь передавать в beginthreadex нестатический метод?

 Вот прямо так и по-наглому....  Отлично
И потом - не собираюсь, а уже работает.
Кроме того, это работает у Борланда, а значит я не первопроходец.
К тому же "почему бы и нет" ?
Собственно, конструкция "CreateThread" и заменяющая ее _beginthreadex
специально предусматривает адрес именно процедуры с параметром-указателем.
 Фактически это не что иное, как
базовый адрес динамического обьекта. (Это стало ясно, после применения SoftIce  Ага)
И что нельзя ?  :?
Исследования SoftIce-ом показывают, что все работает "правильно".

Кстати, без ассемблера, похоже, можно обойтись. Видимо, так:
{
 void *some_ptr;
char pstr [20];


  sprintf (pstr, "%x", this->threadRoutine);
  sscanf (pstr, "%x", &some_ptr);

// и даллее :
 
  Handle =(HANDLE) _beginthreadex
    (
     (void *) NULL,                                                   // pointer to thread security attributes
     (unsigned) 0,                                                    // initial thread stack size, in bytes
     (unsigned int (__stdcall *)(void *) )some_ptr,
     (void *) this,                                                    // argument for new thread
     (unsigned) (DWORD)CreateSuspended,               // creation flags
     (unsigned *) (& idTH)                                       // pointer to returned thread identifier
    );

}
 :-P
Записан
npak
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #5 : 05-04-2005 15:03 » 

Я не знаю, что показывает SoftIce, но передача this в качестве lpParameter функции CreateThread приводит к краху приложения

Код:
#include <windows.h>
#include <stdio.h>
#include <stdarg.h>


extern "C" void * HACK_CAST(int ignored, ...) {
va_list args;
void * res;

va_start(args, ignored);
res = va_arg(args, void *);
va_end(args);
return res;
}

class TryBeginThreadEx {
protected:
int objectID;

public:
unsigned startThread(void) {
printf("Thread started, object %d\n", objectID);
return 0;
}

void run(void) {
void * ptr = HACK_CAST(0, this->startThread);

printf("Pointer to start routine: %p\n", ptr);
unsigned long id;
HANDLE ret = CreateThread(NULL, 0, reinterpret_cast<LPTHREAD_START_ROUTINE>(ptr), this, 0, &id );
if (ret == NULL) {
printf("CreateThread failed, error %u\n", GetLastError());
} else {
printf("CreateThread success\n");
}
Sleep(1000);
}

TryBeginThreadEx(): objectID(0) {}
};


extern "C" int main(void) {
TryBeginThreadEx * obj = new TryBeginThreadEx();

obj->run();
return 0;
}

В отладчике оказалось, что при таком вызове this в потоке будет указывать на область памяти начиная со 101, которая запрещена на чтение.

Я бы предположил, что в недрах библиотек Борланда есть функция-пускач, которая умеет вызывать метод Run (или как он называется в Builder) объекта класса TThread
Записан

UniTesK -- индустриальная технология надежного тестирования.

http://www.unitesk.com/ru/
Finch
Спокойный
Администратор

il
Offline Offline
Пол: Мужской
Пролетал мимо


« Ответ #6 : 05-04-2005 15:07 » 

Цитата
Я бы предположил, что в недрах библиотек Борланда есть функция-пускач, которая умеет вызывать метод Run (или как он называется в Builder) объекта класса TThread

Есть макрос, MAKEINSTANCE помоему называется. Но могу ошибаться. И все функции обратного вызова пускаются через него.
« Последнее редактирование: 05-04-2005 15:09 от Finch » Записан

Не будите спашяго дракона.
             Джаффар (Коша)
npak
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #7 : 05-04-2005 15:21 » 

Собственно, как реализовать объектный подход к реализации потока с помощью "секретного" пускача:
Код:
extern "C" unsigned long WINAPI _internal_starter(void * arg);

class TryThread {
protected:
int objectID;

public:
virtual unsigned long threadFunc(void) {
printf("Thread started, thread %d\n", objectID);
return 0;
}

void run(void) {
unsigned long id;
HANDLE ret = CreateThread(NULL, 0, _internal_starter, this, 0, &id );
if (ret == NULL) {
printf("CreateThread failed, error %u\n", GetLastError());
} else {
printf("CreateThread success\n");
}
Sleep(1000);
}

TryThread(int threadID): objectID(threadID) {}
};


extern "C" unsigned long WINAPI _internal_starter(void * arg) {
TryThread * p_thread = reinterpret_cast<TryThread *>(arg);

return p_thread->threadFunc();
}

extern "C" int main(void) {
TryThread * obj = new TryThread(1);

obj->run();
return 0;
}

Пускач _internal_starter используется внутри метода run потока для создания точки входа в поток.
Записан

UniTesK -- индустриальная технология надежного тестирования.

http://www.unitesk.com/ru/
Tумблер
Гость
« Ответ #8 : 05-04-2005 17:04 » 

Я не знаю, что показывает SoftIce, но передача this в качестве lpParameter функции CreateThread приводит к

 Речь шла о _beginthreadex...CreateThread использовать не надо. У Рихтера описано почему.
У меня краха нет. Все работает.
И вообще:
"Пипыл - зацени мой ирокез":

.H -файл:

//---------------------------------------------------------------------------
// file mth.h  04.04.05
//---------------------------------------------------------------------------
#ifndef __MTH_H__
#define __MTH_H__
//---------------------------------------------------------------------------

#include "sasha.h"

//---------------------------------------------------------------------------

#define VER_W32S 0
#define VER_W95 1
#define VER_W98 2
#define VER_WNT 3
#define VER_WXP 4

#define MTH_DEBUG
/*
tpIdle           The thread executes only when the system is idle-Windows
                won't interrupt other threads to execute a thread
                with tpIdle priority.
tpLowest       The thread's priority is two points below normal.
tpLower           The thread's priority is one point below normal.
tpNormal       The thread has normal priority.
tpHigher       The thread's priority is one point above normal.
tpHighest       The thread's priority is two points above normal.
tpTimeCritical   The thread gets highest priority.
*/

//---------------------------------------------------------------------------
class MyThread
{
 private:
//    void *make_addr (unsigned long (__stdcall MyThread::*)(void *));

    volatile BOOL Terminated; // =false
    HANDLE Handle;
    void  *prou;

    int retcode;

    unsigned WINAPI __Execute__ (LPVOID)  ;

    volatile char go_flag;
   volatile int uninstall_place;
   volatile int iw;

    unsigned idTH;

    int err;

    unsigned CreateSuspended;

 protected:


 public:
   
  MyThread (BOOL CreateSuspended=FALSE);
  virtual ~MyThread(void);

  virtual int WINAPI Execute () ;
  virtual int  WINAPI uninstall (void);
 
  BOOL WINAPI GetTerminate (void); 
  void WINAPI type_message (char *mess, char *cap, unsigned int w); 
  void WINAPI Resume(void);
  void WINAPI Suspend(void);
  void WINAPI Terminate(void);
  int  WINAPI GetPriority (void);
  void WINAPI SetPriority (int pr);
};
//---------------------------------------------------------------------------

#endif


.cpp - файл:
//---------------------------------------------------------------------------
// file mth.cpp 04.04.05
// класс - поток
//---------------------------------------------------------------------------


#ifndef _MT
#define _MT
#endif



#include <windows.h>
#include <process.h>

#include <string.h>
#include <stdio.h>
#include "mth.h"
#include "sasha.h"


#ifdef MTH_DEBUG

#include "vd.hpp"
#include "vsd.hpp"
#include "fj.ext"

#endif

//---------------------------------------------------------------------------
MyThread::MyThread (BOOL CrSus)
{
char pstr[20]; 
   
  uninstall_place=0;

  Terminated = false;

  err=0;
  go_flag=-1;

// преобразование типов:
//  prou = make_addr (this->__Execute__);
  sprintf (pstr, "%x", this->__Execute__);
  sscanf  (pstr, "%x", &prou);

  if (CrSus) { CreateSuspended = CREATE_SUSPENDED; }
  else       { CreateSuspended = 0; }

  Handle =NULL;

  Handle =(HANDLE) _beginthreadex
    (
     (void *) NULL,                             // pointer to thread security attributes
     (unsigned) 0,                              // initial thread stack size, in bytes
     (unsigned (WINAPI *)(void *) )prou,
     (void *) this,                             // argument for new thread
     (unsigned) CreateSuspended,                // creation flags
     (unsigned *) (& idTH)                      // pointer to returned thread identifier
    );
 

#ifdef MTH_DEBUG
    sprintf (sss, "TROU=%x %x", prou, *((DWORD *)prou)); vs2->type_string (sss);
    sprintf (sss, "THIS=%x %x", this, *((DWORD *)this)); vs2->type_string (sss);
    sprintf (sss, "THIS->EXE=%x",this->__Execute__);     vs2->type_string (sss);         
#endif

    if (Handle == NULL)
   {
    type_message
      (
      "Install Thread:ERR",
      "Install Thread:ERR",
      MB_OK | MB_ICONERROR | MB_APPLMODAL
      );
      err=1;
   }
}
//---------------------------------------------------------------------------
MyThread::~MyThread(void)
{

}
//---------------------------------------------------------------------------
void WINAPI MyThread::type_message (char *mess, char *cap, unsigned int w)
{
    ::MessageBox(NULL,mess,cap,w);
}
//------------------------------------------------------------------------
int WINAPI MyThread::Execute ()
{
#ifdef MTH_DEBUG
    vs2->type_string ("START EXECUTE", RGB(200,0,200));   
#endif
   
    for ( ; ; )
   {
      if ( MyThread::GetTerminate () ) break;
   }

#ifdef MTH_DEBUG
    vs2->type_string ("END   EXECUTE", RGB(200,0,200));
#endif   

   return 0;
}
//------------------------------------------------------------------------
unsigned WINAPI MyThread::__Execute__ (LPVOID)
{
  go_flag=0;
 
  retcode = MyThread::Execute ();
 
  go_flag=1;

  _endthreadex (retcode);
 
  return 0;
}
//------------------------------------------------------------------------
//
// Автомат завершения потока.
// Включается в WM_TIMER ~200-400- ms
//
// 0 - поток не завершен
// 1 - поток нормально завершен
// 2 - авария - что то сломалось.
//
int WINAPI MyThread::uninstall (void)
{
   switch(uninstall_place)
   {
   case 0:
         MyThread::Terminate();
         uninstall_place=1;
            iw=0;
         return 0;

   case 1:
         if(go_flag leq 1){ uninstall_place=2;}
         return 0;

   case 2:
      {
         DWORD rcode;
         
         if ( ::GetExitCodeThread ((HANDLE)Handle, &rcode) )
         {
            if (rcode lne STILL_ACTIVE)  return 1;
         }
         else      { ++ iw; }
         if (iw leq 5) return 2;
         return 0;
      }

   }
   return 0;
}
//------------------------------------------------------------------------
void WINAPI MyThread::Resume (void)
{
 if (err lne 0) return;
 if (Handle == NULL) return;
 
 ::ResumeThread (Handle);
}
//------------------------------------------------------------------------
void WINAPI MyThread::Suspend (void)
{
 if (err lne 0) return;
 if (Handle == NULL) return;
 
 ::SuspendThread (Handle);
}
//------------------------------------------------------------------------
void WINAPI MyThread::Terminate (void)
{
    Terminated=true;
}
//------------------------------------------------------------------------
BOOL WINAPI MyThread::GetTerminate (void)
{
   return Terminated;
}
//------------------------------------------------------------------------
void WINAPI MyThread::SetPriority (int pr)
{
 if (Handle leq NULL) return;
 ::SetThreadPriority (Handle,pr);
}
//------------------------------------------------------------------------
int WINAPI MyThread::GetPriority (void)
{
 if (Handle leq NULL) return 0;
 return (::GetThreadPriority (Handle) );
}
//---------------------------------------------------------------------------
/*
void *MyThread::make_addr (unsigned long (__stdcall MyThread::*)(void *))
{
void *p;

__asm
{
 mov  eax,[ebp+8]
 mov  p,eax
}
 return p;
}
*/
//---------------------------------------------------------------------------
« Последнее редактирование: 20-12-2007 18:48 от Алексей1153++ » Записан
Chaa
Помогающий

ru
Offline Offline
Пол: Мужской

« Ответ #9 : 06-04-2005 02:59 » 

Tумблер, зачем же так сложно? Можно сделать проще, без вставок на ассемблере, потом проблем меньше будет.
Код:
class MyThread
{
    static void _cdecl InternalThreadProc(void* Param)
    {
        MyThread* pThis = reinterpret_cast<MyThread*>(Param);
        pThis->Execute();
    }

  public:

    MyThread()
    {
        _beginthreadex(&InternalThreadProc, ...,
            reinterpret_cast<void *>(this));
    }

    virtual void Execute() = 0;
};
Записан
npak
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #10 : 06-04-2005 08:45 » 

Chaa

из обсуждения сложилось впечатление, что Тумблер хочет сделать чиста объектную реализацию, без статических методов и внешних функций.  Вроде как текущая реализация _beginthreadex позволяет организовать вызов метода, если передать указатель на метод и соответствующий объект как аргументы.

Это предположение ставит предложенный код в очень сильную зависимость от реализации CRT Microsoft.  Стоит им поменять пускач потоков, как код перестанет работать.  По моему мнению, лучше написать свой пускач (статический или глобальную функцию) чем надеяться на благоволение Microsoft.

Вторая причина не писать так -- переносимость.  Имея свой пускач можно легко спортировать код на другие системы многопотоковости (pthreads, sun threads, прочие ...).  Разбираться на другой операционной системе как они стартуют потоки и как потокам передают инициализирующие данные -- удовольствие очень дорогое.
Записан

UniTesK -- индустриальная технология надежного тестирования.

http://www.unitesk.com/ru/
Tумблер
Гость
« Ответ #11 : 06-04-2005 09:22 » 

Chaa
хочет сделать чиста объектную реализацию, без статических методов
Это предположение ставит предложенный код в очень сильную зависимость от реализации CRT Microsoft.
Точно подмечено ! Именно - обьектную, и именно для Микрософт.
Дело в том, что у Борланда это сделано, значит можно пользоваться. 

Chaa
 Стоит им поменять пускач потоков, как код перестанет работать.

 Они не могут. Пускач - внутри Виндус. Если они это сделают, остановятся
вообще все программы для Виндус.


Chaa

 лучше написать свой пускач..

Вторая причина не писать так -- переносимость.


 Действительно, можно не передавать параметр "this" и написать свой
"стартер". Это я тоже сделал - и мне не понравилось, именно из за причин
плохой переносимости.(не буду приводить исходные тексты - длинно)
 Дело в том, что поточная процедура должна "знать" базовый адрес
своего класса - и все тут.
 Как передать ?
У Микрософт - можно (как выяснилось) через параметр-указатель
при создании потока. Похоже, само наличие такого параметра
у CreateThread не случайно, поскольку Виндус писала сама Микрософт.
 У Борланда я не знаю - как, сейчас это мне не интересно. Но скорее всего - так же.
Может отличаться лишь ассемблерная реализация, но принцип тот же.
Кстати, это легко обнаружить - исходные тексты TThread доступны:
..
# if defined(BI_MULTI_THREAD_RTL)
  Handle = (HANDLE)::_beginthreadNT(&TThread::Execute, 4096, this, 0, 0, &ThreadId);
..

Как видим, нет у Борланда никакого пускача.
А передача "this" - это "правильно" - в среде Виндус по крайней мере.

Итак, я отвлекся.
Как написать стартер, без передачи "зыс"?
Очень просто.
1. смотрим СофтАйсом "внутри" и видим, что базовый адрес
загружается у Микрософта так:

push esi
mov esi,[ebp+...не помню]

и всего делов.
2. мы знаем,  что база = this. получим ее в виде WORD.
3.
a)пропатчим собственные исполнимые коды на:
push esi
mov esi,const ; const=this

или:
 б) первой командой поточной процедуры сделаем:
__asm
{
 mov esi,const
}
Приложение заработает..без проблем. !

 И вот как раз в этом случае про переносимость эээ рассказывать неудобно.

Улыбаюсь
Записан
Chaa
Помогающий

ru
Offline Offline
Пол: Мужской

« Ответ #12 : 06-04-2005 09:48 » 

У меня нет C++Builder'а, но в делфи у борланда все просто. Думаю в билдере 100% тот же код (как бы это было в C++):
Код:
void ThreadProc(TThread* pThread)
{
    pThread->Execute();
}

class TThread
{
  public:

    void Create()
    {
        _beginthreadex(&ThreadProc, 4096,
            reinterpret_cast<void*>(this));
    }

    void Execute();
};
В общем-то функция ThreadProc как раз и выполняет то, что описано в предыдущем посте - помещает this в соответствующий регистр.

Вопрос: зачем так все усложнять и писать код на три экрана, если все это помещается в три строчки и прекрасно работает?
« Последнее редактирование: 20-12-2007 18:52 от Алексей1153++ » Записан
Tумблер
Гость
« Ответ #13 : 06-04-2005 09:55 » 

Вопрос: зачем так все усложнять и писать код на три экрана, если все это помещается в три строчки и прекрасно работает?

 Извините, но Вы не поняли.
У Борланда - "ДА" . Есть хороший класс и все работает.
А у Микрософта для VC такого класса  нет.
 И без помощи ysv в этом конкретном обсуждения, я не смог это сделать.

Всем участникам - ОГРОМНОЕ СПАСИБО !
Записан
Страниц: [1]   Вверх
  Печать  
 

Powered by SMF 1.1.21 | SMF © 2015, Simple Machines