DLLを作ってみちゃう?


さて、「DLLを使っています」とか「ソケットという機構です」とか言っていても、プログラムを書いた事がある方には
「ああ、そうですか」という感じだし、そうでない人には「勝手に話が進んでるよ・・まったく・・」という
全く面白くも無い話になってしまいます。

そこで!実際にDLLを作ってみようではありませんか!
プログラムを書いたことがある方には、暫く面白くない話が続きますが、ご理解ください.

Windowsとあなたのプログラムとのファーストコンタクト!

プログラムが動くと言うことは、動き始める部分があるはずです。
それは何処でしょうか?

さて、ここでインストールしていただいたヘルプを立ち上げて見てください。
VCを立ち上げ「メニュー」⇒ヘルプ⇒キーワード を選んでください。

「キーワードを入力してください」というところでカーソルが点滅している大きなWindowが表示されましたね。
ここで「WinMain」と入力してください。
インストールした環境によってはCDを要求されるかもしれませんが、その指示に従ってください。

出ましたね?
ちょっと文章を読んで見ましょう。

「The WinMain function is called by the system as the initial entry point for a Win32-based application. 」
(WinMain 関数は、Win32 アプリケーションの初期エントリポイントです。Windows システムが呼び出します。)

とあります。でもこれは、アプリケーションの動き始める部分ですね。DLLではないようです。
もし、アプリケーションを作る場合には、この関数が呼び出されてスタートします。
覚えておいて損は有りませんので、記憶の片隅にでも置いておきましょう。

では、DLLの動き始める部分は何処でしょうか?
先程と同じように、今度は「DllMain」で検索して見てください。
出ましたね?

The DllMain function is an optional method of entry into a dynamic-link library (DLL).
If the function is used, it is called by the system when processes and threads are initialized
and terminated, or upon calls to the
LoadLibrary and FreeLibrary functions.
(DllMain関数はダイナミックリンクライブラリ(DLL)へのエントリーポイントです。
この関数が呼び出されるタイミングは、プロセスまたはスレッドが作成、終了する時です。
この関数はWindowsシステムが呼び出すか、あるいはLoadLibraryおよびFreeLibraryによって呼び出されます。)

これですね。
いずれも「Main」という名前が付きます。
Win(dows Application)Main
D(ynamic)(ink)(ibrary)Main

という大変判りやすい関数名がつけられています。

 


[ちょこっとコラム]

私は、Windowsプログラムを作り始めた頃、ずっと不思議だったことがあります。
それは、「なんで、関数名がこの名前に限定されているのか?」と言うことです。

調べているうちに、何で動くのかな?と言うところまで来てしまった事があります。
よくよく考えて見ると、Windowsを作ったマイクロソフト社だって、日本のしがないプログラマ(私)が
作る小さなプログラムをいちいちサポートする特別な口を設ける義務などは全く無いわけですよね・・・

ですから、マイクロソフト社は「アプリケーションは必ずこのWinMain関数を持っていること!、DLLは必ず
DllMain関数を持っていること!、これ以外は知りません。パラメータは全て決まっています。
必ず一番最初に呼び出しますので、それまで私(Windows)を呼ばないで!」
と言うことにしているのです。
だから、Windowsが発売された後に、ユーザーが好きに作ったアプリケーションが起動できるのです。

名前が決まっていて、やり取りも決まっている。で、必ず最初に呼び出される。
これって、なんかソケットみたいじゃないですか。

そうなのです。いわばアプリケーションもDLLも皆、WindowsのShellから見ればWindowsソケットアプリケーション
なんですよね!!


 

では、DLLを作ってみましょう。
VCを立ち上げて、「ファイル」⇒新規作成 を選んでください。
プロジェクトダイアログが開いていますね?
ではここで、下の図のように入力して見てください。

プロジェクトダイアログ

次に表示されるダイアログでは「空のプロジェクト」を選んでください。
さて、これでプロジェクトを作ることが出来ました。
では、プログラムを作って行きましょう!
「ファイル」⇒新規作成
で、ファイル作成ダイアログが開きましたね?先程は「プロジェクトタブ」が選ばれていましたが
今回は、「ファイルタブ」が選ばれていると思います。
「C++ソースファイル」を選び、右側のファイル名を同じように書き込んでください。
ファイルの新規作成
これで、OKボタンを押すと、エディタが表示され、「さあ!プログラム書いてよ!」ってモードになります。

それでは、Windowsの流儀に則って、プログラムを書いてみましょう!
まず最初は、ヘッダの定義ですね。
Windowsのプログラムを作る際には、必ずインクルードするものとして覚えておきましょう。

#include   <windows.h>

この[Windows.h]には、Windowsプログラムを作る際に必要な事柄が細かく定義されています。
これを忘れてしまうと、エラーやらワーニングがい〜〜〜〜〜っぱい出てしまいます。
だから、好む好まないを考えずに、必ず使うようにしましょう。

では、次に一番最初に呼ばれる関数「DllMain」を作ります。

HINSTANCE  h_instdll ;

BOOL WINAPI DllMain(
  HINSTANCE inst,
  DWORD fdwReason,
  LPVOID lpvReserved
)
{
    switch (fdwReason) {
        case DLL_PROCESS_ATTACH :
            h_instdll = inst;
            break;
        case DLL_THREAD_ATTACH :
            break;
        case DLL_THREAD_DETACH :
            break;
        case DLL_PROCESS_DETACH :
            h_instdll = NULL;
            break;
    }
    return TRUE;
}

さあ、これで完了です。これがDLLの肝となる部分です。
たったこれだけの関数ですが、色々な仕組みが隠されています。
では、それを質疑応答の形で解説して見ましょう。

質問 : BOOLって何よ?
回答 : BOOLとは、コンピュータの世界では最早説明も要らないブール代数を示す変数型です。
      ブール代数は「ある」「ない」の2つの状態を示すことが出来る最も基本的な型です。
      本来、BOOL型というものは存在していませんが、明示的にBOOLという型を作ることによって
      この関数が「成功した」か、「失敗した」かを示すために使われています。
      関数の最後に「TRUE」と言う値を返しています(RETUEN)。
      これは、「成功したぜ!」と言っているのですね。
      ここを、「return FALSE ;」とすると、「失敗しちゃった・・・てへっ!」て事を意味します。

質問 : WINAPIって何でしょう?
回答 : これは、「自分以外のプログラムから呼び出されますので、Windowsさん、仲介してくださいね」
      という事を宣言しています。
      細かくは、専門書に譲りますが、C/C++言語を使ってWindowsプログラムを作る場合には
      呼び出し規約(この関数を呼び出す手順のことです)が2つ有ります。
      一つは「PASCAL規約」、もう一つは「C規約」というものです。

      「いやいや、これはC言語だから、C規約だろ?何を言っとるのかね君は?」
      と上司に突っ込まれたことが有りますが、これが意味するものは、
      「Windowsがユーザプログラムを呼び出す際には全てPASCAL規約で統一されている」ことを意味しています。
      ですので、Windowsを仲介させて呼び出される関数は、全てPASCAL規約である必要があるのです。
      少々乱暴な使い方が出来る「C」より、「しっかり記述されるPASCAL」の方が問題が起こりにくい
      というわけです。(その他色々とメリットがあります。気になる方は専門書を参照して見てください)

      (拡張ボードDLLを作ってみると、EmuZ-2000では、掟破りの「C規約コールバック」が有りますが・・)

質問 : switch 〜 caseにかかれていることは、何を意味していますか?
回答 : これには深〜い意味があるのです。
      これがあるお陰で、DLLという仕組みはうまく動いていると言っても過言ではないでしょう。
      少々長くなりますが、DLLを作るためには、その特徴を押えておく必要があるので説明します。

      DLLはプログラムコードを一つにまとめることが出来て、メモリの節約が出来ることをお話しました。
      でも、プログラムが使用するメモリエリアは、一つにまとめられない、まとめると大変なことになるとも
      お話しましたね。
      これは、そのからくりをWindowsと協調しながら作り出す重要な部分なのです。
      caseの中身を一つずつ見ていきましょう。

      DLL_PROCESS_ATTACH
       これは、DLL(このライブラリプログラム)を使用するアプリケーションが起動されたことを通知してきます。
       Windowsシステムは、DLLに対して
       「これがあなたの作業領域にアクセスするパスです。これがあれば、親アプリにも私(システム)にも
        辿り着けます。これが無いと、あなたの要求は処理されませんので、ヨロシクね」
       と言って、引数「HINSTANCE inst」を渡してきます。
       ですから、受け取ったら、大事に保存しているのです。
       これがあるから、自分を利用するアプリケーションが、いくつ起動されても、間違いなく応答が出来るのです。
       考えてますねぇ。

       ここが何回呼ばれたかを覚えておけば、自分を利用しているアプリケーションがいくつ起動しているかを
       知ることが出来ます。
       但し、メモリエリアは別々に作られますので、共通のメモリエリアを設けておく必要があります。

      DLL_PROCESS_DETACH
       これは、DLL_PROCESS_ATTACHの逆で、
       「今まさに、あなたを利用していたアプリケーションが終焉の時を迎えています。あなたも帰り支度をして下さい。」
       という通知がなされているのです。
       これを受け取ったなら、終わる準備をしましょう。
       準備が完了したら、システムに制御を返せばよいのです。
       複数のアプリケーションから呼ばれている場合にも、特に気にする必要はありません。
       なんてったって、作業領域は全く別なのですから。

       自分を利用しているアプリケーションの数をカウントしている場合には、ここが呼ばれたときに、そのカウントを
       デクリメント(−1)してあげればよいでしょう。

      DLL_THREAD_ATTACH
      DLL_THREAD_DETACH
       プロセスのATTACH、DETACHの2つは、アプリケーションの起動・終了を知るために呼ばれるものでした。
       この2つは、プロセスの中で使用されている、スレッドの起動・終了を知るために通知されてきます。
       一般的には、あまり利用されることは少ないと思いますが、スレッド単位で何か細かな制御が必要な場合には
       この通知を利用するべきでしょう。
       (って言うか、私もこの2つは利用したことがありません・・・ごめん)

さて、一番最初に呼ばれるべきところは作りました。
次は、それ以外のタイミングで呼ばれる部分を作りましょう!
そうですねぇ、じゃあ、ある値を渡せば、その値を2で割って、商とあまりを返すDLLにして見ましょう。

成功したか、失敗したかを返す関数を作るとして、型はBOOL、Windowsが仲介するのでWINAPI、関数名は
Divide2、引数は、値を持ってくるVal、商を返すshou、あまりを返すamari、とこんなものでしょうか?
ではこれにしたがって、関数を書いてみましょう。

BOOL
WINAPI
Divide2( DWORD Val, DWORD *shou, DWORD *amari )
{
    *amari = ( Val & 1 );
    *shou = ( Val >> 1 );
    return TRUE ;
}

と、こうなります。
え?こんなのDLLにする必要なんて無い?
ごもっともです。
ごもっともですが、単純なDLLで動きを確認してから、大きな開発に行こうではありませんか。

では、このファイルをセーブしましょう。
「ファイル」⇒「上書き保存 (Ctrl+S)」を選んで、セーブしてください。
このままでは、「dllmain.c」というファイルが作られたに過ぎませんので、プロジェクトの仲間に入れてやりましょう。

「プロジェクト」⇒「プロジェクトへ追加」⇒「ファイル」を選んでください。
すると、ファイルダイアログが開いて、先程セーブした「dllmain.c」が見えると思います。
このファイルをクリックして、青く反転させておいて、OKボタンを押してください。

これで、完了です。

さあ、ビルド(実行可能な状態にコンパイルしてリンクすること)しましょう!
「F7」ボタンを押してください。
ビルドが始まります。
エラーが出ましたか? エラーが出た場合には、タイプミスがあるかもしれませんので、もう一度じっくり見直してください。

さて、ビルドがノーエラーで完了すれば出来上がりです。
これで、プログラムが実行可能な状態になったことが保証されたのです。

でも、ここで忘れてはならないことが一つあります。
このままでは、DLLというファイルが作られたに過ぎないと言うことです。
「DLLが出来たけど、動かないよぅ」という事になるので、ここからの話も重要です。

エクスポートしてあげよう!

関数が出来たのなら、その関数を外に向かって「輸出(エクスポート)」してあげようではありませんか!
海外へ物品を送る場合など、その手続きがあるように、DLLの関数も外に対して公開するには
それなりの手続きが必要なのです。

そのからくりが、DEFファイルなのです。
皆さんが作られた、拡張ボードDLLのソースを見て見ると、必ず「*.DEF」というファイルがあることが確認できます。
(Suga様、たご様、常岡様、各人のソースを参考に出来るよう公開してくださっていることに感謝いたします)

このファイルを作っておかないと、DLLが出来上がっても、DLLという名前のファイルがあるだけになってしまいます。
さて、もう一度VCを使って、DEFファイルを作って行きましょう。

DEFファイルを作ろう!

上記のように、ファイルを新しく作成して見てください。
OKボタンを押せば、またエディタモードでカーソルが点滅しているはずです。
では、下のように入力して見ましょう!

LIBRARY   TESTDLL

DESCRIPTION 'TESTDLL Beans Burn'

EXPORTS
    Divide2

LIBRARYの行は、DLLファイル名と同じ名前を書くようにしましょう。
DESCRIPTIONの行には、そのDLLに対するコメントを書いておきましょう。
            Copyrightを書いても良いでしょう。
EXPORTSより下の行には、外に公開するべき関数名を書きましょう。

入力できましたか?
それでは、セーブをして、プロジェクトに追加して見てください。
え?ファイルダイアログに出てこない?
そうですね、ではファイルダイアログの下のほうに、「ファイルの種類」という部分があると思いますので、
そこで、「定義ファイル(DEF)」というのを選んでください。
出てきましたね。
それでは、追加して、もう一度ビルドして見ましょう!

リンク中...
   ライブラリ Debug/testdll.lib とオブジェクト Debug/testdll.exp を作成中
お!こんな行がレポートされました。下のほうに出てきていると思いますので、確認して見てください。

DLLファイルと、LIBファイルが出てきました。
このLIBファイルには、動的にライブラリをリンクするからくりが仕組まれています。
DLLファイルの大きさは約220KByte(デバッグ版だから大きいのですが・・・)、
LIBファイルの大きさは、約2KByte、大きさに差が有りすぎますね。

で、作られたものは、あくまでもライブラリですから、これだけでは実行できません。
確認用アプリケーションを付けておきますので、自分で作ってみたDLLが動くかどうか、
試して見てください。
確認用プログラムに、リンクされるのは、このLIBファイルなのです。
え?ライブラリはDLLの方じゃないかって?、そうなんです。

DLLをリンクするのではなくて、LIBをリンクすることによって、プログラムを騙しているのです。
変な言い方ですね、でもこれが現実なのです。
ビルドはプログラムが存在しない関数を使用していることを発見すると、エラーを出して中断してしまうのですが、
このLIBファイルがあると、あたかも関数がそこに存在しているかのように勘違いさせることが出来るのです。

実行直前に、リンクをするDLLという方式だからこそ、この様になっているのですね。
でも、実行する際にDLL本体が見つからなければ、Windowsから怒られてしまいます^^;;;

では、最後に、デバッグ版を、リリース版にするやり方を説明します。
VCから「ビルド」⇒「アクティブな構成の設定」を選んでください。
ここには、DebugとReleaseと2つ選べるようになっています。
ここで、以下のように選び
アクティブな構成の設定だよ

OKボタンを押せば完了です。

もう一度、「F7」キーを押せば、ビルドが始まります。

いやあ、今回は長かったですね、説明が下手なもので、こんなに長くなってしまいました。
DLLを作ったことが無い人にも、DLLの作り方を説明しておきたかったので、この様になりました。


長い割に、判りにくいですか?ごめんなさいm(_ _)m

次回は、このDLLを使って、拡張ボードDLLをEmuZ-2000がどのように呼び出しているかを
説明いたします。

テストアプリケーションは、こちらからダウンロードできます。

テストアプリケーションのプロジェクトは、こちらからダウンロードしてください。

DLLのプロジェクトは、ここからダウンロードできます。