2007年2月21日水曜日

DLLの作り方

1.ダイナミックリンクライブラリの作成

ダイナミック リンク ライブラリ (DLL) は、関数の共有ライブラリとして機能する実行ファイルのことです。
Windows APIもDLLとして実装されています。
DLLの作成手順はWindows標準の実行ファイルを作成する場合とそれほど変わりありません。
Visual C++の場合、新規プロジェクトとして Win32 Dynamic-Link Library を選択して
エクスポートする関数を定義してコンパイルすればDLLが出来上がります。

エクスポートするためのキーワードは__declspec(dllexport) を使いますが、
定義ファイル(.DEFファイル)を使う場合にはこのキーワードは必要ありません。
次のサンプルは定義ファイルを使わない場合のものです。

MyDll.cpp

#include

__declspec(dllexport) BOOL __stdcall MyBeep();

BOOL __stdcall MyBeep()
{
return MessageBeep(0);
}

C++の場合はエクスポート、インポートする関数の外部シンボルを装飾します。
C++の名前の装飾を取りやめるためにはプロトタイプ宣言にextern "C" 構文を使いますが
EXEとDLLを同一環境で作成する場合は装飾規則を気にする必要はないでしょう。

関数をエクスポートする場合、他の言語からも呼び出せるようにするために、
__stdcall 呼び出し規約を使うのが一般的です。


2.関数のインポート

作成されたDLL内の関数を呼ぶためにはコンパイル時に作成されたLIBファイルをリンクして
__declspec(dllimport) キーワードを使います。
DLLはパスの通ったディレクトリかEXEと同じディレクトリにあればOKです。
1.で作成された関数を呼び出す場合は次のような感じになります。

MyDllCall.cpp

#include

#pragma comment(lib, "MyDll.lib")

__declspec(dllimport) BOOL __stdcall MyBeep();

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{

UNREFERENCED_PARAMETER(hInstance);
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
UNREFERENCED_PARAMETER(nCmdShow);

MyBeep();
return 0;
}

3.定義ファイルを使って関数のエクスポート

定義ファイルを使えばエクスポート名をシンプルにできます。
__declspec(dllexport) キーワードも__declspec(dllimport) キーワードもいらなくなります。
また、関数を序数値でもエクスポートすることができます。

定義ファイル(.DEFファイル)に最低限必要な記述は次の2つです。

  • LIBRARY文
    DLL の内部名を指定します。
  • EXPORTS文
    1 つ以上の定義をエクスポートし、ほかのプログラムから利用できるようにします。
    各エクスポート関数に対して NONAME 属性を使用することもできます。
    NONAME 属性を使用すると序数のみでエクスポートするのでファイルサイズを小さくできます。

・DLL側のコード

MyDll.cpp

#include

BOOL __stdcall MyBeep();
int __stdcall MyMessageBox(LPCTSTR);

BOOL __stdcall MyBeep()
{
return MessageBeep(0);
}

int __stdcall MyMessageBox(LPCTSTR lpszMessage)
{
return MessageBox(NULL, lpszMessage, "", 0);
}
MyDll.def

LIBRARY MyDll

EXPORTS
MyBeep @1
MyMessageBox @2

・呼び出し側のコード

MyDllCall.cpp

#include

#pragma comment(lib, "MyDll.lib")

BOOL __stdcall MyBeep();
int __stdcall MyMessageBox(LPCTSTR);

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
UNREFERENCED_PARAMETER(hInstance);
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
UNREFERENCED_PARAMETER(nCmdShow);

MyMessageBox("HELLO!!");
return 0;
}

4.ロード時ダイナミックリンクと実行時ダイナミックリンク

これまで説明してきたのは暗黙的なリンク(ロード時ダイナミックリンク)でした。
暗黙的なリンクを使うと、DLLの存在やエクスポート関数のアドレスなどは
システムが解決してくれますが、DLLが存在しないのにアプリケーションを起動すると
DLLが見つからないとシステムに怒られてしまいます。
また、ロード時にリンクする全てのプロシージャの名前が事前にわかっていなければなりません。

明示的なリンク(実行時ダイナミックリンク)ではアプリケーションの実行時に明示的にDLLを明示的にロードします。
そのためアプリケーション作成時点で存在しない機能をあとから提供することなども可能です。
明示的なリンクでは暗黙的なリンクのような制約はなくインポートライブラリ(LIBファイル)も不要ですが
アプリケーション側での手間が増えるというデメリットもあります。

明示的リンクを使用するためにはDLLをロードして呼び出したいエクスポート関数のアドレスを取得します。
3.で作成したMyMessageBox()を明示的に呼び出す場合は次のコードのようになります。
__declspec(dllexport) キーワードでエクスポートした関数を呼び出す場合はエクスポートされた名前を確認してください)

MyDllCall.cpp

#include

typedef int (__stdcall *pMyFunction)(LPCTSTR);

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
HINSTANCE hLib;
pMyFunction pMyMessageBox;

UNREFERENCED_PARAMETER(hInstance);
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
UNREFERENCED_PARAMETER(nCmdShow);

hLib = LoadLibrary("MyDll.dll");
if(hLib) {
pMyMessageBox = (pMyFunction)GetProcAddress(hLib, "MyMessageBox");
if(pMyMessageBox) {
pMyMessageBox("HELLO!!");
}
FreeLibrary(hLib);
}
return 0;
}

5.Visual Basic からDLLの関数を呼び出す

__stdcall 呼び出し規約でエクスポートされた関数はVisual Basic等から呼び出すことも可能です。
Visual Basicから呼び出す場合外部プロシージャへの参照を宣言します。
宣言ではC、C++のデータ型に対応するVisual Basicのデータ型を使用しなければなりません。
Visual Basicから関数にNULLを渡したい時は、String型であればvbNullStringを、Long型の場合0を渡します。
次のコードは3.で作成したMyMessageBox()をVisual Basicから呼び出すものです。
__declspec(dllexport) キーワードでエクスポートした関数を呼び出す場合はエクスポートされた名前を確認してください)


Visual Basicのフォームにボタンを配置し名前をCommand1とします。

・名前で呼び出す場合

Form1.frm

Option Explicit

Private Declare Function MyMessageBox Lib "mydll.dll" (ByVal message As String) As Long

Private Sub Command1_Click()
Call MyMessageBox("名前でコール")
End Sub

・序数で呼び出す場合

Form1.frm

Option Explicit

Private Declare Function MyMessageBox Lib "mydll.dll" Alias "#2" (ByVal message As String) As Long

Private Sub Command1_Click()
Call MyMessageBox("序数でコール")
End Sub

6.DllMainについて

エントリポイントのDllMainのプロトタイプは以下のようになります。

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved);

DllMainではDLLの初期化処理、終了処理を行うことができます。
DllMainの2番目のパラメータで初期化/終了処理のタイミングを知ることができます。

  • DLL_PROCESS_ATTACH
    DLL が初めてプロセスのメモリ空間にマッピングされる時に渡されます。
  • DLL_PROCESS_ATTACH
    DLLがアタッチしているプロセスで新しいスレッドが作成された時に渡されます。
  • DLL_THREAD_DETACH
    実行中のスレッドが終了する時に渡されます。
  • DLL_PROCESS_DETACH
    実行中のプロセスがDLLを解放する時に渡されます。
MyDll.cpp

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
UNREFERENCED_PARAMETER(hinstDLL);
UNREFERENCED_PARAMETER(lpvReserved);

char szBuf[256];

switch(fdwReason) {
case DLL_PROCESS_ATTACH:
lstrcpy(szBuf, "process attach");
break;
case DLL_THREAD_ATTACH:
lstrcpy(szBuf, "thread attach");
break;
case DLL_THREAD_DETACH:
lstrcpy(szBuf, "thread detach");
break;
case DLL_PROCESS_DETACH:
lstrcpy(szBuf, "process detach");
break;
}
MessageBox(NULL, szBuf, "DllMain", MB_OK);
return TRUE;
}

7.DLLでメモリを共有する

定義ファイルで変数の属性を変更することで、DLLを使用するプロセス間でデータを共有させることができます。
共有させたい変数には#pragma data_segを使い、必ず初期値を与えます。

・DLL側のコード

MyDll.cpp

#include

//
// DLLを使用する全てのアプリケーション間で共有されるデータ
//
#pragma data_seg("data")
LONG count = 0;
#pragma data_seg()

//
// countの値を取得
//
LONG WINAPI GetCount()
{
return count;
}

//
// Entry Point
//
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
UNREFERENCED_PARAMETER(hinstDLL);
UNREFERENCED_PARAMETER(lpvReserved);

switch(fdwReason) {
case DLL_PROCESS_ATTACH:
//カウントアップ
InterlockedIncrement(&count);
return TRUE;
case DLL_PROCESS_DETACH:
//カウントダウン
InterlockedDecrement(&count);
break;
}
return FALSE;
}

属性はREAD WRITE SHAREDにします。

MyDll.def

SECTIONS
data READ WRITE SHARED
LIBRARY MyDll
EXPORTS
GetCount @1

・呼び出し側のコード

MyDllCall.cpp

#include

typedef LONG (WINAPI* pMyFunction)();

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
HINSTANCE hLib;
pMyFunction pGetCount;
char szBuf[10];

UNREFERENCED_PARAMETER(hInstance);
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
UNREFERENCED_PARAMETER(nCmdShow);

hLib = LoadLibrary("MyDll.dll");
if(hLib) {
pGetCount = (pMyFunction)GetProcAddress(hLib, "GetCount");
if(pGetCount) {
wsprintf(szBuf, "%d", pGetCount());
MessageBox(NULL, szBuf, "MyDllCall", MB_OK);
}
FreeLibrary(hLib);
}
return 0;
}

8.マウスイベントを監視するフックコードを書く

特定の種類のイベントを監視するためにはSetWindowsHookEx関数を使います。
SetWindowsHookEx関数のプロトタイプは以下のようになります。

HHOOK SetWindowsHookEx(int idHook, HOOKPROC lpfn, HINSTANCE hMod, DWORD dwThreadId);

idHook インストールするフックの種類
lpfn フックプロシージャのアドレス
hMod アプリケーションインスタンスのハンドル
dwThreadId フックをインストールするスレッドのID

システム内のすべてのスレッドに関連付けられているイベントを監視するためにはフックプロシージャはDLL内になければなりません。
DLL内にフックプロシージャを定義することで既存の全てのスレッドに関連付けることができます。
例えば全スレッドのマウスメッセージを監視する場合は以下のようにフックプロシージャをインストールすることができます。
(サンプルコードはカーソルの下にあるウインドウのクラス名を表示するものです)

・DLL側のコード

MyDll.cpp

#include

#pragma data_seg("MyData")
BOOL bLock = FALSE;
HHOOK hHook = NULL;
HWND hWndDisp = NULL;
#pragma data_seg()

HINSTANCE hInst;

BOOL WINAPI Install(HWND hWnd);
BOOL WINAPI Uninstall();
LRESULT CALLBACK MouseProc(int nCode, WPARAM wParam, LPARAM lParam);

//
// Entry Point
//
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
UNREFERENCED_PARAMETER(lpvReserved);

switch(fdwReason) {
case DLL_PROCESS_ATTACH:
hInst = hinstDLL;
return TRUE;
}
return FALSE;
}

//
// インストール
//
BOOL WINAPI Install(HWND hWnd)
{
BOOL bResult = FALSE;
BOOL bPrev;

bPrev = (BOOL)InterlockedExchange((LPLONG)&bLock, 1);
if(!bPrev) {
hHook = SetWindowsHookEx(WH_MOUSE, (HOOKPROC)MouseProc, hInst, 0);
if(hHook) {
hWndDisp = hWnd;
bResult = TRUE;
}
else
bLock = FALSE;
}
return bResult;
}

//
// アンインストール
//
BOOL WINAPI Uninstall()
{
BOOL bResult = FALSE;

if(bLock) {
bResult = UnhookWindowsHookEx(hHook);
if(bResult) {
hWndDisp = NULL;
hHook = NULL;
bLock = FALSE;
}
}
return bResult;
}

//
// フックプロシージャ
//
LRESULT CALLBACK MouseProc(int nCode, WPARAM wParam, LPARAM lParam)
{
if(nCode == HC_ACTION) {
char szBuffer[256];
RECT rc;
HDC hDC, hMemDC;
HBITMAP hBitmap;

HWND hWnd = WindowFromPoint(((LPMOUSEHOOKSTRUCT)lParam)->pt);
if(IsWindow(hWnd) && IsWindow(hWndDisp)) {
GetClassName(hWnd, szBuffer, 256);
GetClientRect(hWndDisp, &rc);

hDC = GetDC(hWndDisp);
hMemDC = CreateCompatibleDC(hDC);
if(hMemDC) {
hBitmap = CreateCompatibleBitmap(hDC, rc.right, rc.bottom);
if(hBitmap) {
hBitmap = (HBITMAP)SelectObject(hMemDC, hBitmap);
FillRect(hMemDC, &rc, (HBRUSH)(COLOR_WINDOW + 1));

//カーソルの下にあるウインドウクラス名を表示します
TextOut(hMemDC, 10, 10, szBuffer, lstrlen(szBuffer));
BitBlt(hDC, 0, 0, rc.right, rc.bottom, hMemDC, 0, 0, SRCCOPY);
hBitmap = (HBITMAP)SelectObject(hMemDC, hBitmap);
DeleteObject(hBitmap);
}
DeleteDC(hMemDC);
}
ReleaseDC(hWndDisp, hDC);
}
}
//次のフックプロシージャにフック情報を渡す
return CallNextHookEx(hHook, nCode, wParam, lParam);
}
MyDll.def

SECTIONS
MyData READ WRITE SHARED
LIBRARY MyDll
EXPORTS
Install @1
Uninstall @2

・呼び出し側のコード

MyApp.cpp

#include

HINSTANCE hInst;
LPCTSTR lpszAppName = "MyApp";
LPCTSTR lpszAppTitle = "MyApp";

typedef BOOL (WINAPI* pInstall)(HWND);
typedef BOOL (WINAPI* pUninstall)();

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
MSG msg;
HWND hWnd;
WNDCLASSEX wc;
HINSTANCE hLib;

pInstall inst = NULL;
pUninstall uninst = NULL;

UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);

ZeroMemory(&msg, sizeof(MSG));

//メインウィンドウクラス登録
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = 0;
wc.lpfnWndProc = (WNDPROC)WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)GetStockObject(NULL_BRUSH);
wc.lpszMenuName = NULL;
wc.lpszClassName = lpszAppName;
wc.hIconSm = NULL;
if(!RegisterClassEx(&wc))
return 0;

hInst = hInstance;

//メインウインドウ作成
hWnd = CreateWindow(lpszAppName, lpszAppTitle, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInst, NULL
);
if(!hWnd)
return 0;

hLib = LoadLibrary("mydll.dll");
if(hLib) {
inst = (pInstall)GetProcAddress(hLib, "Install");
uninst = (pUninstall)GetProcAddress(hLib, "Uninstall");
if(inst && uninst) {
if(inst(hWnd)) {
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
while(GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
uninst();
hWnd = NULL;
}
}
FreeLibrary(hLib);
}
if(hWnd)
DestroyWindow(hWnd);
return msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch(uMsg) {
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
return 0;
}

1 件のコメント:

匿名 さんのコメント...

Naked babes [url=http://www.radioelsembrador.cl/foro/viewtopic.php?f=2&t=18154&p=46752#p46752] nude shoots[/url] beach naked