システムフックで使用したDLLを開放するには?


よしなり  2004-03-22 06:34:57  No: 53346

この掲示板で助言を頂き、VBからDLLを使ってシステムフックを行い、
他のアプリの状態を監視する事が出来ました。
私のプログラムがマズいのか、VBのEXEを閉じてDLLを削除しようとすると、

〜 を削除できません。アクセスできません。送り側のファイルが使用中の可能性があります。

と言われてしまいます。友人はFreeLibraryを使えば?と言うのですが、どうもウマクいきません。それともシステムフックの場合開放は無理なのでしょうか?OSはWIN2000です。

'--------------------------------------------------
VB側
'--------------------------------------------------
Private Sub Form_Load()

Call sethook

End Sub
Private Sub Form_Unload(Cancel As Integer)

Call freehook

End Sub

'--------------------------------------------------
DLL側
'--------------------------------------------------
#define  STRICT
#include <windows.h>
#define  DllExport __declspec( dllexport )
#define  CLASS_NAME "ThunderFormDC"//開発ツール
//#define  CLASS_NAME "ThunderRT6FormDC"//実行ファイル
#define  TITLE_NAME "777"
#define  MESSAGE    "777"

DllExport void CALLBACK sethook(void);
DllExport void CALLBACK freehook(void);

HWND           g_lng_aaa;
COPYDATASTRUCT g_udt_CDS;

//共有領域
#pragma data_seg(".sharedata")
/*フックプロシージャハンドル
共有領域のデータは初期化しないとうまく確保されない*/
HHOOK hHookWnd=0;
#pragma data_seg()

HINSTANCE hdll;

//BCでは『DllEntryPoint』
BOOL WINAPI DllMain (HINSTANCE hInstance, DWORD reason, LPVOID lpReserved)
{
g_lng_aaa = FindWindow(CLASS_NAME,TITLE_NAME);

(void)lpReserved;

    if(reason==DLL_PROCESS_ATTACH)
    {
     hdll=hInstance;//DLLハンドル保存
    }

return  TRUE;
}

LRESULT CALLBACK  CallWndProc(int nCode,WPARAM wParam,LPARAM lParam)
{
    
    //メッセージを処理する必要があるか?
    if(nCode == HC_ACTION){

        if(((CWPSTRUCT *)lParam) -> message == WM_CREATE)
        {
        char str_aaa[512];
        char str_bbb[256];

        GetClassName(((CWPSTRUCT *)lParam) ->  hwnd,str_aaa,256);
        GetWindowText(((CWPSTRUCT *)lParam) ->  hwnd,str_bbb,256);
        strcat(str_aaa,"■");
        strcat(str_aaa,str_bbb);
        g_udt_CDS.dwData = 0;
        g_udt_CDS.lpData = (void*)str_aaa;
        g_udt_CDS.cbData = lstrlen(str_aaa) + 1;//終端のNULLも送信
        SendMessage(g_lng_aaa,WM_COPYDATA,(WPARAM)NULL,(LPARAM)&g_udt_CDS);
        }
    }

return CallNextHookEx(hHookWnd, nCode, wParam, lParam);//次のフック
}

//フック設定
void CALLBACK sethook(void)
{
hHookWnd=SetWindowsHookEx(WH_CALLWNDPROC,CallWndProc, hdll, 0);
}

//フック解除
void CALLBACK freehook(void)
{
UnhookWindowsHookEx(hHookWnd);
}


シャノン  2004-03-23 04:17:11  No: 53347

似たようなアプリを作って実験してるところですが…しばらくVB触ってなかかったら、メッセージフックのやり方も忘れてました。
しばしお待ちを…


シャノン  2004-03-23 05:01:49  No: 53348

ダメだわかんねぇ…
DLL が見つからないってエラーが出たり関数が見つからないってエラーが出たり VB が何のエラーメッセージも吐かずに IDE ごと落ちたり…

でも再現プログラムが動かないのをここで質問するのもアレだしなぁ…どうしよう


kara  2004-03-23 05:16:47  No: 53349

よしなりさん
とりあえずUnhookWindowsHookExの戻りを確認してみては?

int ret = UnhookWindowsHookEx(hHookWnd);
if (ret != 0) MessageBox(0,"フック解除エラー","エラー",MB_OK);

といった感じで

シャノンさん
> VB が何のエラーメッセージも吐かずに IDE ごと落ちたり
フック系のものをテストしてるときによくなりますね(結局はどこかが間違ってるってことが多いですが)
自分なら質問したいところですが、回答しようとして質問ってのはせつないですよねぇ・・・


kara  2004-03-23 05:28:22  No: 53350

えーと
ちょっと質問です
> 私のプログラムがマズいのか、VBのEXEを閉じてDLLを削除しようとすると、
ここでいうVBのEXEを閉じるはexeを実行して閉じるボタンを押す
ということですよね?

IDEで実行停止ボタンを押したりしてないですよね?
IDEで実行停止ボタンを押して終了していた場合、Form_Unloadに行かないで終了してしまうので
フックの解除をせずに終了してしまいます


よしなり  2004-03-23 07:25:38  No: 53351

メモ帳の監視を行うサンプルです。

'--------------------------------------------------
VBのフォーム
'--------------------------------------------------
Private Sub Form_Load()

'多重チェック(多重すると悲惨です)
If App.PrevInstance = True Then
MsgBox "多重起動です。", MB_TOPMOST, "error"
Unload Form1
Exit Sub
End If

Call g_SetHook(Form1.hWnd) '自分自身フック開始
Call SetHook(Form1.hWnd)   'グローバルフック開始

End Sub
Private Sub Form_Unload(Cancel As Integer)

Call g_UnHook '自分自身フック終了
Call FreeHook 'グローバルフック終了

End Sub
'--------------------------------------------------
VBのモジュール
'--------------------------------------------------
Option Explicit
'--------------------------------------------------
'自作DLL使用
'--------------------------------------------------
Public Declare Function SetHook Lib "hook_test.dll" (ByVal hWnd As Long) As Long
Public Declare Function FreeHook Lib "hook_test.dll" () As Long

'--------------------------------------------------
'自分自身フック用
'--------------------------------------------------
Public Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" ( _
ByVal hWnd As Long, _
ByVal nIndex As Long, _
ByVal dwNewLong As Long) As Long

Public Declare Function CallWindowProc Lib "user32" Alias "CallWindowProcA" ( _
ByVal lpPrevWndFunc As Long, _
ByVal hWnd As Long, _
ByVal Msg As Long, _
ByVal wParam As Long, _
ByVal lParam As Long) As Long

Public Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" ( _
Destination As Any, _
Source As Any, _
ByVal Length As Long)

Public Const WM_COPYDATA = &H4A
Public Const GWL_WNDPROC = (-4)

Public Type COPYDATASTRUCT
lng_dwData As Long '謎
lng_cbData As Long '文字列の長さ
lng_lpData As Long '文字列
End Type

Private m_lng_WH As Long
Private m_lng_Hook As Long
Public Sub g_SetHook(ByVal lng_WH As Long)

'フック開始
m_lng_Hook = SetWindowLong(lng_WH, GWL_WNDPROC, AddressOf g_HookProcedure)
m_lng_WH = lng_WH
    
End Sub
Public Sub g_UnHook()

'フック解除
If m_lng_Hook <> 0 Then
Call SetWindowLong(m_lng_WH, GWL_WNDPROC, m_lng_Hook)
End If

m_lng_Hook = 0

End Sub
Public Function g_HookProcedure( _
ByVal lng_WH As Long, _
ByVal lng_Message As Long, _
ByVal lng_WParameter As Long, _
ByVal lng_LParameter As Long) As Long

If lng_Message = WM_COPYDATA Then
Dim str_Data As String
Dim udt_CDS  As COPYDATASTRUCT

Call CopyMemory(udt_CDS, ByVal lng_LParameter, Len(udt_CDS))
str_Data = String(udt_CDS.lng_cbData, 0)
Call CopyMemory(ByVal str_Data, ByVal udt_CDS.lng_lpData, udt_CDS.lng_cbData)
str_Data = Left(str_Data, InStr(str_Data, vbNullChar) - 1)
    
Debug.Print "受信!"; str_Data
End If

g_HookProcedure = _
CallWindowProc(m_lng_Hook, lng_WH, lng_Message, lng_WParameter, lng_LParameter)

End Function

長くなるのでDLLと分けて書きます。


シャノン  2004-03-23 07:27:01  No: 53352

前回も書きましたが、ひとつ確かなことは、明示的に LoadLibrary(あるいは Ex)していないならば、FreeLibrary すべきではない、ということです。


よしなり  2004-03-23 07:29:23  No: 53353

'--------------------------------------------------
hook_test.cpp
'--------------------------------------------------
#include <windows.h>
#include <stdio.h>
//修飾子『WINAPI』『CALLBACK』は『__stdcall』で定義されている

void GetWindowData(LPARAM lParam);
__declspec(dllexport) void CALLBACK SetHook(HWND hwnd_EXEWH);
__declspec(dllexport) void CALLBACK FreeHook(void);

//共有領域のデータは初期化しないとうまく確保されない
#pragma data_seg("share_data")
HHOOK g_hHookWnd            = 0;
HWND g_hwnd_EXEWH           = 0;
COPYDATASTRUCT g_struct_CDS = {0,0,0};

char char_ClassName[256] = {0};   
char char_TitleName[256] = {0};
char char_WH[256]        = {0};
#pragma data_seg()

HINSTANCE hdll;

//BCでは『DllEntryPoint』
BOOL WINAPI DllMain (HINSTANCE hInstance,DWORD reason,LPVOID lpReserved)
{
//『INSTANCE hinstDLL』 DLLモジュールハンドル
//『WORD fdwReason』    関数を呼び出す理由
//『LPVOID lpvReserved』予約済み
(void)lpReserved;

  if(reason == DLL_PROCESS_ATTACH)
  {
    hdll = hInstance;//DLLモジュールハンドル保存
    }

return TRUE;
}

LRESULT CALLBACK CallWndProc(int nCode,WPARAM wParam,LPARAM lParam)
{
char char_HitFlag         = 0;
char char_SendString[512] = {0};

  if(nCode == HC_ACTION)//メッセージを処理する必要があるか?
  {

    if(GetParent(((CWPSTRUCT *)lParam) -> hwnd) == NULL)//親ウインドウなら
    {
    
      if(((CWPSTRUCT *)lParam) -> message == WM_CREATE)//ウインドウ生成か?
      {
      GetWindowData(lParam);

        if(NULL != strstr(char_ClassName,"Notepad"))
        {

          //if(NULL != strstr(char_TitleName,"メモ帳"))
          {
          strcpy(char_SendString,"生成メモ帳");
          char_HitFlag = 1;
          }

        }      

      }

      if(((CWPSTRUCT *)lParam) -> message == WM_DESTROY)//ウインドウ破棄か?
      {
      GetWindowData(lParam);

        if(NULL != strstr(char_ClassName,"Notepad"))
        {

          if(NULL != strstr(char_TitleName,"メモ帳"))
          {
          strcpy(char_SendString,"破棄メモ帳");
          char_HitFlag = 1;
          }

        }
        
      }
  
    }//if(GetParent(((CWPSTRUCT *)lParam) -> hwnd) == NULL)

  }//if(nCode == HC_ACTION)

  if(char_HitFlag == 1)
  {
  strcat(char_SendString,char_WH);        
  g_struct_CDS.dwData = 0;
  g_struct_CDS.lpData = (void*)char_SendString;
  g_struct_CDS.cbData = lstrlen(char_SendString) + 1;//終端のNULLも送信
  SendMessage(g_hwnd_EXEWH,WM_COPYDATA,(WPARAM)NULL,(LPARAM)&g_struct_CDS);
  }

return CallNextHookEx(g_hHookWnd,nCode,wParam,lParam);//次のフック
}

void GetWindowData(LPARAM lParam)
{
sprintf(char_WH,"%d",(((CWPSTRUCT *)lParam) -> hwnd));
GetClassName(((CWPSTRUCT *)lParam) ->  hwnd,char_ClassName,256);
GetWindowText(((CWPSTRUCT *)lParam) ->  hwnd,char_TitleName,256);
}

__declspec(dllexport) void CALLBACK SetHook(HWND hwnd_EXEWH)
{
g_hwnd_EXEWH = hwnd_EXEWH;
g_hHookWnd  = SetWindowsHookEx(WH_CALLWNDPROC,CallWndProc,hdll,0);//フック設定
}

__declspec(dllexport) void CALLBACK FreeHook(void)
{
UnhookWindowsHookEx(g_hHookWnd);//フック解除
}
'--------------------------------------------------
hook_test.def
'--------------------------------------------------
LIBRARY hook_test

EXPORTS
SetHook
FreeHook

SECTIONS
share_data READ WRITE SHARED

UnhookWindowsHookExの戻り値は今から確認してみます。皆さんレスありがとうございます。


よしなり  2004-03-23 07:59:23  No: 53354

シャノンさんのカキコに気付かずサンドイッチしてしまいました。
結果はフック解除エラーが出てしまいます。困った。


kara  2004-03-23 19:12:57  No: 53355

よしなりさん
まじですみません
2004/03/22(月) 20:16:47に発言した
> int ret = UnhookWindowsHookEx(hHookWnd);
> if (ret != 0) MessageBox(0,"フック解除エラー","エラー",MB_OK);
は間違いでした

MSDNには
---------------------------------------------------------------
戻り値

関数が成功すると、0以外の値が返ります。

関数が失敗すると、0が返ります。拡張エラー情報を取得するには、GetLastError関数を使います。
---------------------------------------------------------------
と書いてあるので
int ret = UnhookWindowsHookEx(hHookWnd);
if (ret == 0) MessageBox(0,"フック解除エラー","エラー",MB_OK);
としなければいけませんでした

きちんと確認もせずに適当なことを言ってしまいました
お詫びして訂正します


よしなり  2004-03-24 09:37:48  No: 53356

いえいえ全然かまいませんよ。
ところで、上のサンプルからVBのEXEを作成して、起動後にメモ帳を1個立ち上げます。そしてEXEを閉じます。するとDLLは問題無く削除できるのですが、メモ帳が2個だと送り側のファイル使用中エラーが出ます。まさにこの現象なのです。フック解除のエラーは出ていないのですが...。


よしなり  2004-03-24 09:50:12  No: 53357

で、EXEの後にメモ帳を閉じるとDLLも削除できます。
何の問題も無いですね...。私の監視しているプログラムはそれを閉じてもDLLが使用中エラーが出てしまうんです。実は東風荘なのですが...。この結果でVBとDLL側に問題は無いと結論付けて良いのでしょうか?


kara  2004-03-24 19:11:37  No: 53358

似たようなことで悩んでいた方がいらっしゃいました
http://forums.belution.com/ja/vc/000/100/67.shtml

その中の回答で凸凹さんが
http://forums.belution.com/ja/vc/000/100/77.shtml
> フックを仕込まれた側のプロセスがフックプロシージャを
> 実行している最中に、

> > 正常にUnhookWindowsHookEx()が呼ばれていることも確認済みです。

> この関数が実行された場合、対象プロセスからの DLL アンロード
> が先延ばしにされると思います。(関数は正常終了)
と言われていますので
もしかしたら、どうしようもないことなのかもしれません(しかし釈然としない)

> この結果でVBとDLL側に問題は無いと結論付けて良いのでしょうか?
問題ないと思うのですが・・・
正直あまり自信がないです
ソースを見た限りでは問題なさそうな気もしますし・・・(自分に問題発見能力がないだけかも)

うーん、自分にはこのへんが限界のようです
後は他の方の回答を期待ですね


AUT`s  2004-03-25 22:39:11  No: 53359

かなり長いけど、使えるのかな↓
http://www.microsoft.com/japan/msdn/library/default.asp?url=/japan/msdn/library/ja/vccore/html/_core_run.2d.time_library_behavior.asp

一読すれば見えてくるけど、DLLは終了したからといって、すぐには開放されない!って事。
他人が使ってたらもちろんの事、デタッチプロセスでバグってたり、リソース開放待ちなんかも発生するから。
特にデタッチ時に自分自身の共有リソースを開放待ちしてた日にゃ、どんなに待ってもアンロードされない罠。

んで、多分だけど、メモ帳の2個目を起動時にhHookWndを見失ってるか、DLLの2個目を起動したプロセスがshare_dataを潰してるんじゃないかと思う今日この頃。
Sourceを見て書いてるので、間違ってたらごめんなさいです。

> この結果でVBとDLL側に問題は無いと結論付けて良いのでしょうか?
なけりゃ削除できるに。

あっ、ちなみにVB5以前のP-Codeで実行している場合は、デタッチされないらしい...正確にはDLLがアンロードされるタイミングが分からないらしい...と言う噂を、どこかで聞いた記憶が...


よしなり  2004-03-26 00:53:25  No: 53360

みなさんレスありがとうございます。私も限界っぽいです。
皆さん資料を元にボチボチ調べてみようかと思います。
あ、一応VB6のネイティブでコンパイルしております。


よしなり  2004-03-26 00:54:26  No: 53361

皆さん×  皆さんの○


よしなり  2004-03-26 01:36:53  No: 53362

DLLを確実に開放する方法が見つかりました...。
大抵開発環境では、一つのフォルダにEXEとDLLを一緒に入れていると思います。で、そのフォルダを開いた状態で、EXEを起動→他プロセス起動→EXE終了とするとDLLがロックされるようです。EXEを起動→他プロセス起動→EXE終了の後にそのフォルダを閉じて、もう一度そのフォルダを開いてDLLを消せば問題無く消せます。結果的に消せるというだけで、どうしてなのかはわかりません。


シャノン  2004-03-26 05:15:29  No: 53363

ちょっと数日、ネットに繋げないでおりました。
AlwaysUnloadDLL が手がかりかもしれません。


よしなり  2004-03-27 06:29:23  No: 53364

どうもすんません(;´Д`)


よしなり  2004-03-28 05:11:20  No: 53365

ダメポです。レジストリにキーを追加してもダメっぽいです。
これだけ皆さんに協力頂いて解決できないのだから、結構難しい問題なのかもしれません。こういう場合一旦諦めてみるのも手かと(W
皆さん有難う御座いました。また進展があれば書き込みします。


※返信する前に利用規約をご確認ください。

※Google reCAPTCHA認証からCloudflare Turnstile認証へ変更しました。






  このエントリーをはてなブックマークに追加