Главная страница
    Top.Mail.Ru    Яндекс.Метрика
Форум: "Система";
Текущий архив: 2002.10.31;
Скачать: [xml.tar.bz2];

Вниз

Не вызывается 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;
Скачать: [xml.tar.bz2];

Наверх




Память: 0.55 MB
Время: 0.01 c
3-101093
Юра
2002-10-09 12:02
2002.10.31
Картинки формата jpg в Paradox


6-101411
Gerda
2002-08-30 16:28
2002.10.31
Выбивание паролей из под звездочек на инетовских страничках


4-101574
disa
2002-09-19 12:33
2002.10.31
Управление приложениями


14-101440
MVova
2002-10-10 11:36
2002.10.31
Почему ICQ а не MSN


6-101430
MetalFan
2002-08-28 15:59
2002.10.31
TWebBrowser





Afrikaans Albanian Arabic Armenian Azerbaijani Basque Belarusian Bulgarian Catalan Chinese (Simplified) Chinese (Traditional) Croatian Czech Danish Dutch English Estonian Filipino Finnish French
Galician Georgian German Greek Haitian Creole Hebrew Hindi Hungarian Icelandic Indonesian Irish Italian Japanese Korean Latvian Lithuanian Macedonian Malay Maltese Norwegian
Persian Polish Portuguese Romanian Russian Serbian Slovak Slovenian Spanish Swahili Swedish Thai Turkish Ukrainian Urdu Vietnamese Welsh Yiddish Bengali Bosnian
Cebuano Esperanto Gujarati Hausa Hmong Igbo Javanese Kannada Khmer Lao Latin Maori Marathi Mongolian Nepali Punjabi Somali Tamil Telugu Yoruba
Zulu
Английский Французский Немецкий Итальянский Португальский Русский Испанский