Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Delphi: WinAPI и системное программирование > Подвисание при создании потока в хуке


Автор: kami 17.5.2009, 15:52
В продолжение темы http://forum.vingrad.ru/topic-258798.html.
По рекомендации dumb вместо пайпов стал использовать MMF.
Работа с mmf производится в доп.потоке, данные добавляются асинхронно.
Проблема:
при аттаче dll в процессы, они подвисают на создании этого доп.потока, обслуживающего mmf.
Подвисают на строках в конструкторе:
Код

constructor TMMFThread.Create(SyncMutexName: string; MMFSize: TDataLength);
var
  i: integer;
begin
  inherited Create(True); // создаем спящим
  FMutexName := SyncMutexName; // инициируем поля
  FMMFSize := MMFSize;
  i := 0;
  FWndHandle:=0;
  Resume; // пробуждаем
  while (not PostThreadMessage(ThreadID, WM_NULL, 0, 0)) and (i < 50) and (not Terminated) do // и ждем пока начнет работать очередь сообщений
// вот на этом цикле и идет подвисание.
    begin
      LogFile.WriteChronoStringCRLF('Wait pause. Error='+IntToStr(GetLastError)); // если работать без счетчика i, то мерзнет абсолютно все.
      Sleep(100);
      inc(i);
    end;
end;

Ошибка в лог выводится каждый раз одна и та же - 1444, ERROR_INVALID_THREAD_ID. Несмотря на то, что в действительности поток создается и работает нормально (данные от него через mmf приходят исправно).
Очередь сообщений доп.потока так же создается и используется:

Код

procedure TMMFThread.Execute;
var
  msg: TMSG;
begin
  PeekMessage(msg, 0, WM_USER, WM_USER, PM_NOREMOVE);
  // инициализация всего и вся.
  while not Terminated and GetMessage(msg, 0, 0, 0) do
    if msg.hwnd = 0 then
      case msg.Message of
        msgThreadClose: /
          begin
            Terminate;
            break;
          end;
      else
        DispatchMessage(msg);
      end
    else
      DispatchMessage(msg);
 // деинициализация всего и вся.
end;


тестовый пример - во вложении.

Где я не прав?

Добавлено через 1 минуту и 17 секунд
ай-я, пока редактировал - вложение потерялось :(
вот оно.

Автор: MetalFan 17.5.2009, 16:11
на сколько я помню, не рекомендуется делать лишние телодвижения(в т.ч. создавать потоки) в DllMain при загрузке библиотеки...

Добавлено через 9 минут и 52 секунды
хотя нет, я не прав - явного запрета на создание потока в DllMain я не нашел...
но... может стоит крайне упростить код, выполняемый в ней. не очень хорошо пускать цикл, начинать писать в файл, чего-то ждать, имхо.

Автор: kami 17.5.2009, 16:21
Цитата(MetalFan @  17.5.2009,  16:11 Найти цитируемый пост)
на сколько я помню, не рекомендуется делать лишние телодвижения(в т.ч. создавать потоки) в DllMain при загрузке библиотеки...

точно, нашел такое...
http://msdn.microsoft.com/en-us/library/ms682583(VS.85).aspx, в ремарках.

Если точнее, то
Цитата

Because DLL notifications are serialized, entry-point functions should not attempt to communicate with other threads or processes. Deadlocks may occur as a result.


А як жеш быть?
imho, лучше 1 раз потерять во времени при инициализации, чем каждый раз на вызове хука для синхронизации обращения к разделяемому ресурсу...

Автор: MetalFan 17.5.2009, 16:25
кстати, а как вообще написана DllMain?

Автор: kami 17.5.2009, 16:27
Цитата(MetalFan @  17.5.2009,  16:11 Найти цитируемый пост)
может стоит крайне упростить код, выполняемый в ней. не очень хорошо пускать цикл, начинать писать в файл, чего-то ждать, имхо.

В файл пишется только для отладки, в готовом коде это будет убрано.

Цикл в конструкторе доп.потока могу убрать, но тогда нет уверенности в том, что при обращении к его методам он будет уже проинициализирован.

Сейчас пришла мысль:

А если инициализацию провести при первом вызове процедуры хука?
Правда, это тоже не рекомендуется - инициализация процесс длительный, Windows может не дождаться его окончания и прервать выполнение hook callback функции...

Добавлено через 3 минуты и 52 секунды
Цитата(MetalFan @  17.5.2009,  16:25 Найти цитируемый пост)
кстати, а как вообще написана DllMain?

Код

  hMap := OpenFileMapping(FILE_MAP_READ, False, @s[1]); // здесь содержатся только идентификаторы хуков.
  if (hMap <> 0) and (hMap <> INVALID_HANDLE_VALUE) then
    begin
      hr := MapViewOfFile(hMap,  FILE_MAP_READ,  0, 0,  SizeOf(THookRec));
      if hr <> nil then
        begin
          Move(hr^, HookRec, SizeOf(THookRec)); // скопировали их (идентификаторы хуков) себе
          UnMapViewOfFile(hr);
          begin
            s := 'Start sending data to main='#13#10;
            if Assigned(f) then
              F.Write(s[1], Length(s));

            if not Assigned(MMFThread) then
              MMFThread := TMMFThread.Create(GetMapName, 1024 * 1024); // непосредственно создание Thread-а обмена
              // именно на его конструкторе и "зависаем".
            MMFThread.AsyncAdd := True;
          end
        end
      else
        begin
          s := 'Cant get map view. Error=' + IntToStr(GetLastError) + #13#10;
          if Assigned(f) then
            F.Write(s[1], Length(s));
        end;
      CloseHandle(hMap);
    end
  else
    begin
      s := 'Cant open file mapping. Error=' + IntToStr(GetLastError) + #13#10;
      if Assigned(f) then
        F.Write(s[1], Length(s));
    end;

Автор: MetalFan 17.5.2009, 16:31
Цитата(kami @  17.5.2009,  16:21 Найти цитируемый пост)
imho, лучше 1 раз потерять во времени при инициализации, чем каждый раз на вызове хука для синхронизации обращения к разделяемому ресурсу...

расшифруй плиз...
в общем тут дело такое - создать то поток можно, но нельзя в него "лезть" из dllmain. а инициализировать можно все в Execute потока.
ибо, как написано у http://wm-help.net/books-online/book/59464/59464-14.html#h20t2p5, все DllMain вызываются линейно.
и у тебя как раз такая ситуация, что поток не будет запущен, пока не отработает DllMain c DLL_THREAD_ATTACH, а она не запустится, пока не отработает DLLMain с DLL_PROCESS_ATTACH

Автор: kami 17.5.2009, 16:41
Цитата(MetalFan @  17.5.2009,  16:31 Найти цитируемый пост)
расшифруй плиз

Хук пишет данные в mmf, основная программа "выгребает" их.
Чтобы не было накладок (один недописал, второй уже считал и обнулил mmf) идет синхронизация с помощью мьютексов.
Если добавлять данные в mmf непосредственно из потока, в котором вызыван хук, то неизвестно, сколько времени будет потеряно на WaitFor-ожидание, что для хука недопустимо. Посему было сделано так - данные из хука в доп.поток, работающий с mmf, передаются с помощью PostThreadMessage, что не задерживает выполнение хука. А этот доп.поток пускай ждет сколько угодно - его ожидания не лимитируют.

Это и имел ввиду, говоря "лучше день потерять, зато потом за 5 минут долететь"    smile 

Цитата(MetalFan @  17.5.2009,  16:31 Найти цитируемый пост)
и у тебя как раз такая ситуация, что поток не будет запущен, пока не отработает DllMain c DLL_THREAD_ATTACH, а она не запустится, пока не отработает DLLMain с DLL_PROCESS_ATTACH

Ух ты...
спасибо, буду знать.

Автор: MetalFan 17.5.2009, 17:16
kami, а какой смысл в конструкторе потока слать PostThreadMessage? чтобы убедиться, что создалась очередь сообщений потока?неужели это настолько критично?

Автор: kami 17.5.2009, 17:34
Цитата(MetalFan @  17.5.2009,  17:16 Найти цитируемый пост)
неужели это настолько критично?

В условиях данной задачи (имеется ввиду - полной задачи, а не этого упрощения) - некритично.
Но я не люблю что-то сделав, возвращаться потом к тому же вопросу для доработки. (например, в разрабатываемом классе есть ф-и AddData, GetAllData, но нет GetFirstData).
Вдруг в другой задаче нужно будет отправить критически важные данные сразу после создания MMFThread?

Предпочитаю делать что-либо с претензией на какую-никакую, а универсальность  smile 

Автор: MetalFan 17.5.2009, 19:41
кстати... к алгоритму можно придраться... имхо - он не надежен.
ибо Post{Thread}Message не гарантирует доставку сообщения. а ты наверняка передаешь с пом.него данные, которые затем будут очищаться в потоке? а если потоку сказали Terminate, он завершится не обработав возможно ожидающие в очереди сообщения...
в общем мое мнение: сообщения можно здесь использовать только для нотификации, но никак не для передачи важных данных.

Добавлено через 11 секунд
з.ы. конечно же я могу быть не прав

Автор: CodeMonkey 17.5.2009, 21:53
http://transl-gunsmoker.blogspot.com/2009/01/dllmain.html (серия на 4 поста).
Далее, из ERROR_INVALID_THREAD_ID - двойку вам за знание TThread. Поток запускается ТОЛЬКО после выхода из конструктора (в AfterConstruction). Хитроумные манипуляции с Resume здесь не к месту. Вставьте этот код на Execute. Даже, если вы оставите подобный подход - нет гарантии, что поток успеет создастся до вызова PostThreadMessage - вам всё равно нужна доп. синхронизация.
И ещё MetalFan прав насчёт возмодности пропуска сообщений. Для синхронизации потоков надо использовать события, крит. секции и др. объекты.

Автор: kami 17.5.2009, 21:55
Цитата(MetalFan @  17.5.2009,  19:41 Найти цитируемый пост)
ты наверняка передаешь с пом.него данные, которые затем будут очищаться в потоке?

Совершенно верно.
PostMessage(msgAddData, integer(pData), DataSize);
Цитата(MetalFan @  17.5.2009,  19:41 Найти цитируемый пост)
а если потоку сказали Terminate, он завершится не обработав возможно ожидающие в очереди сообщения...

Поток будет терминирован, если:
1. Завершено приложение, в которое внедрен хук.
2. Вызывано UnHookWindowsHookEx для всех установленных из dll хуков.

И в том и в другом случае приходящие после этого данные уже не актуальны.

Автор: kami 17.5.2009, 22:14
Цитата(CodeMonkey @  17.5.2009,  21:53 Найти цитируемый пост)
двойку вам за знание TThread. Поток запускается ТОЛЬКО после выхода из конструктора (в AfterConstruction)

Протестую!
Код

constructor TThread.Create(CreateSuspended: Boolean);
begin
  inherited Create;
  AddThread;
  FSuspended := CreateSuspended;
  FCreateSuspended := CreateSuspended;
  FHandle := BeginThread(nil, 0, @ThreadProc, Pointer(Self), CREATE_SUSPENDED, FThreadID); 
  // ThreadID становится доступен сразу в конструкторе. Инициация потока (CreateThread) произведена, чего ж еще надо?
  // с точки зрения Windows все более чем нормально. С точки зрения Delphi - все основополагающие манипуляции
  // а именно - добавление ThreadProc в список, IsMultiThread:=True так же выполнены в конструкторе TThread.
  if FHandle = 0 then
    raise EThread.CreateResFmt(@SThreadCreateError, [SysErrorMessage(GetLastError)]);
end;


А AfterConstruction только и делает, что вызывает Resume при необходимости.
Я же вызывал его в явном виде в своем классе после inherited Create(True). Просветите меня, если это ошибочно.
Цитата(CodeMonkey @  17.5.2009,  21:53 Найти цитируемый пост)
 Вставьте этот код на Execute.

В Execute выполняется код, предназначенный к выполнению в доп.потоке, инициализация данных и ожидание начала работы потока должны происходить не в нем.
Цитата(CodeMonkey @  17.5.2009,  21:53 Найти цитируемый пост)
нет гарантии, что поток успеет создастся до вызова PostThreadMessage - вам всё равно нужна доп. синхронизация.

Как раз-таки этот цикл гарантирует, что:
1. поток запущен и вошел в Execute.
2. Очередь сообщений инициализирована и работает.

Добавлено через 4 минуты и 52 секунды
Цитата(CodeMonkey @  17.5.2009,  21:53 Найти цитируемый пост)
И ещё MetalFan прав насчёт возмодности пропуска сообщений. Для синхронизации потоков надо использовать события, крит. секции и др. объекты.

Тем самым стопоря работу осн.потока.
Самым простым методом синхронизации будет создание окна в доп.потоке и передача данных в него с помощью SendMessage, что гарантирует поступление данных в поток до выхода из оконной процедуры, т.е. до выхода из SendMessage.

Такая возможность заложена, в примере (вложение первого поста) реализована.

Добавлено через 11 минут и 44 секунды
Цитата(CodeMonkey @  17.5.2009,  21:53 Найти цитируемый пост)
Подробное описание проблем с DllMain (серия на 4 поста)

Уже понял из msdn.
Кошмар!
dll придется пересматривать :(

Автор: MetalFan 18.5.2009, 12:39
Цитата(kami @  17.5.2009,  22:14 Найти цитируемый пост)
Цитата(CodeMonkey @  17.5.2009,  21:53 Найти цитируемый пост)
Для синхронизации потоков надо использовать события, крит. секции и др. объекты. 

Тем самым стопоря работу осн.потока.

ну уж извините) и рыбку съесть и....  не получится. либо синхронизация либо возможная порча данных. а синхронизация с пом. SendMessage конечно проста, но имхо не самый быстрый и надежный вариант.

Автор: CodeMonkey 18.5.2009, 13:43
Цитата
Тем самым стопоря работу осн.потока.

Цитата
ну уж извините) и рыбку съесть и....  не получится

Нет, почему же: http://msdn.microsoft.com/en-us/library/ms684242(VS.85).aspx (см. также TThread.WaitFor). 
Да даже, если просто тупо WaitForSingleObject - там "завис"-то будет в сотые доли секунды. И уж всяко меньше вашего Sleep(100). Что, долго потоку раскручиваться, что-ли?

Цитата
Я же вызывал его в явном виде в своем классе после inherited Create(True). Просветите меня, если это ошибочно.

А о том, что Resume в AfterConstruction вызывается - вы не подумали?
Такие действия надо делать так:
Цитата
  FThread := TThread.Create(...);
  ...
  WaitForThreadToBeReady(FThread);
  // do something


Цитата
Как раз-таки этот цикл гарантирует, что

Да, что-то я не так этот код прочитал  smile 
Тогда проверяйте свой код - должен работать. Может, конечно, первая итерация и с ERROR_INVALID_THREAD_ID будет, но вторая - успешно.
Что-то мне кажется, вы с i напутали.

Автор: MetalFan 18.5.2009, 14:31
Цитата(CodeMonkey @  18.5.2009,  13:43 Найти цитируемый пост)
А о том, что Resume в AfterConstruction вызывается - вы не подумали?

а что в этом плохого, если поток и так запущен?

Автор: CodeMonkey 18.5.2009, 15:02
Цитата
а что в этом плохого, если поток и так запущен?

За исключением того, что мы городим кривой код на ровном месте - ничего ;)

Автор: MetalFan 18.5.2009, 17:51
CodeMonkey, 
ну с одной стороны, делать Resume в конструкторе TThread нам ничего не мешает, но с другой - это нарушает идеология самого класса...

Автор: kami 18.5.2009, 18:42
Цитата(CodeMonkey @  18.5.2009,  13:43 Найти цитируемый пост)
А о том, что Resume в AfterConstruction вызывается - вы не подумали?

Подумал давно, и тогда же посмотрел.
Код

procedure TThread.AfterConstruction;
begin
  if not FCreateSuspended then // если создали не "спящим",
    Resume; // то пробуждаем.
// а если спящим (мой случай) - ничего не делаем.
end;

И зачем мне перекрывать еще и этот метод? Не вижу разницы в вызове Resume в AfterConstruction или в конструкторе после inherited.

Цитата(CodeMonkey @  18.5.2009,  13:43 Найти цитируемый пост)
Да даже, если просто тупо WaitForSingleObject - там "завис"-то будет в сотые доли секунды.

Вопрос:
а что ждать?
В конструкторе ничего не создается.
В процедуре потока - мьютекс, MMF и окно. Но! Они могут быть не-инициализированы даже после отработки AfterConstruction, ибо кто кроме планировщика потоков Windows может знать, когда управление в первый раз будет отдано в этот новый поток?
Цитата(CodeMonkey @  18.5.2009,  13:43 Найти цитируемый пост)
Тогда проверяйте свой код - должен работать.

Теперь, с учетом 
Цитата(MetalFan @  17.5.2009,  16:11 Найти цитируемый пост)
не рекомендуется делать лишние телодвижения (в т.ч. создавать потоки) в DllMain при загрузке библиотеки...

работает. Правда, одно лишнее телодвижение осталось - создание потока. Вот только без ожидания его инициализации. Потокобезопасные процедуры работы с ним позволяют безболезненно обойти это, за исключением 2-х моментов:
1. поток создан, но данные в execute не инициализированы. В этом случае утечек нет.
2. Поток жестко терминирован. Утечки будут. Малого размера, но будут.

Автор: MetalFan 18.5.2009, 20:27
Цитата(kami @  18.5.2009,  18:42 Найти цитируемый пост)
Малого размера, но будут. 

тебя это, похоже, не должно беспокоить...

Цитата(kami @  17.5.2009,  21:55 Найти цитируемый пост)
Совершенно верно.
PostMessage(msgAddData, integer(pData), DataSize);
Поток будет терминирован, если:
1. Завершено приложение, в которое внедрен хук.
2. Вызывано UnHookWindowsHookEx для всех установленных из dll хуков.

И в том и в другом случае приходящие после этого данные уже не актуальны. 

т.е. необработанное сообщение приведет опять же к небольшой утечке памяти.

Автор: kami 18.5.2009, 21:46
Цитата(MetalFan @  18.5.2009,  20:27 Найти цитируемый пост)
тебя это, похоже, не должно беспокоить

Да.
Я не думаю, что утечка в 2*SizeOf(integer) - большая проблема для чужого приложения. Даже случившаяся несколько раз.
Цитата

Вызывано UnHookWindowsHookEx для всех установленных из dll хуков.

Небольшая поправка:
вызов UnHookWindowsHookEx не приведет к моментальной выгрузке dll из адресного пространства всех процессов, но хуки уже вызываться не будут.

Автор: CodeMonkey 18.5.2009, 23:30
Цитата(kami @  18.5.2009,  21:46 Найти цитируемый пост)
Я не думаю, что утечка в 2*SizeOf(integer) - большая проблема для чужого приложения

http://transl-gunsmoker.blogspot.com/2009/03/blog-post_06.html ;)

Автор: kami 18.5.2009, 23:41
Цитата(CodeMonkey @  18.5.2009,  23:30 Найти цитируемый пост)
На сервере подкачка = смерти ;)

/me тяжко вздыхает.
Знаю. Читал. Стараюсь свести к минимуму.
Но синхронизацию при добавлении данных использовать не могу. В данном случае.

В любом случае - огромное спасибо MetalFan и CodeMonkey за помощь.
Без вас наверное, до сих пор зависал бы на конструкторе потока, пытаясь понять отчего же он не работает.

Кстати, это может объяснить и неработоспособность компонентов-оберток над namedPipes, которые и послужили началом разбирательства на предыдущую и эту темы.

Автор: dumb 19.5.2009, 02:48
kami, я правильно понимаю: избегая "блокировки" на "ожидании" мьютекса, ты создаешь поток, чтобы "быстро" туда постить инфу?
тогда тебе удалось сделать из мухи здоровенного такого африканского слона. smile

если между захватом мьютекса(WaitForSingleObject) и его освобождением(ReleaseMutex) будут только элементарные операции копирования кусков памяти, то функция ожидания(WaitForSingleObject) в подавляющем большинстве случаев будет встречать свободный мьютекс и выполняться без какой-либо задержки.

если же брать в расчет доп.затраты на обслуживание созданных тобой потоков(в количестве равном числу gui-процессов), то суммарное время, затрачиваемое на обработку, будет больше в разы.

Цитата(kami @  18.5.2009,  23:41 Найти цитируемый пост)
Но синхронизацию при добавлении данных использовать не могу. В данном случае.
сдается, что в данном случае такая категоричность безосновательна.

"Premature optimization is the root of all evil" (с)

Автор: CodeMonkey 19.5.2009, 08:37
Мне тоже кажется, что вы что-то не то делаете, но мне как-то лень копаться  smile 
В общем случае надо бы убрать всё из DllMain, все вещи инициализировать по первому обращению, а глобальные ресурсы хранить в глоб. переменных/threadvar/списке и удалять при выгрузке DLL. Плюс предусмотреть ситуацию переустановки хука без выгрузки DLL.

Автор: kami 19.5.2009, 13:06
Цитата(dumb @  19.5.2009,  02:48 Найти цитируемый пост)
 избегая "блокировки" на "ожидании" мьютекса, ты создаешь поток, чтобы "быстро" туда постить инфу?

Нет, этого я  не избегаю.
Работа доп.потока организована в строгом соответствии с Вашими рекомендациями в предыдущей ветке.
А вот добавление данных из другого потока сделано как PostMessage(hwnd, msgAddData, integer(@Data), DataSize);

Цитата(dumb @  19.5.2009,  02:48 Найти цитируемый пост)
если между захватом мьютекса(WaitForSingleObject) и его освобождением(ReleaseMutex) будут только элементарные операции копирования кусков памяти,

Именно так.

Цитата(CodeMonkey @  19.5.2009,  08:37 Найти цитируемый пост)
Мне тоже кажется, что вы что-то не то делаете

Спорить не буду, т.к. учусь, в основном - на своих ошибках smile.
Цитата(CodeMonkey @  19.5.2009,  08:37 Найти цитируемый пост)
но мне как-то лень копаться

Согласен, чужой код, как и душа - потемки.

Цитата(CodeMonkey @  19.5.2009,  08:37 Найти цитируемый пост)
В общем случае надо бы убрать всё из DllMain

Уже думал, попробую и этот вариант.
Цитата(CodeMonkey @  19.5.2009,  08:37 Найти цитируемый пост)
Плюс предусмотреть ситуацию переустановки хука без выгрузки DLL.

Ага, для меня сейчас это самое узкое место.

Автор: CodeMonkey 19.5.2009, 14:14
Цитата
Ага, для меня сейчас это самое узкое место.

Как-то так:

DLL:
Код
var
  SavedHandle: THandle;
  GlobalData: PSomeStruct;  

  Threads: array of TThread; // и т.п.

procedure HookHandler(...);
begin
  if Assigned(GlobalData) and (GlobalData^.Handle <> SavedHandle) then
  begin
    DeleteHookData; // + стоп всего
    SavedHandle := 0; // внутри DeleteHookData
  end;
  if GlobalData = nil then
  begin
    CreateHookData; // + старт всего
    SavedHandle := GlobalData^.Handle; // внутри CreateHookData
  end;

  // работа хука
end;

// DllProc:
begin
  // при process detach:
  DeleteHookData; // + стоп всего
  SavedHandle := 0; // внутри DeleteHookData
end;


App:
Код
var
  GlobalData: PSomeStruct;  

  CreateHookData;
  GlobalData^.Handle := // любое значение, уникально идентифицирующее хук. Например, ровно описатель хука от SetWindowsHookEx
...
  GlobalData^.Handle := 0;
  DeleteHookData;


Добавлено через 3 минуты и 8 секунд
Немного непонятно, на что конкретно ставиться хук. В принципе, если хук, скажем, на GetMessage, то вместо DeleteHookData при process detach можно делать broadcast сообщения, по которому отцепляется DeleteHookData. Ну и в DLLProc вызов всё же оставить - на всякий пожарный.

Автор: dumb 19.5.2009, 16:02
народ, не настаиваю, но может таки на "ты"? - общение все же не официальное.

Цитата(kami @  19.5.2009,  13:06 Найти цитируемый пост)
Нет, этого я  не избегаю.
для чего тогда вообще создается доп.поток в контексте чужого процесса? - из обработчика хука сразу класть в mmf структуру.

Цитата(kami @  19.5.2009,  13:06 Найти цитируемый пост)
Работа доп.потока организована в строгом соответствии с Вашими рекомендациями в предыдущей ветке.
эм. я насчет доп.потока ничего не говорил. сейчас, исходя из того, что вижу в коде hlibstaex, еще раз скажу: не нужен доп.поток.

Автор: kami 19.5.2009, 17:47
Цитата(dumb @  19.5.2009,  16:02 Найти цитируемый пост)
народ, не настаиваю, но может таки на "ты"? - общение все же не официальное.

Согласен, просто привык обращаться уважительно к людям, которые могут что-то подсказать/посоветовать.

Цитата(dumb @  19.5.2009,  16:02 Найти цитируемый пост)
исходя из того, что вижу в коде hlibstaex, еще раз скажу: не нужен доп.поток.

Ок, переделать из потока в обычный класс, обслуживающий mmf - небольшая проблема. Попробую.

Вах!
Цитата(dumb @  19.5.2009,  16:02 Найти цитируемый пост)
исходя из того, что вижу в коде hlibstaex, 

а это как?
Присоединённый файл ( Кол-во скачиваний: 1 ) - это ж я скачивал, чтобы проверить работоспособность ссылки  smile  smile 

Цитата(CodeMonkey @  19.5.2009,  14:14 Найти цитируемый пост)
Как-то так:

Примерно так и думал.
Единственное - до этого времени не держал открытой GlobalData.

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)