Delphi Programming / Object Pascal
サイトのトップページへ リンクのページへ ロゴマーク
[掲載 2005年04月02日] [更新 2016年05月20日] Delphi サンプルプログラム集
   
DLL 使用サンプルの利用方法
動作確認等 Windows 7 U64(SP1) + Delphi XE(UP1) Pro
図は Windows 7 U64(SP1) + Delphi XE3(UP1) Pro で作成
001_UseDLLProject.zip [2,632 KB] 2016年05月20日版(EXE同梱)







01_DLL のプロジェクトを開いてコンパイル ( [ Ctrl ] + [ F9 ] )


このサンプルプログラム集には,既成の DLL を使用するのではなく,サンプルとして DLL を作成して,その DLL を使用するサンプルがあります.
DLL を使用するプログラムではまず,以下の手順で DLL を作成した後で,本体のプロジェクトをコンパイル.実行して下さい.DLL を同梱しているサンプルもありますが,その DLL のコードを修正変更した場合も同様に修正変更した DLL をコンパイルし直す必要があります.
  • (1) DLL のプロジェクトを IDE で開く
  • (2) [Ctrl] + [F9] でコンパイルする
  • (3) サンプルプログラムの本体プロジェクトを開いて [F9] で実行する


図1
plHookCBT.dpr を開く
図2
[Ctrl] + [F9]
  • DLL と dcu が作成される




02_プロジェクトグループを利用


DLL を使用したサンプルの様に,複数のプロジェクトを使用するアプリケーションでは,プロジェクトグループを作成すると便利な場合があります.既存のプロジェクトに,[プロジェクト] [既存のプロジェクト追加] で追加できます.もちろん,新規のプロジェクトも追加可能です.

次回からは,このプロジェクトグループを IDE で開けば,同時の複数のプロジェクトの編集ができることになります.


図3
プロジェクトグループの例
  • 編集対象となるのは太字のプロジェクト
  • プロジェクトグループでの保存もできる




03_DLL 内の処理コードについて


当サイトのサンプルプログラムでは,DLL にしなければ動作しないサンプル以外は基本的に DLL を使用していません.これは,Delphi の特徴である,ランタイム等を必要としないアプリケーションが作成できる,ということにもつながります.

各サンプルの DLL 内のコードは,汎用性があるわけではありません.DLL を使用しているサンプルは,ほとんどフック関数の関係ですが,フックの処理内容によっては,他のプロセスにメッセージを送らない方がいい場合もあります.また一方では他のプロセスにメッセージを送らないと,システム全体に影響を与えることもあります.
したがって,汎用性のある DLL のコードを作成するには,それなりの工夫が必要となります.




04_DLL をリソースとして EXE に埋め込む


DLL を使用するアプリは DLL も配布する必要があります.そこで, DLL を リソースとして EXE に埋め込んでしまう方法が考えられます.そうすれば,配布の際に DLL は必要ありません.

本項は,EXE に埋め込んだ DLL のテストです.テストに使用する DLL は以下のように,関数を 1 つだけ実装した簡単な内容としています.このプロジェクトをコンパイルして DLL を作成します.



リスト1
テストに使用した DLL のプロジェクト plTestDLL.dpr のコード
DLL 内の関数は 1 つだけ (ダイアログを表示する関数)
library plTestDLL;

uses
  Windows;

function DiaplayDialog(hHandle: HWND; Text: PChar): Boolean; stdcall;
begin
  Result := True;
  MessageBox(hHandle, Text, 'DLL のダイアログ', MB_ICONINFORMATION);
end;

exports
  DiaplayDialog;
end.


DLL を作成したら新規にプロジェクトを作成します.そして,[プロジェクト] [リソースと画像] から下図の [リソースマネージャ] を表示して,[追加] ボタンで対象の DLL を追加します.
プロジェクトをコンパイルすれば,リソースに DLL が格納されます.リソースを変更したら,プロジェクトをビルドまたは再構築します.


図4
[リソースと画像] のダイアログ
  • このダイアログは [リソースマネージャー] とも言う
  • リソース識別子の名前は任意.ここではデフォルトのままで使用

下に示すコードが EXE に埋め込んだ DLL を使用するサンプルです.
ビットマップ画像や JPEG 画像であれば,以下のような,LoadFromStream というメソッドで,メモリから画像を読み込むことができますが,DLL ではそのような操作はできません.そこで,リソースから読み込んだ DLL をディスクに保存して使用します.

後は,DLL を動的にロードして使用します.


  TMemo.Lines.LoadFromStream(TResourceStream);
  TBitmap.LoadFromStream(TResourceStream);
  TJpegImage.LoadFromStream(TResourceStream);
  sndPlaySound(TResourceStream.Memory, SND_MEMORY or SND_SYNC);




図5
実行結果
  • このダイアログは,DLL 内の関数が表示している

リスト2
DLL を使用するプログラムのコード
リソースから取り出した DLL をディスクに保存してから利用する
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
    FDLLHandle   : THandle;
    FShowDlgFunc : function (hHandle: HWND; AText: PChar): BOOL; stdcall;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

//=============================================================================
//  フォーム生成時の処理
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
var
  LDllName    : string;
  LResStream  : TResourceStream;
  LFilePath   : string;
begin
  LDllName := 'plTestDLL.dll';
  //対象のリソースの存在を確認
  if FindResource(hInstance, 'Resource_1', RT_RCDATA) = 0 then exit;

  //リソースを読み出してディスクに保存
  LResStream := TResourceStream.Create(hInstance, 'Resource_1', RT_RCDATA);
  LFilePath  := ExtractFilePath(Application.ExeName) + LDllName;
  try
    LResStream.SaveToFile(LFilePath);
  finally
    FreeAndNil(LResStream);
  end;


  //DLLをロード
  FDLLHandle := LoadLibrary(PChar(LFilePath));
  if FDLLHandle <> 0 then begin
    //DLL内の関数のアドレスを取得
    //関数が複数ある場合は各々の関数のアドレスを取得
    //あるいはその関数を使用する時に取得
    @FShowDlgFunc := GetProcAddress(FDLLHandle, 'DiaplayDialog');
  end;
end;

//=============================================================================
//  フォーム破棄時の処理
//=============================================================================
procedure TForm1.FormDestroy(Sender: TObject);
begin
  if FDLLHandle <> 0 then begin
    FreeLibrary(FDLLHandle);
  end;
end;

//=============================================================================
//  DLL内の関数の実行
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
  if @FShowDlgFunc <> nil then begin
    FShowDlgFunc(Handle, 'リソース DLL のテスト');
  end;
end;

end.




05_リソースからロードした DLL を直接使用する方法


リソースとして埋め込んだ DLL をディスクに保存しないで,メモリ上で操作して使用する方法があります.そのためには,EXE と DLL の構造を解析して操作する必要がありますが,そのような操作用のコードを提供しているサイトがあります.



上記の DelphGear.cn から mm_20130708.zip をダウンロードしてテスしてみました.ダウンロードして解凍したユニット MemoryModule を使用します.
前項と同じ DLL のプロジェクトを使用して,32 ビット と 64 ビットの DLL を作成しました.そして,32 ビットと 64 ビットの EXE を作成して動作を確認しました.EXE は Delphi XE(UP1) Pro と Delphi XE7 でコンパイルして作成しました.Windows 7 U64(SP1) 上で実行しています.
下に示すのがテストに使用したコードです.実行結果は前項と同じです.

警告が幾つか現れます.気になるのであれば修正するか,警告の表示を無効にします.



図6
実行結果
  • このダイアログは,DLL 内の関数が表示している

リスト3
リソースの DLL をディスクに保存しないで利用する方法
DelphiGear.cn からダウンロードした MemoryModule というユニットを使用する例
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
    FMemModule : NativeUInt;
    FShowDlgFunc : function (hHandle: HWND; AText: PChar): BOOL; stdcall;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses
  MemoryModule;

{$R *.DFM}

//=============================================================================
//  フォーム生成時の処理
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
var
  LResStream : TResourceStream;
begin
  //対象のリソースの存在を確認
  if FindResource(hInstance, 'Resource_1', RT_RCDATA) = 0 then exit;

  //リソースを読み出す
  LResStream := TResourceStream.Create(hInstance, 'Resource_1', RT_RCDATA);
  try
    FMemModule := MemLoadLibrary(LResStream.Memory);

    if FMemModule <> 0 then begin
      //DLL内の関数のアドレスを取得
      //関数が複数ある場合は各々の関数のアドレスを取得
      //あるいはその関数を使用する時に取得
      @FShowDlgFunc := MemGetProcAddress(FMemModule, 'DiaplayDialog');
    end;
  finally
    FreeAndNil(LResStream);
  end;
end;

//=============================================================================
//  フォーム破棄時の処理
//=============================================================================
procedure TForm1.FormDestroy(Sender: TObject);
begin
  if FMemModule <> 0 then begin
    MemFreeLibrary(FMemModule);
  end;
end;

//=============================================================================
//  DLL内の関数の実行
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
  if @FShowDlgFunc <> nil then begin
    FShowDlgFunc(Handle, 'リソース DLL のテスト');
  end;
end;

end.


下記の DSPlayer のサイトに GitHub へのリンクがあります.そこから BTMemoryModule と言うユニットがダウンロードできます.この BTMemoryModule も,リソースから取り出した DLL を,メモリ上で操作できます.デモプログラムも同梱されています.

下のコードは,この BTMemoryModule を使用する例です.動作結果は前項のサンプルと同じです.
BTMemoryModule は 32 ビットの EXE, DLL 専用です.



[備考]
BTMemoryModule の最新版は,2016年05月20日の時点では,参考リンクの GitHub にある v0.0.42 です.これは 14.04.2015 の News の記事にリンクがあります.
下記の Google Code Archive には,0.0.41, 0.0.4 があります.これらは同サイトの Download のページにリンクがあります.これらのユニットを使用して,下のコードを実行すると,アプリの終了時に例外が発生します.OnCreate イベント内で実行しているコードを,OnShow イベント内で実行すれば例外は発生しません.


リスト4
リソースの DLL をディスクに保存しないで利用する方法
BTMemoryModule というユニットを使用する例
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, BTMemoryModule;

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
    FPBTMemModule : PBTMemoryModule;
    FShowDlgFunc : function (hHandle: HWND; AText: PChar): BOOL; stdcall;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

//=============================================================================
//  フォーム生成時の処理
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
var
  LResStream : TResourceStream;
begin
  //対象のリソースの存在を確認
  if FindResource(hInstance, 'Resource_1', RT_RCDATA) = 0 then exit;

  //リソースを読み出す
  LResStream := TResourceStream.Create(hInstance, 'Resource_1', RT_RCDATA);
  try
    FPBTMemModule := BTMemoryLoadLibary(LResStream.Memory, LResStream.Size);
    if FPBTMemModule <> nil then begin
      //DLL内の関数のアドレスを取得
      //関数が複数ある場合は各々の関数のアドレスを取得
      //あるいはその関数を使用する時に取得
      @FShowDlgFunc := BTMemoryGetProcAddress(FPBTMemModule, 'DiaplayDialog');
    end;
  finally
    FreeAndNil(LResStream);
  end;
end;

//=============================================================================
//  フォーム破棄時の処理
//=============================================================================
procedure TForm1.FormDestroy(Sender: TObject);
begin
  if FPBTMemModule <> nil then begin
    BTMemoryFreeLibrary(FPBTMemModule);
  end;
end;

//=============================================================================
//  DLL内の関数の実行
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
  if @FShowDlgFunc <> nil then begin
    FShowDlgFunc(Handle, 'リソース DLL のテスト');
  end;
end;

end.