est-il possible de connecter la terminaison de fil sur Windows? Je voudrais être informé si un fil à l'intérieur du processus (non intéressé par d'autres processus et de leurs threads) s'est terminé (normalement ou - plus important - avec force). P>
Alternativement, l'accrochage dans la création de fil ferait également. P>
Justification: J'ai une bibliothèque qui gère des informations sur la base de thread (y penser comme un cache par thread à l'échelle du processus pour certaines informations). Lorsqu'un thread est terminé, je dois supprimer toutes les informations spécifiques à thread du cache. [Les associations de cache sont implémentées à l'aide d'un identifiant de thread qui peut être réutilisé pour les futurs threads.] P>
Il n'y a pas de problème avec l'ordre d'exécution "normal" car l'utilisateur de la bibliothèque détachera le fil actuel de la bibliothèque qui effacera l'état. Les problèmes commencent à apparaître si quelqu'un tue le fil de la ressource mise en cache. p>
9 Réponses :
Vous pouvez utiliser quelque chose comme détours pour accrocher au niveau de l'API de Win32 apis comme Je ne vois pas pourquoi vous devez faire cela, cependant. On dirait que vous avez besoin d'effacer le cache associé du fil lorsque le thread meurt de sorte que vous pouvez réutiliser cette fente si un autre thread avec le même identifiant vient. Est-ce correct? P>
Si tel est le cas, ne pourriez-vous pas simplement effacer l'association de cache dans L'autre alternative qui pourrait fonctionner est thread-local Stockage (TLS). Vous pouvez utiliser Win32 API comme TerminaTéthread code> a>. p>
dllmain code> lorsque vous obtenez le dll_thread_attach code> événement? C'est essentiellement votre nouvelle notification de fil. À ce stade, vous savez que vous avez un nouveau thread, alors n'est-il pas sûr d'effacer le cache associé existant? P>
tlsalloc code > / tlssetvalue code > pour stocker des informations spécifiques à thread. Vous pouvez également définir une variable avec __ DeclSpec (fil ) code> pour que le compilateur gère le TLS pour vous. De cette façon, chaque fil maintient son propre cache. Le code reste le même pour chaque thread, mais les accès des données sont par rapport au fil. P>
Dll_thread_attach fonctionnerait si la bibliothèque en question a été mise en œuvre comme une DLL, mais ce n'est pas le cas. La terminaison d'accrochage est une possibilité, convenue.
Chris 'Mention de dll_thread_attach m'a donné une idée ... p>
Fondamentalement, l'association de cache avec un identifiant de thread est une mauvaise chose. Je dois retravailler ma bibliothèque afin qu'un thread établisse initialement une sorte de manipulation puis gérera ensuite des associations à l'aide de cette poignée. p>
boost fournit Boost :: this_thread :: at_ththread_exit () code> qui vous permet de fournir un code arbitraire à exécuter lorsque le fil actuel sort. Si vous appelez ceci sur chaque thread, alors quand il quitte normalement le code sera exécuté. Si un thread est terminé de force avec TerminaTéthread code>, plus aucun code n'est exécuté sur ce thread, de sorte que les fonctions at_thread_exit code> ne sont pas appelées. Le seul moyen de gérer de tels cas serait de crochet TerminaTéthread Code>, bien que cela ne gère pas nécessairement le cas qu'un autre processus termine vos threads. P>
Le seul moyen de de manière fiable forte> Faites ceci est dans une DLL qui croche dll_thread_attach et dll_thread_detach. Voir la discussion précédente ici . P>
Le meilleur moyen est d'appeler WaitforSingObject avec la poignée du fil (appelez OPENHREAD à l'aide de l'ID de thread pour obtenir la poignée). P>
Très intéressant en effet. Un tel événement sera-t-il un incendie sur la terminaison de fil ou dans d'autres situations également (ce qui pourrait être)?
Je suppose que si vous voulez vraiment assez mal, vous pouvez utiliser l'API de débogage (par exemple, Je ne peux pas dire que c'est exactement un moyen simple ou simple de le faire, mais si vous ne pouvez pas venir avec quoi que ce soit d'autre, c'est probablement mieux que rien. P> WaitFordebugevent code>, ContinueDeBuVENT code>) ,. Vous obtiendrez une sortie_thread_debug_event lorsqu'un fil est sorti. P>
Si votre programme est dans une DLL, vous pouvez configurer pour gérer la méthode DLLMAIN. Ceci est appelé lorsqu'un thread ou un processus commence / se termine / se termine.
Par exemple, P>
library MyDLL;
uses
SysUtils, Windows;
procedure DllMain(reason: integer) ;
var
dyingThreadId: Cardinal;
begin
case reason of
DLL_THREAD_DETACH:
begin
dyingThreadId := GetCurrentThreadId();
// handle thread exit with thread id
end;
end;
end;
begin
DllProc := @DllMain;
end.
Désolé, c'est faux. Il est seulement appelé lorsqu'un processus ou un thread qui charge la DLL i> commence ou terminé. Sauf si chaque fil de l'application charge cette DLL, elle ne sera pas appelée (et si la DLL peut être chargée par chaque thread, il existe de meilleurs moyens de se faire avertir quand ils se terminent).
Ken, tu as mal compris. Ceci est une API Windows standard - la méthode est appelée lorsque tout thread sort du processus que la DLL est chargée. La DLL est chargée dans le processus, non dans le thread, une fois que le processus charge la DLL (par n'importe quel fil), DLLMAIN est appelé à chaque fil de sortie. Voir msdn.microsoft.com/en-us/ Bibliothèque / MS682583 (v = vs.85) .aspx en particulier la partie DLL_Thread_Detatch.
@MDMA: Je ne pense pas. Veuillez vous reporter à la première phrase de votre sujet lié ( emphasis mine b>): "Un point d'entrée optionnel dans une bibliothèque de liaison dynamique (DLL). Lorsque le système démarre ou termine un processus ou un thread, il appelle la Fonction d'entrée-point pour chaque DLL chargée à l'aide du premier fil du processus B>. " Notez le "premier fil" dans cette phrase. Je ne sais pas comment cela peut être plus clair, mais je serais prêt à admettre que j'avais tort si vous avez quelque chose qui démontre le contraire ...
La documentation a changé au fil des ans. Maintenant ce n'est pas si clair. Bien que ce ne soit pas expressément écrit, cela signifie le DLL_PROCESS_ATTACH, qui est appelé à partir du premier fil du processus. Je suis d'accord avec le texte n'est pas clair, mais c'est ce que cela signifie - dll_thread_detach est appelé à chaque fil qui quitte le processus. Sinon, quelle serait la différence entre un détachement de fil et le processus? S'il vous plaît essayez-le si vous ne me croyez pas. J'ai codé de nombreuses dlls "colle" qui utilisent une fonctionnalité spécifique.
@MDMA: Je ne pense pas que cette question répond à la question de Mason (car elle ne fournit pas l'information qu'il recherche, à savoir le threadid du fil de sortie). Cependant, je retire mon vote en désaccord parce que très i> techniquement parler, dllmain fait i> est appelé comme vous l'avez dit (mais en utilisant le premier fil, comme je l'ai mentionné dans mon commentaire) . Par conséquent, très i> techniquement parler, vous n'étiez pas faux. Néanmoins, ne pensez pas que c'est une réponse à la question posée, cependant :) - ne méritait tout simplement pas un vote au bas.
@ken - J'ai répondu à la question. L'appel est effectué dans le contexte du thread de sortie afin que vous puissiez obtenir l'ID de thread. De nombreuses implémentations de variables locales de fil s'appuient sur cette fonctionnalité.
@MDMA: Je vois ça. :) Nice Travail - Vous avez inversé un vote en panne et avez fait un vote en place. Première fois que j'ai jamais fait que i>.
Merci, bien que je n'ai changé que ma réponse légèrement - le changement plus important était de votre compréhension, bien sûr, des malentendus se produisent. Je n'ai réussi que de vous amener à inverser le bowvote parce que j'ai passé le temps de défendre ma réponse. Vous dites que c'est la première fois que vous avez inversé un vote - je l'apprécie - mais peut-être que d'autres que vous avez évité, vous avez corrigé, mais ne prendrait pas le temps de défendre leurs réponses. J'espère que, à l'avenir, vous ne baisserez que lorsque vous êtes totalement sûr de quelque chose et que vous avez une expérience empirique pour la corroborer. De cette façon, alors sera une place plus heureuse pour tous.
Flamewars privé de côté, ce n'est vraiment pas une réponse parce que je ne cours pas à l'intérieur d'une DLL.
? 'Flamewars "? Je vois une discussion sur une réponse. Où est la flamme?
+1 pour la discussion constructive et pour la solution. @ Mason, prenez l'indice de MDMA: Quote: J'ai codé de nombreuses dlls "colle" qui utilisent une fonctionnalité spécifique. Code>. Écrivez une petite dll qui vous rappelle quand un fil sort.
program Project7;
{$APPTYPE CONSOLE}
uses
Windows,
Classes;
{==============================================================================}
function IsWin9x: Boolean;
asm
MOV EAX, FS:[030H]
TEST EAX, EAX
SETS AL
end;
{------------------------------------------------------------------------------}
function CalcJump(Src, Dest: DWORD): DWORD;
begin
if (Dest < Src) then begin
Result := Src - Dest;
Result := $FFFFFFFF - Result;
Result := Result - 4;
end else begin
Result := Dest - Src;
Result := Result - 5;
end;
end;
{------------------------------------------------------------------------------}
function OpCodeLength(Address: DWORD): DWORD; cdecl; assembler;
const
O_UNIQUE = 0;
O_PREFIX = 1;
O_IMM8 = 2;
O_IMM16 = 3;
O_IMM24 = 4;
O_IMM32 = 5;
O_IMM48 = 6;
O_MODRM = 7;
O_MODRM8 = 8;
O_MODRM32 = 9;
O_EXTENDED = 10;
O_WEIRD = 11;
O_ERROR = 12;
asm
pushad
cld
xor edx, edx
mov esi, Address
mov ebp, esp
push 1097F71Ch
push 0F71C6780h
push 17389718h
push 101CB718h
push 17302C17h
push 18173017h
push 0F715F547h
push 4C103748h
push 272CE7F7h
push 0F7AC6087h
push 1C121C52h
push 7C10871Ch
push 201C701Ch
push 4767602Bh
push 20211011h
push 40121625h
push 82872022h
push 47201220h
push 13101419h
push 18271013h
push 28858260h
push 15124045h
push 5016A0C7h
push 28191812h
push 0F2401812h
push 19154127h
push 50F0F011h
mov ecx, 15124710h
push ecx
push 11151247h
push 10111512h
push 47101115h
mov eax, 12472015h
push eax
push eax
push 12471A10h
add cl, 10h
push ecx
sub cl, 20h
push ecx
xor ecx, ecx
dec ecx
@@ps:
inc ecx
mov edi, esp
@@go:
lodsb
mov bh, al
@@ft:
mov ah, [edi]
inc edi
shr ah, 4
sub al, ah
jnc @@ft
mov al, [edi-1]
and al, 0Fh
cmp al, O_ERROR
jnz @@i7
pop edx
not edx
@@i7:
inc edx
cmp al, O_UNIQUE
jz @@t_exit
cmp al, O_PREFIX
jz @@ps
add edi, 51h
cmp al, O_EXTENDED
jz @@go
mov edi, [ebp+((1+8)*4)+4]
@@i6:
inc edx
cmp al, O_IMM8
jz @@t_exit
cmp al, O_MODRM
jz @@t_modrm
cmp al, O_WEIRD
jz @@t_weird
@@i5:
inc edx
cmp al, O_IMM16
jz @@t_exit
cmp al, O_MODRM8
jz @@t_modrm
@@i4:
inc edx
cmp al, O_IMM24
jz @@t_exit
@@i3:
inc edx
@@i2:
inc edx
pushad
mov al, 66h
repnz scasb
popad
jnz @@c32
@@d2:
dec edx
dec edx
@@c32:
cmp al, O_MODRM32
jz @@t_modrm
sub al, O_IMM32
jz @@t_imm32
@@i1:
inc edx
@@t_exit:
jmp @@ASMEnded
@@t_modrm:
lodsb
mov ah, al
shr al, 7
jb @@prmk
jz @@prm
add dl, 4
pushad
mov al, 67h
repnz scasb
popad
jnz @@prm
@@d3: sub dl, 3
dec al
@@prmk:jnz @@t_exit
inc edx
inc eax
@@prm:
and ah, 00000111b
pushad
mov al, 67h
repnz scasb
popad
jz @@prm67chk
cmp ah, 04h
jz @@prmsib
cmp ah, 05h
jnz @@t_exit
@@prm5chk:
dec al
jz @@t_exit
@@i42: add dl, 4
jmp @@t_exit
@@prm67chk:
cmp ax, 0600h
jnz @@t_exit
inc edx
jmp @@i1
@@prmsib:
cmp al, 00h
jnz @@i1
lodsb
and al, 00000111b
sub al, 05h
jnz @@i1
inc edx
jmp @@i42
@@t_weird:
test byte ptr [esi], 00111000b
jnz @@t_modrm
mov al, O_MODRM8
shr bh, 1
adc al, 0
jmp @@i5
@@t_imm32:
sub bh, 0A0h
cmp bh, 04h
jae @@d2
pushad
mov al, 67h
repnz scasb
popad
jnz @@chk66t
@@d4: dec edx
dec edx
@@chk66t:
pushad
mov al, 66h
repnz scasb
popad
jz @@i1
jnz @@d2
@@ASMEnded:
mov esp, ebp
mov [result+(9*4)], edx
popad
end;
{------------------------------------------------------------------------------}
function ApiHook(ModName, ApiName: PChar; FuncAddr, HookedApi: Pointer; var MainApi: Pointer): Boolean;
var
dwCount, Cnt, i, jmp: DWORD;
P: Pointer;
hMod, OldP, TMP: Cardinal;
begin
Result := False;
if IsWin9x then
Exit;
P := FuncAddr;
if P = nil then begin
hMod := GetModuleHandle(ModName);
if hMod = 0 then
hMod := LoadLibrary(ModName);
P := GetProcAddress(hMod, ApiName);
end;
if (P = nil) or (HookedApi = nil) then
Exit;
if not VirtualProtect(P, $40, PAGE_EXECUTE_READWRITE, @OldP) then
Exit;
if ((Byte(P^) = $68) and (DWORD(Pointer(DWORD(P) + 1)^) = DWORD(HookedApi))) then
Exit;
MainApi := VirtualAlloc(nil, $1000, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if MainApi = nil then
Exit;
Cnt := 0;
for dwCount := 0 to $3F do begin
Inc(Cnt, OpCodeLength(DWORD(P) + Cnt));
for i := 0 to Cnt - 1 do
PByte(MainApi)[i] := PByte(P)[i];
if Cnt > 5 then
Break;
end;
PByte(MainApi)[Cnt] := $68;
DWORD(Pointer(DWORD(MainApi) + Cnt + 1)^) := DWORD(P) + Cnt;
PByte(MainApi)[Cnt + 5] := $C3;
PByte(MainApi)[Cnt + 6] := $99;
if (OpCodeLength(DWORD(MainApi)) = 5) and
((Byte(MainApi^) = $E8) or (Byte(MainApi^) = $E9)) then
begin
jmp := DWORD(P) + DWORD(Pointer(DWORD(MainApi) + 1)^) + 5;
DWORD(Pointer(DWORD(MainApi) + 1)^) := CalcJump(DWORD(MainApi), jmp);
end;
PByte(P)[0] := $68;
DWORD(Pointer(DWORD(P) + 1)^) := DWORD(HookedApi);
PByte(P)[5] := $C3;
VirtualProtect(P, $40, OldP, @TMP);
Result := True;
end;
{------------------------------------------------------------------------------}
function ApiUnHook(ModName, ApiName: PChar; FuncAddr, HookedApi: Pointer; var MainApi: Pointer): Boolean;
var
dwCount, Cnt, i, jmp: DWORD;
P: Pointer;
hMod, OldP, TMP: Cardinal;
begin
Result := False;
if IsWin9x then
Exit;
P := FuncAddr;
if P = nil then begin
hMod := GetModuleHandle(ModName);
P := GetProcAddress(hMod, ApiName);
end;
if (P = nil) or (MainApi = nil) or (HookedApi = nil) then
Exit;
if not VirtualProtect(P, $40, PAGE_EXECUTE_READWRITE, @OldP) then
Exit;
if ((Byte(P^) <> $68) or (DWORD(Pointer(DWORD(P) + 1)^) <> DWORD(HookedApi))) then
Exit;
Cnt := 0;
for dwCount := 0 to $3F do begin
Inc(Cnt, OpCodeLength(DWORD(MainApi) + Cnt));
if (Byte(Pointer(DWORD(MainApi) + Cnt)^) = $C3) and
(Byte(Pointer(DWORD(MainApi) + Cnt + 1)^) = $99) then
Break;
for i := 0 to Cnt - 1 do
PByte(P)[i] := PByte(MainApi)[i];
end;
if (OpCodeLength(DWORD(P)) = 5) and ((Byte(P^) = $E8) or (byte(P^) = $E9)) then begin
jmp := DWORD(MainApi) + DWORD(Pointer(DWORD(MainApi) + 1)^) + 5;
DWORD(Pointer(DWORD(P) + 1)^) := CalcJump(DWORD(P), jmp);
end;
VirtualProtect(P, $40, OldP, @TMP);
VirtualFree(MainApi, 0, MEM_RELEASE);
Result := True;
end;
{==============================================================================}
type
TLdrShutdownThread = procedure; stdcall;
var
LdrShutdownThreadNext : TLdrShutdownThread;
procedure LdrShutdownThreadCallback; stdcall;
begin
WriteLn('Thread terminating:', GetCurrentThreadId);
LdrShutdownThreadNext;
end;
begin
ApiHook('ntdll.dll', 'LdrShutdownThread', nil, @LdrShutdownThreadCallback, @LdrShutdownThreadNext);
TThread.CreateAnonymousThread(procedure begin
WriteLn('Hello from Thread');
Sleep(1000);
WriteLn('Waking up');
end).Start;
ReadLn;
ApiUnHook('ntdll.dll', 'LdrShutdownThread', nil, @LdrShutdownThreadCallback, @LdrShutdownThreadNext);
TThread.CreateAnonymousThread(procedure begin
WriteLn('Hello from Thread');
Sleep(1000);
WriteLn('Waking up');
end).Start;
ReadLn;
end.
Semble très gentil. Toute façon de le faire sans la bibliothèque exclusive?
Oui, c'est un peu plus compliqué, mais faisable. Fondamentalement, vous devez écraser le premier couple d'octets de LDRSHUTTODTODHADAD avec une instruction de saut à votre propre code, puis faites ce que vous voulez faire et enfin faire quelles que soient les instructions que vous avez écrasées et retournez dans l'original LDRSHUTTODTHADAD après l'instruction de saut. Le problème est que vous ne pouvez pas simplement couper des instructions en deux, vous devez donc démonter le code d'origine de LDRSHUTTODODADAD pour vous assurer de copier un certain nombre d'instructions complètes. Il commence la même chose sur toutes les versions du système d'exploitation que cela devient plus facile ...
Une recherche rapide trouve ceci: sites.google.com/site/delphibasics/home/ Delphibasicssnippets / ... Après un coup d'œil rapide au code, cela va faire plus que ce dont vous avez besoin (il est écrit de pouvoir injecter des API de DLL et de crochet dans d'autres processus) afin que vous puissiez pouvoir extraire le quantité minimale de code pour celui-ci que vous devez simplement accrocher l'API dans votre propre processus.
Il semble que Jeff Atwood lui-même a fusionné la question de Mason avec Gabr sur. Une étape que je dois dire que je suis fortement en désaccord parce que les deux questions ne sont vraiment pas identiques du tout.
@Thorstenengler est-il possible de gérer rtluserthreadstart, baséhreadinitthunk code>? Pour commencer, initialisation?
Vous pouvez utiliser le win32_threadstoptrace code> événement WMI Pour détecter la résiliation de tout thread dans le système.
Pour commencer à surveiller cet événement, vous devez écrire une phrase chèque Cet exemple P> maintenant Pour utiliser ce fil dans votre application, vous devez l'appeler de cette manière p> et dans le Fonction de rappel p> wql code> comme ceci p>
+1 pour encore une autre réponse WMI de Rruz. WMI est-il une solution raisonnable dans ce cas? Les frais généraux ne sont-ils pas trop grains pour un travail aussi bas? Remarque: Je ne vais passer que par le nom, l'instrumentation de gestion de Windows, et elle ne semble pas être utilisée comme utilisée pour libérer des variables de fil, par exemple.
@Cosmin, je sais qui n'est pas très courant d'utilisation un objet COM avec des tâches de faible niveau, telles que la surveillance des threads et du processus. Dans une autre main, le WMI code> est principalement connu en récupérant des informations système telles que le matériel installé et réfléchit comme ça, mais ce n'est qu'une petite partie de son pouvoir. Personnellement, j'ai écrit de nombreuses applications pour surveiller les systèmes distants avec succès à l'aide de l'OMM. Avec le WMI code> Vous pouvez détecter le début des threads, le processus, les pannes matérielles, le nouveau matériel connecté ou déconnecté, etc. En résumé comme réponse à votre question est une solution raisonnable dans ce cas? code>, oui c'est.