Конференция "WinAPI" » Внедрение кода в другой поток. Замечания, дополнения, критика [WinXP]
 
  • SPeller © (24.11.09 02:40) [0]
    Есть задача внедряться в произвольный поток своего процесса, выполнять некий код, возвращать на круги своя. Смотрел на модуль AsyncCalls, но там используется VCL, что в моей задаче недостаточно, ибо целевой поток может быть чей угодно, помимо VCL. Но понравился организованный там принцип кодирования внедрения в основной поток через Enter/LeaveMainThread, поэтому взял его за основу. В основном придется внедряться в главный GUI поток чтобы создать там окно со своей оконной процедурой и в дальнейшем слать ему сообщения для выполнения кода в контексте основного потока не нарушая нормальное течение выполнения. Задачу вроде решил, вроде даже всё работает, но что-то какие-то сомнения у меня, всё ли правильно сделал, ибо решал задачу 2 дня, но окончательное решение родилось как-то быстро и получилось (на мой взгляд) простым. Но на сколько оно правильно идеологически - вопрос пока открытый. Вот код, писал на Д2009, должно работать на Д2007:

    unit Synchronizer;

    interface
    uses
     Windows, Classes, SysUtils, libWinApi, Messages, SyncObjs;

    type

     PSyncRec = ^TSyncRec;
     TSyncRec = record
       idCaller: Cardinal;
       idTarget: Cardinal;
       hCaller: THandle;
       hTarget: THandle;
       ctxTarget: TContext;
       Lock: TRTLCriticalSection;
       RefCount: Integer;
       function AddRef: Integer;
       function Release: Integer;
     end;

     TSynchronizer = class(TThread)
     private
       FSyncList: TList;
       FSyncListLock: TCriticalSection;
     protected
       function CreateSync(idTarget: Cardinal): PSyncRec;
       procedure BeginSync(SR: PSyncRec);
       function FindSync(idTarget: Cardinal): PSyncRec;
       procedure EndSync(SR: PSyncRec);
       procedure FreeSync(SR: PSyncRec);
       procedure DoSync(SR: PSyncRec; Flag: PCardinal);
       procedure DoSyncEnd(idTarget: Cardinal; Flag: PCardinal);
       function LockTargetThreadSync(idTarget: Cardinal): PSyncRec;
       procedure UnlockTargetThreadSync(idTarget: Cardinal);
       function ProcessMessages: Boolean;
       function ProcessMessage(var Msg: TMsg): Boolean;
       procedure Execute; override;
       procedure Quit;
       procedure SyncListLock;
       procedure SyncListUnlock;
     public
       constructor Create;
       destructor Destroy; override;

       function Sync(TargetThreadID: Cardinal): Boolean;
       procedure SyncEnd;
       procedure Post(Msg, Param1, Param2: Integer);
     end;
     

    function InjectThread(TargetThreadID: Cardinal): Boolean;
    procedure LeaveThread;
     

    implementation

    const
     WM_MYSYNC = WM_USER + 1;
     WM_MYSYNCEND = WM_USER + 2;

    var
     Sync: TSynchronizer;

    function InjectThread(TargetThreadID: Cardinal): Boolean;
    begin
     if (TargetThreadID <> GetCurrentThreadId) then
       Result := Sync.Sync(TargetThreadID)
     else
       Result := True;
    end;

    procedure LeaveThread;
    begin
     Sync.SyncEnd;
    end;
     
     
    { TSynchronizer }

    procedure TSynchronizer.BeginSync(SR: PSyncRec);
    begin
     SyncListLock;
     try
       SR.hCaller := OpenThread(THREAD_ALL_ACCESS, False, SR.idCaller);
       SR.hTarget := OpenThread(THREAD_ALL_ACCESS, False, SR.idTarget);
     finally
       SyncListUnlock;
     end;
    end;

    constructor TSynchronizer.Create;
    begin
     inherited Create(True);
     FSyncList := TList.Create;
     FSyncListLock := TCriticalSection.Create;
     Resume;
    end;

    function TSynchronizer.CreateSync(idTarget: Cardinal): PSyncRec;
    begin
     SyncListLock;
     try
       Result := AllocMem(SizeOf(Result^));
       Result.idTarget := idTarget;
       Result.Lock.Initialize;
       FSyncList.Add(Result);
     finally
       SyncListUnlock;
     end;
    end;

    destructor TSynchronizer.Destroy;
    begin
     SyncListLock;
     SyncListUnlock;
     FSyncListLock.Free;
     FSyncList.Free;
     inherited;
    end;

    procedure TSynchronizer.DoSync(SR: PSyncRec; Flag: PCardinal);
    var
     ctxCaller: TContext;
    begin
     BeginSync(SR);
     SuspendThread(SR.hCaller);
     SuspendThread(SR.hTarget);
     SR.ctxTarget.ContextFlags := CONTEXT_FULL;
     GetThreadContext(SR.hTarget, SR.ctxTarget);
     ctxCaller.ContextFlags := CONTEXT_FULL;
     GetThreadContext(SR.hCaller, ctxCaller);

     SetThreadContext(SR.hTarget, ctxCaller);
     Flag^ := 1;
     PostThreadMessage(SR.idTarget, WM_USER, 0, 0);
     ResumeThread(SR.hTarget);
    end;

    procedure TSynchronizer.DoSyncEnd(idTarget: Cardinal; Flag: PCardinal);
    var
     sr: PSyncRec;
     ctxTmp: TContext;
    begin
     sr := FindSync(idTarget);
     SuspendThread(sr.hTarget);
     ctxTmp.ContextFlags := CONTEXT_FULL;
     GetThreadContext(sr.hTarget, ctxTmp);
     SetThreadContext(sr.hTarget, sr.ctxTarget);
     SetThreadContext(sr.hCaller, ctxTmp);
     Flag^ := 1;
     ResumeThread(sr.hTarget);
     ResumeThread(sr.hCaller);
     EndSync(sr);
     UnlockTargetThreadSync(sr.idTarget);
    end;

    procedure TSynchronizer.EndSync(SR: PSyncRec);
    begin
     SyncListLock;
     try
       CloseHandle(SR.hCaller);
       CloseHandle(SR.hTarget);
       SR.hCaller := 0;
       SR.hTarget := 0;
       FillChar(SR.ctxTarget, SizeOf(SR.ctxTarget), 0);
       SR.idCaller := 0;
     finally
       SyncListUnlock;
     end;
    end;

    procedure TSynchronizer.Execute;
    var
     msg: TMsg;
    begin
     PeekMessage(msg, 0, WM_USER, WM_USER, PM_NOREMOVE);
     while ProcessMessages and WaitMessage do;
    end;

    function TSynchronizer.FindSync(idTarget: Cardinal): PSyncRec;
    var
     i: Integer;
    begin
     SyncListLock;
     try
       for i := 0 to FSyncList.Count - 1 do
       begin
         Result := FSyncList[i];
         if (Result.idTarget = idTarget) then
           Exit;
       end;
     finally
       SyncListUnlock;
     end;
     Result := nil;
    end;

    procedure TSynchronizer.FreeSync(SR: PSyncRec);
    begin
     SyncListLock;
     try
       FSyncList.Remove(SR);
       SR.Lock.Destroy;
       FreeMem(SR);
     finally
       SyncListUnlock;
     end;
    end;

    function TSynchronizer.LockTargetThreadSync(idTarget: Cardinal): PSyncRec;
    begin
     SyncListLock;
     try
       Result := FindSync(idTarget);
       if (Result = nil) then
         Result := CreateSync(idTarget);
       Result.AddRef;
     finally
       SyncListUnlock;
     end;
     Result.Lock.Enter;
    end;

    procedure TSynchronizer.Post(Msg, Param1, Param2: Integer);
    begin
     PostThreadMessage(ThreadID, Msg, Param1, Param2);
    end;

    function TSynchronizer.ProcessMessage(var Msg: TMsg): Boolean;
    begin
     Result := True;
     case Msg.message of
       WM_MYSYNC:
         DoSync(Pointer(Msg.lParam), Pointer(Msg.wParam));
       WM_MYSYNCEND:
         DoSyncEnd(Msg.lParam, Pointer(Msg.wParam));
       WM_QUIT:
         Result := False;
     end;
    end;

    function TSynchronizer.ProcessMessages: Boolean;
    var
     item: TMsg;
    begin
     Result := True;
     while Result and PeekMessage(item, 0, 0, 0, PM_REMOVE) do
       Result := ProcessMessage(item);
    end;

    ...

  • SPeller © (24.11.09 02:41) [1]
    procedure TSynchronizer.Quit;
    begin
     Post(WM_QUIT, 0, 0);
     WaitFor;
    end;

    function TSynchronizer.Sync(TargetThreadID: Cardinal): Boolean;
    var
     flag: Cardinal;
     sr: PSyncRec;
    begin
     Result := True;
     sr := LockTargetThreadSync(TargetThreadID);
     sr.idCaller := GetCurrentThreadId;
     flag := 0;
     Post(WM_MYSYNC, Integer(@flag), Integer(sr));
     while (flag = 0) do;
     if (sr <> nil) then;
    end;

    procedure TSynchronizer.SyncEnd;
    var
     flag: Cardinal;
    begin
     flag := 0;
     Post(WM_MYSYNCEND, Integer(@flag), GetCurrentThreadId);
     while (flag = 0) do;
    end;

    procedure TSynchronizer.SyncListLock;
    begin
     FSyncListLock.Enter;
    end;

    procedure TSynchronizer.SyncListUnlock;
    begin
     FSyncListLock.Leave;
    end;

    procedure TSynchronizer.UnlockTargetThreadSync(idTarget: Cardinal);
    var
     sr: PSyncRec;
    begin
     SyncListLock;
     try
       sr := FindSync(idTarget);
       sr.Lock.Leave;
       if (sr.Release = 0) then
         FreeSync(sr);
     finally
       SyncListUnlock;
     end;
    end;

    { TSyncRec }

    function TSyncRec.AddRef: Integer;
    begin
     Result := InterlockedIncrement(RefCount);
    end;

    function TSyncRec.Release: Integer;
    begin
     Result := InterlockedDecrement(RefCount);
    end;

    initialization
     Sync := TSynchronizer.Create;

    finalization
     Sync.Quit;
     Sync.Free;

    end.



    Использовать так:

     TMyThread = class(TThread)
     protected
       procedure Execute; override;
     end;

    { TMyThread }

    procedure TMyThread.Execute;
    begin
     InjectThread(MainThreadID);
     try
       if (GetCurrentThreadId = MainThreadID) then
       begin
         MessageBox(Form1.Handle, 'Work!!!', nil, 0);
         //Logger.LogText('', 'Injected code');
       end;
     finally
       LeaveThread;
     end;
    end;

    { TForm1 }

    procedure TForm1.Button3Click(Sender: TObject);
    var
     t: TMyThread;
    begin
     t := TMyThread.Create(True);
     t.FreeOnTerminate := True;
     t.Resume;
    end;



    Из модуля libWinApi понадобится следующее:

    const
     THREAD_ALL_ACCESS                 = $1F03FF;

    function OpenThread(
     dwDesiredAccess: DWORD; bInheritHandle: BOOL; dwThreadId: DWORD): THandle; stdcall;
    function OpenThread; external kernel32 name 'OpenThread';



    Принцип работы. Есть поток-менеджер, который обрабатывает запросы на синхронизацию потоков. Вызывающий поток, желающий выполнить часть своего кода от имени другого потока, заходит в критическую секцию целевого потока (чтобы исключить одновременные внедрения в один поток), отправляет запрос менеджеру и зависает в бесконечном цикле в ожидании когда менеджер обработает запрос и выпустит из цикла. Менеджер, получив запрос на синхронизацию, замораживает оба потока, и вызывающего и целевого, затем через Get/SetThread Context задает целевому потоку контекст вызывающего и отпускает целевой поток на выполнение. Для случая когда целевой поток находится в ожидании сообщения менеджер шлет ему сообщение чтобы оживить поток, иначе всё висит до прихода потоку любого сообщения. Таким образом код вызывающего потока со стеком и прочим продолжает выполнение под управлением целевого потока. Дойдя до конечной точки поток снова стучится к менеджеру с запросом на возвращение на круги своя. Тот останавливает целевой поток, возвращает ему его оригинальный контекст, а вызвавшему потоку вручает контекст, который получился в результате выполнения его кода целевым потоком, и выходит из критической секции, разрешая другим потокам внедряться. Вот и всё.

    Помимо возможных подводных камней думаю еще о целесообразности юзать свою очередь вместо системной очереди сообщений. Насколько она выгоднее системной в плане быстродействия?

    Еще один глюк - у меня сейчас из критической секции выходит другой поток, а не тот, который ее занял. Вот и думаю, на сколько это опасно? Ведь работает вроде. Перенос выхода из критической секции в конец метода SyncEnd выдал плавающий экзепшин...

    Нужны ваши мысли, замечания, критика
  • Leonid Troyanovsky © (24.11.09 07:58) [2]

    > SPeller ©   (24.11.09 02:40)  

    > поэтому взял его за основу. В основном придется внедряться
    > в главный GUI поток чтобы создать там окно со своей оконной
    > процедурой и в дальнейшем слать ему сообщения для выполнения

    Тоже мне бином Ньютона.

    >    SR.hCaller := OpenThread(THREAD_ALL_ACCESS, False, SR.
    > idCaller);
    >    SR.hTarget := OpenThread(THREAD_ALL_ACCESS, False, SR.
    > idTarget);

    ??

    > procedure TSynchronizer.DoSync(SR: PSyncRec; Flag: PCardinal);

    Ой, как все запущено.

    Так перректально работать с собственными потоками?

    Код - в печь, почитать что-либо, скажем, хоть
    http://rsdn.ru/summary/227.xml

    --
    Regards, LVT.
  • Вариант (24.11.09 08:01) [3]
    Ответ на вопрос


    > Еще один глюк - у меня сейчас из критической секции выходит
    > другой поток, а не тот, который ее занял. Вот и думаю, на
    > сколько это опасно? Ведь работает вроде.



    Цитата из MSDN
    > If a thread calls LeaveCriticalSection when it does not
    > have ownership of the specified critical section object,
    >  an error occurs that may cause another thread using EnterCriticalSection
    > to wait indefinitely.


    И приблизительный перевод
    "если поток вызвавший LeaveCriticalSection, не является владельцем указанного объекта критическая секция, возникает ошибка , которая может привести к неопределенному по времени ожиданию другим потоком, вызвавшим EnterCriticalSection".
  • SPeller © (24.11.09 08:21) [4]

    > Ой, как все запущено

    > Код - в печь, почитать что-либо, скажем, хоть
    > http://rsdn.ru/summary/227.xml

    Что именно там читать? Можно конкретней что не так?


    > Вариант   (24.11.09 08:01) [3]
    Всё понял
  • Leonid Troyanovsky © (24.11.09 08:33) [5]

    > SPeller ©   (24.11.09 08:21) [4]

    > > http://rsdn.ru/summary/227.xml

    > Что именно там читать?

    Что понравится, то и читай.
    Там и рядом еще много всякой мудрости.

    > Можно конкретней что не так?

    Все не так.
    Не работают со своими потоками через функции отладки.

    Да и, во-ще, смешано все в кучу: гуи, контексты, критсекции
    и оконные сообщения.

    Пока неясна даже цель (или смысл) показанного.

    --
    Regards, LVT.
  • SPeller © (24.11.09 08:46) [6]

    > Что понравится, то и читай.
    > Там и рядом еще много всякой мудрости
    Не нашел ничего, что мне нужно. Потому и спросил, что же там для меня полезного?


    > Все не так.
    > Не работают со своими потоками через функции отладки
    Как так? Работают, отчего нет? Вроде цель с огрехами, но достигнута.


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


    > Пока неясна даже цель (или смысл) показанного
    Критика. Только не написал сразу что важна не "критика ради критики", а критика по существу. Пока что от тебя я увидел только непонятные нападки в духе "выкинуть всё и переписать" без объяснений. Потому что тебе так почудилось. Ты спроси что тебе не понятно в реализации - я тебе расскажу зачем я написал код именно так, а не иначе.
  • Leonid Troyanovsky © (24.11.09 09:07) [7]

    > SPeller ©   (24.11.09 08:46) [6]

    > > Там и рядом еще много всякой мудрости
    > Не нашел ничего, что мне нужно.

    А мы пока не поняли, что тебе нужно.

    > Как так? Работают, отчего нет? Вроде цель с огрехами, но
    > достигнута.

    Чего непонятного? Это debug API.
    Работать так со _своими_ потоками - извращение.

    > гуй - потому что часто придется залазить в поток, который
    > обрабатывает гуй

    Чего в него лазить? Послал оконное сообщение - синхронное
    или асинхронное, все и выполнит. И очереди, слава богу, есть.

    > контексты - ну это то, чем живет поток, на сколько я понимаю

    Пока не понимаем, что ты понимаешь.

    Пока не ясно само устремление выполнять код в контексте
    чужого потока. Для гуи я еще могу понять, но все уже
    украдено до нас, см. выше.
    Скорее всего, на это толкает некий существующий
    непотокобезопасный (видимо, кривонаписанный) код.
    Ну, если уж так хочется в чужом изволь - см., например,
    QueueUserAPC и прочий  LPC.

    > вникнуть в решение?

    Решение чего? Проблема не озвучена.

    > нападки в духе "выкинуть всё и переписать" без объяснений

    Для объяснений нужна база. Пока я видел одну путанницу.

    --
    Regards, LVT.
  • SPeller © (24.11.09 10:34) [8]

    > QueueUserAPC
    А оно будет работать когда поток в ожидании оконного сообщения и чужим программистом не было предусмотрено что кто-то захочет залезть в поток? Когда поток в введен в ожидание не функциями SleepEx, SignalObjectAndWait, WaitForSingleObjectEx, WaitForMultipleObjectsEx, or MsgWaitForMultipleObjectsEx, как хочет того msdn?


    > А мы пока не поняли, что тебе нужно.
    Нужно единожды создать окно в целевом потоке чтобы затем некий код выполнять синхронизированно с этим потоком. Поток, в который надо влезть, неизвестного происхождения и может быть написан кем угодно, поскольку мой код может быть как скомпилен с приложением вместе, так и быть в длл, в том числе динамически загружаемой (в этом случае поток, инициализирующий длл, не обязан быть главным потоком приложения, поэтому MainThreadID, доступный в ртл длл, будет указывать не на то что нужно).


    > Это debug API.
    > Работать так со _своими_ потоками - извращение
    Ну и что что debug. Свои обязанности выполняет ведь. Или теперь недокументированными функциями тоже нельзя пользоваться?
  • SPeller © (24.11.09 10:49) [9]

    > Скорее всего, на это толкает некий существующий
    > непотокобезопасный (видимо, кривонаписанный) код
    Прорицатель, да? :) Предсказамус :) То, что я могу чего-то не знать, еще не говорит что я полный ламер подобно большинству вопрошающих, и меня надо пинать в привычном местном мастаковском духе. Благо у меня иммунитет уже к этому :) Ах да, простите, когда думал как решить задачу на глаза ничего кроме Get/SetThreadCOntext не попалось, и здесь я, простите, не спрашивал как решить. Что нашел, что придумал то и выложил показать и спросить.

    Толкает на это всё то, что создается объект, который, подобно COM объекту в апартменте, должен выполняться в главном потоке с сериализацией вызовов. Некий рабочий поток может через мой ком-подобный объект обращаться к гую. Вот тут и нужно чтобы мой объект был в одном потоке с этим гуем. При этом абсолютно не обременяя гуйный поток заботой об этом.
  • SPeller © (24.11.09 12:08) [10]
    Кстати, по поводу debug некашерно юзать в программа. Интересно, для чего в мсдн в описании QueueUserAPC написано:

    hThread
    [in] Handle to the thread. The handle must have the THREAD_SET_CONTEXT access right


    Уж не для "страшных" ли debug функций Get/SetThreadContext, для которых всё тот же мсдн говорит The thread identified by the hThread parameter is typically being debugged, but the function can also operate when the thread is not being debugged?
  • Leonid Troyanovsky © (24.11.09 12:33) [11]

    > SPeller ©   (24.11.09 10:49) [9]

    > чтобы мой объект был в одном потоке с этим гуем. При этом
    > абсолютно не обременяя гуйный поток заботой об этом.

    Для создания в произвольном GUI thread окна много усилий не требуется.
    Для синхронного вызова любых процедур (функций), выполняющихся
    в контексте оного потока, достаточно SendMessage.

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

    --
    Regards, LVT.
  • Leonid Troyanovsky © (24.11.09 12:43) [12]

    > SPeller ©   (24.11.09 10:49) [9]

    >  Что нашел, что придумал то и выложил показать и спросить.

    Ну, я, например, и ответил. Глумлений же, IMO, пока не допускал.
    Предположение о кривости некого кода с моей стороны вполне
    обосновано, судя, хотя бы, по твоему настойчивому стремлению
    исполнять его в неком контексте.

    --
    Regards, LVT.
  • Leonid Troyanovsky © (24.11.09 12:50) [13]

    > SPeller ©   (24.11.09 10:34) [8]

    > А оно будет работать когда поток в ожидании оконного сообщения
    > и чужим программистом не было предусмотрено что кто-то захочет
    > залезть в поток?


    > так и быть в длл, в том числе динамически загружаемой (в
    > этом случае поток, инициализирующий длл, не обязан быть
    > главным потоком приложения,

    Вот оно, как. "Мой процесс".
    А выглядит как ворованный.

    --
    Regards, LVT.
  • SPeller © (24.11.09 13:50) [14]

    > Вот оно, как. "Мой процесс".
    > А выглядит как ворованный.
    Я пакостями не занимаюсь, мне не 17 лет :) В проектах просто могу разных использовать, которые могут быть написаны на чем угодно. Так и быть, скажу что пишу аналог стандартного кома. Дком уже готов. Для чего? Позанимавшись любовью с попытками победить настройки безопасности стандартного дкома понял что проще написать один раз своё, чем постоянно заниматься интимом с пользователями, у которых постоянно будут возникать проблемы. А так одно сетевое соединение вместо россыпи соединений для каждого удаленного интерфейса, плюс полный контроль трафика, возможность работы хоть по хттп-хмл, хоть через почту. А общение с точки зрения программы - как с обычным ком объектом. Как там вызовы убегают на удаленную машину - не основной программы забота. И не ее забота где объект физически: в одном процессе или на другом конце города. Кроме того, никаких записей в реестре, что очень хорошо сказывается на юзабилити: скачал и запустил, особенно когда нет админских прав. До сего момента всё работало в куче разных рабочих потоков и возникли ненужные костыли когда некий объект используется в программе, взаимодействует с гуем, и при этом является ком-сервером. Когда к нему приходит уведомление и нужно отреагировать на гуе, то оченама линива каждый раз писать диспетчирование сообщений в основной форме. Хочется в методе, вызванном в моем объекте извне, просто написать DialogForm.ShowModal, к примеру, и всё, и не париться ничем, особенно когда не известно, где клиент - создан тут же формой, локально, или он удаленный и вызов идет из глубин клиент-сервера, диспетчирующего сетевое взаимодействие пулом рабочих потоков. Так вот, сабж затеян для того, чтобы организовать свою реализацию апартмента, чтобы незаметно для основного приложения создавать окно в основном потоке в заранее неопределенный момент, чтобы все обращения к моему объекту прозрачно шли через это окно и не вызывали проблем в работе с основным потоком и гуем, чтобы не думать вообще ни о каком межпотоковом взаимодействии и защите данных, как будто нет никаких потоков. Стандартный ком этим тоже занимается, но мне нужна своя реализация по причинам, описанным выше. Поэтому если я чересчур сложно взялся за решение задачи, то можно было спокойно сказать что это всё лишнее и вот так-то гораздо проще и элегантнее, подсказав что нужно использовать, а не заниматься стёбом "Тоже мне бином Ньютона", "Ой, как все запущено", "Так перректально работать", "Что понравится, то и читай. Там и рядом еще много всякой мудрости", "Все не так", "А выглядит как ворованный".


    > Глумлений же, IMO, пока не допускал
    Это тебе так кажется. На дельфимастере особая манера общения мастеров и их подражателей с теми, кто из-за экрана монитора и по посту на форуме показался не достоин высочайшего снисхождения в виде нормального общения и дискуссии. Но это уже оффтопик.
  • SPeller © (24.11.09 13:55) [15]

    > Для создания в произвольном GUI thread окна много усилий
    > не требуется
    А сразу указать где оно, которое не требует усилий, нельзя было? Мне гугл на запросы выдавал только тысячи копипастов как создать поток в чужом процессе, как загрузить в него длл, но ни одного варианта как из соседнего потока создать в другом окно. Может, я неправильно искал, конечно, не спорю. Но и на это можно было спокойно указать, без высокомерия.
  • имя (24.11.09 14:05) [16]
    Удалено модератором
  • SPeller © (24.11.09 14:19) [17]
    прошу не развивать здесь оффтопик
 
Конференция "WinAPI" » Внедрение кода в другой поток. Замечания, дополнения, критика [WinXP]
Есть новые Нет новых   [134431   +15][b:0][p:0.006]