Главная страница
Top.Mail.Ru    Яндекс.Метрика
Текущий архив: 2002.10.31;
Скачать: CL | DM;

Вниз

Не вызывается DLLProc(DLL_PROCESS_DETACH)   Найти похожие ветки 

 
QymL   (2002-08-21 20:53) [0]

В DLL не могу обработать выгрузку библиотеки из процесса
Это глюк Delphi или неверный код:

library t1lib;

uses
Windows;

//uNet in "uNet.pas";
var hth:THandle;
id:CArdinal;
{$R *.res}
function go():DWord;stdcall;
var i:Cardinal;
begin

ExitThread(0);
end;


function DLLEntryPoint(dwReason: DWORD):BOOL; stdcall;
var i:Integer;
Begin
Case dwReason Of
DLL_PROCESS_ATTACH:
Begin
DisableThreadLibraryCalls(HInstance);
hth:=CreateThread(0,0,@go,0,0,id);
End;
DLL_PROCESS_DETACH:
Begin
WaitForSingleObjectEx(hth,INFINITE,true);
TerminateThread(hth,0);
CloseHandle(hth);
End
End;
Result:=true;
End;


begin
DLLProc := @DLLEntryPoint;
DLLEntryPoint(DLL_PROCESS_ATTACH)
end.


 
Digitman ©   (2002-08-22 08:49) [1]

DLL_PROCESS_ATTACH/DETACH вызываются только если загрузка/выгрузка библиотеки инициируется осн.кодовым потоком процесса в run-time либо ОС в процессе PE load-time. В противном же случае (загрузка/выгрузка библиотеки инициируется доп.кодовым потоком в run-time) вызываются DLL_THREAD_ATTACH/DETACH.


 
QymL   (2002-08-22 10:05) [2]

Да,я это знаю.
Я динамически загружаю DLL (LoadLibrary), а при вызове FreeLibrary DLL выгружается, не дожидаясь окончания работы потока.


 
SEM ©   (2002-08-22 11:15) [3]

Дополнение:
Объявите DLLEntryPoint без stdcall.


 
Digitman ©   (2002-08-22 11:15) [4]

Тогда непонятно, в чем проблема, если знаешь это


 
QymL   (2002-08-22 11:27) [5]

2 SEM Пробовал, не помогает
2 Digitman: Проблема в преждевременной выгрузке DLL, как с ней справиться?


 
Digitman ©   (2002-08-22 11:46) [6]

Что значит - "в преждевременной" ?


 
SEM ©   (2002-08-22 11:50) [7]

Когда я убрал stdcall, у меня стал приходить DLL_PROCESS_DETACH
при FreeLibrary.

Что касается выше приведенного кода, то при загрузке Вашей Dll
на сообщение DLL_PROCESS_ATTACH происходит создание потока, который выполняет функцию go. В функции go происходит выход из потока. Почему Dll должна выгрузится? Когда Вы будите выгружать
свою dll (Наверное там, где Вы ее загрузили), то будет вызван
DLL_PROCESS_DETACH.


 
QymL   (2002-08-22 11:59) [8]


> SEM

Дело в том, что если для DLL вызывается FreeLibrary из EXE, то DLL выгружается, не завершая поток, т.е. если в потоке идет цикл, и по выгрузке DLL его надо завершить, то надо обработать DLL_PROCESS_DETACH, что и не получается.


 
QymL   (2002-08-22 12:00) [9]


> Digitman

См. пред. постинг


 
Ученик ©   (2002-08-22 12:07) [10]

А точку останова в DLLEntryPoint(dwReason: DWORD):BOOL; пробовали ?


 
QymL   (2002-08-22 13:10) [11]


> Ученик
Поясните, пожалуйста


 
Digitman ©   (2002-08-22 13:13) [12]

>QymL


> вызывается FreeLibrary из EXE


FreeLibrary может быть вызывана откуда угодно из любой точки исп.кода АП процесса (в т.ч. и из АП, распределенного под другие используемые тобой DLL, вызывающие данную DLL) и сколько угодно раз. Здесь гораздо важно четкое осознание того, в каком кодовом потоке происходит вызов FreeLibrary(). Нотификацию DLL_PROCESS_DETACH, которую ты "ловишь", можно "поймать" лишь в том случае, если ранее имела место нотификация DLL_PROCESS_ATTACH. Последняя же возникает лишь при явном или неявном вызове LoadLibrary() в контексте осн.кодового потока процесса, и неважно , какой код в этот момент исполняется - код хост-приложения или код некоей иной DLL, загруженной в АП тек.процесса.

Иными словами, если не было DLL_PROCESS_ATTACH, не жди и DLL_PROCESS_DETACH.

Лучше и надежней всего будет регистрировать ATTACH- и DETACH-нотификации (безразлично каких - PROCESS или THREAD) с определением ThreadId каждой.

В некоем частном случае я реализовал это так :

(см. усеченный код ниже)


 
Digitman ©   (2002-08-22 13:13) [13]


unit ThreadManager; // модуль в составе DLL

interface

uses Windows, Messages, Classes, SysUtils, SyncObjs, Tlhelp32, Hooks;


type

TThreadManager = class
private
FThreads: TStringList;
FLock: TCriticalSection;
function FindThread(const ThreadId: DWord): Integer;
procedure FreeThreadData(ThreadData: PThreadRegisterEntry);
procedure FreeObjects(List: TList);
protected
function GetThreadIndex(const ThreadId: DWord): Integer;
function GetThreadName(const ThreadId: DWord): String;
procedure SetThreadName(const ThreadId: DWord; const Value: String);
public
constructor Create;
destructor Destroy; override;
function Count: Integer;
function AddThread: Integer; overload;
function AddThread(const ThreadId: DWord): Integer; overload;
function AddThread(const ThreadName: String): Integer; overload;
function AddThread(const ThreadId: DWord; const ThreadName: String): Integer; overload;
function RemoveThread: Integer; overload;
function RemoveThread(const ThreadName: String): Integer; overload;
function RemoveThread(const ThreadId: DWord): Integer; overload;
property Threads[const ThreadId: DWord]: Integer read GetThreadIndex; default;
property ThreadName[const ThreadId: DWord]: String read GetThreadName write SetThreadName;
end;

...
var
AttachedThreads: TThreadManager = nil;
ProcessId: DWord = 0;
hProcessModule: THandle = 0;
ProcessMainThreadId: DWord = 0;


...

implementation

var

//получение Id основного кодового потока заданного процесса

function GetProcessMainThreadId(AProcessId: DWord): DWord;
var
hSnapShot: THandle;
ThreadInfo: TThreadEntry32;
Found: Boolean;
begin
Result := 0;
hSnapShot := CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
Win32Check(hSnapShot <> INVALID_HANDLE_VALUE);
try
ThreadInfo.dwSize := SizeOf(TThreadEntry32);
Found := Thread32First(hSnapShot, ThreadInfo);
while Found do
if ThreadInfo.th32OwnerProcessID = ProcessId then
begin
Result := ThreadInfo.th32ThreadID;
Break;
end
else
begin
ThreadInfo.dwSize := SizeOf(TThreadEntry32);
Found := Thread32Next(hSnapShot, ThreadInfo);
end;
finally
CloseHandle(hSnapShot);
end;
end;

procedure DLLProcHandler(Reason: Integer);
var
CurrentThreadId: DWord;
begin
//Id тек.код.потока тек.процесса
CurrentThreadId := GetCurrentThreadId;
case Reason of
DLL_PROCESS_ATTACH:
begin
//Id тек.процесса
ProcessId := GetCurrentProcessId;
//Id осн.код.потока тек.процесса
ProcessMainThreadId := GetProcessMainThreadId(ProcessId);
//эмуляция нотификации DLL_THREAD_ATTACH,
//если тек.код.поток - не осн.код.поток тек.процесса
if ProcessMainThreadId <> CurrentThreadId then
//безусловная регистрация любого код.потока тек.процесса, вызвавшего LoadLibrary()
DLLProcHandler(DLL_THREAD_ATTACH);
...
// здесь ты стартуешь свой поток
// или что-то еще делаешь - библиотека только что загружена
// в АП тек.процесса
...
end;
DLL_PROCESS_DETACH:
begin
// здесь уничтожаешь свой поток
// или что-то еще делаешь - библиотека будет выгружена
// из АП тек.процесса сразу же за завершением обработки
// тек.нотификации
...

//эмуляция нотификации DLL_THREAD_DETACH,
//если тек.код.поток - не осн.код.поток тек.процесса
if ProcessMainThreadId <> CurrentThreadId then
DLLProcHandler(DLL_THREAD_DETACH);
end;
DLL_THREAD_ATTACH:
begin
//регистрация кодовых потоков тек.процесса, вызвавших LoadLibrary()
AttachedThreads.AddThread(CurrentThreadId);
end;
DLL_THREAD_DETACH:
begin
//снятие с регистрации кодовых потоков тек.процесса, вызвавших FreeLibrary()
AttachedThreads.RemoveThread(CurrentThreadId);
end;
end;
end;


function CurrentThreadId: THandle;
begin
Result:= GetCurrentThreadId;
end;

{ TThreadManager }


initialization

//хэндл модуля хост-процесса
hProcessModule := GetModuleHandle(nil);

//ловушка для DllEntryPoint
DLLProc:= @DLLProcHandler;

//создание объекта-регистратора
AttachedThreads := TThreadManager.Create;

try
//эмуляция нотификации DLL_PROCESS_ATTACH
//ибо она происходит далеко не всегда и, если происходит, то до установки ловушки
DLLProcHandler(DLL_PROCESS_ATTACH);
except
//аварийное уничтожение объекта-регистратора
AttachedThreads.Free;
raise;
end;


finalization
//штатное уничтожение объекта-регистратора
AttachedThreads.Free;

end.



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


 
Ученик ©   (2002-08-22 13:22) [14]

>QymL (22.08.02 13:10)
Поставить точку останова (Breakpoint, F5) в процедуре DLLEntryPoint



 
SEM ©   (2002-08-22 13:32) [15]

Первоначальный пример заработал при замене ExitThread на
TerminateThread. Соответственно, на старом месте TerminateThread
необходимо убрать.


 
QymL   (2002-08-22 14:57) [16]


> Ученик
Ставил, DLL_PROCESS_ATTACH вызывался, а DLL_PROCESS_DETACH нет.

> SEM & Digitman
Попробую, спасибо


 
paul_shmakov ©   (2002-08-22 15:52) [17]

2 QymL:
проверяй почту - я послал код. все вызывается, и DLL_PROCESS_DETACH в том числе.


 
QymL   (2002-08-22 16:30) [18]


> paul_shmakov

Спаасибо за код. Все работает, но не выполняется MessageBox(0,"Dll: DLL_PROCESS_DETACH wait succeed","",0), я не вижу MessageBox"ов. Почему?


 
QymL   (2002-08-24 14:33) [19]

Может кто уже сталкивался и есть решение этой проблемы?

> SEM

В какой ОС?


 
SEM ©   (2002-08-26 11:27) [20]

win2000Prof


 
QymL   (2002-08-26 13:07) [21]


> SEM © (22.08.02 13:32)
> Первоначальный пример заработал при замене ExitThread на
> TerminateThread. Соответственно, на старом месте TerminateThread
> необходимо убрать.

Первоначальный - это мой ?


 
SEM ©   (2002-08-26 13:49) [22]

Ну да.


 
SEM ©   (2002-08-26 14:00) [23]

При желании, могу кинуть исходники твоего кода :)
Кстати, там я вывожу messagebox на DLL_PROCESS_DETACH.


 
QymL   (2002-08-26 14:04) [24]


> SEM

Кинь мне на мыло или в форум


 
SEM ©   (2002-08-26 14:18) [25]

Кинул.


 
QymL   (2002-08-26 15:03) [26]


> SEM © (26.08.02 14:18)

Project1.exe просто вылетает и все


 
SEM ©   (2002-08-26 15:49) [27]

1.Вылетает сразу после нажатия Button1?
2.Какая ОС?


 
paul_shmakov ©   (2002-08-26 15:51) [28]

все дело в том, что QymL использует Delphi 6 Enterprise. я только что обнаружил баг в его system.pas (размер файла 491801), который и приводит к тому, что DllProc не вызывается.


procedure _StartLib
//...
{ Call any DllProc }

PUSH ECX
MOV ECX,[ESP+4] // Вот здесь должно быть [ESP+8]
TEST ECX,ECX
JE @@noDllProc
MOV EAX,[EBP+12]
MOV EDX,[EBP+16]
CALL ECX
@@noDllProc:
//...


похоже, что сохранение регистра ECX они добавили позже, но совсем забыли, что нужно поправить и смещение параметра DllProc, изначально лежащего в [ESP+4], на [ESP+8].

Так что, если у вас еще не исправлено (может какой service pack это дело исправляет), то правьте руками.

2 QymL: я послал по почте версию вашей dll, в которой эторт байт исправлен - все работает.



 
SEM ©   (2002-08-26 15:56) [29]

Может быть. У меня Delphi5.


 
SEM ©   (2002-08-26 15:58) [30]

А насчет вылетания Project.exe, файл c:\aaa\bbb\test.txt создан?
Я писал это в письме.


 
paul_shmakov ©   (2002-08-26 16:15) [31]

кстати, к вопросу о том, почему глюк раньше не обнаружили, или почему ничего не грохалось? все просто.

пока один из программистов borland не добавил команду PUSH ECX, стек выглядел следующем образом:

[ESP] - адрес возврата из процедуры _StartLib
[ESP+4] - значение переменной DllProc

потом добавили команду PUSH ECX. теперь [ECX+4] указавает туда, куда раньше указывал [ECX], т.е. на адрес возврата из процедуры _StartLib.
т.е. вызываться теперь из-за ошибки будет не наша DllProc, а процедура по "адресу возврата из _StartLib".

теперь посмотрим, а куда же указывает этот адрес возврата.
_StartLib вызывается из процедуры _InitLib (sysinit.pas).

procedure _InitLib;
asm
// ......
PUSH DllProc
MOV ECX,offset TlsProc
CALL _StartLib
end;

или на ассемблере:

// ......
PUSH DllProc
MOV ECX,offset TlsProc
CALL _StartLib
RET // !!!!!!!!!!!

строчка, отмеченная табуном восклицательных знаков - это как раз и есть тот самый адрес возврата из процедуры _StartLib.

т.е. вместо нашей DllProc вызываться будет процедура, состоящая из одной команды RET.

вот так, по счастливой случайности все работало и совсем не глючило :)


 
paul_shmakov ©   (2002-08-26 16:19) [32]

> вот так, по счастливой случайности все работало и совсем не глючило :)
точнее: ничего не работало и совсем не глючило ;)


 
Romkin ©   (2002-08-26 16:30) [33]

{ Call any DllProc }

PUSH ECX
MOV ECX,[ESP+8]
TEST ECX,ECX
JE @@noDllProc
MOV EAX,[EBP+12]
MOV EDX,[EBP+16]
CALL ECX

Update Pack 2


 
paul_shmakov ©   (2002-08-26 16:53) [34]

2 Romkin:
" Update Pack 2"

вот и мораль - ставьте последние service packs.


 
Digitman ©   (2002-08-26 16:56) [35]

Я так понял - речь идет искл-но о D6 ?
В D5 все работает вроде бы как положено


 
paul_shmakov ©   (2002-08-26 18:03) [36]

да, только delphi 6, да и без второго сервис пака к тому же.


 
Romkin ©   (2002-08-26 18:16) [37]

Мораль здесь - прежде чем переходить на новую версию ПО, дождитесь и установите сервис пак (лучше второй :-)))
или хотя бы смотрите bug report


 
QymL   (2002-08-26 19:13) [38]

Спасибо всем, кто принимал обсуждение данного вопроса.



Страницы: 1 вся ветка

Текущий архив: 2002.10.31;
Скачать: CL | DM;

Наверх




Память: 0.57 MB
Время: 0.021 c
1-101271
Vladislav
2002-10-22 14:22
2002.10.31
ShellExecute


8-101392
Alexfdfdfd
2002-06-27 01:30
2002.10.31
Народ , чё такое данные в формате dib


14-101514
TTCustomDelphiMaster
2002-10-11 21:17
2002.10.31
К Белорусам


1-101208
Юра
2002-10-21 16:06
2002.10.31
Свой MessageDlg


1-101199
neodiX
2002-10-21 14:04
2002.10.31
Копирование десктопа. Непростое, а золотое.