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







01_DLL のプロジェクトを開いてコンパイル


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


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




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


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

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


[備考]
DLL のプロジェクトもプロジェクトグループに追加して IDE 上でデバックできますが、1 つのプロジェクトは 1 つのプログラムです。つまり、DLL は、アプリケーションで言えば、全く別のアプリケーションです。このことに注意する必要があります。



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




03_サンプルで使用している DLL 内の処理コードについて


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

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





04_DLL 内の関数や手続き内の処理結果の文字列を取得


関数や手続きで文字列の引数扱う場合、同じプロジェクト内の関数や手続きであれば、String 型の変数を使用できます。Strring 型の変数の前に予約語 var を付ければ入出力兼用の引数にすることができます。
DLL 内の関数や手続きの場合、DLL の汎用性ということを考慮すると、PChar 型の変数とした方が確実で間違いがありません。
以下のサンプルは、引数に PChar 型の変数を使用する例です。

関数や手続きの PChar 型の引数から文字列を取得するには、PChar 型の文字列を格納するバッファとしてのメモリ領域の確保が必要です。確保したメモリは解放処理が必要です。
このサンプルの関数では、引数の PChar 型変数の値を nil にすると必要なメモリのサイズをバイト数で取得するようにしています。この方法は、Windows API で文字列を扱う際によく使用されている方法です。

関数や手続き内部と呼び出し側では、メモリを確保した変数には文字列を直接代入しないようにします。文字列をセットする時は、StrPCopy や StrPLCopy 関数あるいは MoveChars 関数等を使用して、確保したメモリ領域のメモリに文字列をコピーします。
メモリを確保した変数に文字列を直接代入すると、確保したメモリ以外の場所に文字列が格納されます。その結果、期待する処理結果が取得できないことになります。インデックス (添字) を指定して文字単位での代入であれば問題ありません。また、メモリを開放した後であれば、その変数に文字列を直接代入しても構いません。



図4
DLL 内の関数に文字列を渡す
  • このダイアログは DLL 内部で表示している
図5
DLL から文字列を取得

リスト1
DLL のコード
外部から文字列を受け取る関数と文字列を外部に渡す関数だけを実装
library DllTest_PChar;

uses
  Windows,
  SysUtils;

//-----------------------------------------------------------------------------
//  DLL内に文字列を渡す
//-----------------------------------------------------------------------------
procedure SetText2Dll(Buff: PChar); stdcall;
var
  LText : string;
begin
  LText := Buff;
  MessageBox(0, PChar(LText), 'DLL 受信文字列', MB_ICONINFORMATION);
end;

//-----------------------------------------------------------------------------
//  DLL内の文字列を外部に渡す関数
//  第1引数をnilにすると必要なバッファの確保サイズの取得となる
//-----------------------------------------------------------------------------
function GetDllText(Buff: PChar; Len: DWORD): DWORD; stdcall;
var
  LText : string;
begin
  LText := 'DLL の文字列取得';

  // +1はnull終端文字の分
  Result := Length(LText) + 1;
  if Buff <> nil then begin
    // 呼出し側で確保した(メモリまたは文字列の)領域に文字列をコピーする
    // Buff := PChar(LText); のような直接代入はしないこと
    SysUtils.StrPLCopy(Buff, LText, Result);
    Result := Length(LText);
  end;
end;

//-----------------------------------------------------------------------------
exports
  SetText2Dll,
  GetDllText;

end.


DLL を使用する外部のプログラムからは、例えば以下のようにして文字列を取得します。



リスト2
DLL を使用するプログラムのコード
implementation

{$R *.dfm}

procedure SetText2Dll(Buf: PWChar); stdcall; external 'DllTest_PChar.dll';
function GetDllText(Buf: PChar; Len: DWORD): DWORD; stdcall; external 'DllTest_PChar.dll';

//=============================================================================
//  DLLに文字列を渡す
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LText : string;
begin
  LText := '日本語';
  SetText2Dll(PChar(LText));
end;

//=============================================================================
//  DLL内の関数から文字列を取得
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LLen   : DWORD;
  LPChar : PChar;
  LText  : string;
begin
  LLen := GetDllText(nil, 0);
  if LLen > 0 then begin
    // 文字列を格納するメモリ領域を確保
    LPChar := StrAlloc(LLen);
    try
      GetDllText(LPChar, LLen);

      // 取得した文字列を使用する処理
      // LPChar に文字列を直接代入しないこと
      LText := LPChar;
      MessageBox(Handle, PChar(LText), '結果', MB_ICONINFORMATION);
    finally
      StrDispose(LPChar);
    end;
    // LPChar のメモリを解放したので, LPChar に文字列を代入してもよい
  end;
end;


当然ですが、DLL 内の文字列を取得する際、十分大きな領域を確保すれば、以下のように、必要なバッファサイズを取得する必要はありません。ファイルのフルパス名の取得のように、取得すべき長さが限定されている場合も同じように考えることができます。


//=============================================================================
//  DLL内の関数から文字列を取得
//  十分なメモリを確保していれば必要な文字数取得処理は不要
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LLen   : DWORD;
  LPChar : PChar;
  LText  : string;
begin
  LLen := 100;
  LPChar := StrAlloc(LLen);
  try
    GetDllText(LPChar, LLen);

    LText := LPChar;
    MessageBox(Handle, PChar(LText), '結果', MB_ICONINFORMATION);
  finally
    StrDispose(LPChar);
  end;
end;


文字列を Char 型の静的な文字配列で受け取る場合の例です。
Char 型の静的配列の変数は PChar 型の引数にそのまま渡せます。静的な配列の場合、FillChar 手続きで初期化しておかないと結果の文字列にゴミが入ることがあります。


//=============================================================================
//  DLL内の関数から文字列を取得
//  Char 型の静的配列を渡して DLL から文字列を取得する場合
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LLen    : DWORD;
  LBuffer : array [0..100] of Char;
  LText   : string;
begin
  // 配列要素の初期化
  FillChar(LBuffer, SizeOf(LBuffer), #0);

  LLen := Length(LBuffer);

  GetDllText(LBuffer, LLen);

  LText := LBuffer;
  MessageBox(Handle, PChar(LText), '結果', MB_ICONINFORMATION);
end;


文字列を Char 型の動的な文字配列で受け取る場合の例です。
Char 型の動的配列の変数は PChar でキャストすれば PChar 型の引数に渡せます。SetLength 手続きを使用して文字配列の文字数を設定すればメモリの確保となります。動的な配列の場合は、SetLength 手続きでメモリを確保すると、確保したメモリ領域が初期化されます。


//=============================================================================
//  DLL内の関数から文字列を取得
//  Char 型の動的配列を渡して DLL から文字列を取得する場合
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LLen    : DWORD;
  LBuffer : array of Char;
  LText   : string;
begin
  LLen := 100;
  SetLength(LBuffer, LLen);

  GetDllText(PChar(LBuffer), LLen);

  LText := string(LBuffer);
  MessageBox(Handle, PChar(LText), '結果', MB_ICONINFORMATION);
end;


文字列を String 型の変数で受け取る場合の例です。
String 型の変数は PChar でキャストすれば PChar 型の引数に渡せます。バッファとしてのメモリの確保には SetLength 関数を使用します。


//=============================================================================
//  DLL内の関数から文字列を取得
//  String 型の変数を渡して DLL から文字列を取得する場合
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LLen    : DWORD;
  LBuffer : string;
  LText   : string;
begin
  LLen := 100;
  SetLength(LBuffer, LLen);

  GetDllText(PChar(LBuffer), LLen);

  LText := LBuffer;
  MessageBox(Handle, PChar(LText), '結果', MB_ICONINFORMATION);
end;


[備考]
一般的に、DLL 内の関数の処理結果は引数で取得するようにします。
DLL 内の関数で発生したエラーは、通常は、例外を含めて DLL 内の関数を使用する側からは捕捉できません。そこで、DLL 内の関数の戻り値でエラー等を検出するようにします。Windows が提供している Windows API の関数類の多くはそういう仕様になっています。





05_文字列変数を関数や手続きの入出力兼用の引数として使用


関数や手続きの PChar 型の引数を入出力兼用として使用する例です。
PChar 型の引数を入出力兼用しして使用する場合も、前項と同様、その PChar 型の変数用のメモリ領域を確保します。確保したメモリは解放処理が必要です。関数や手続き内部での処理、呼び出し側における変数の扱いについても前項と同じです。

引数を入出力兼用にする場合は、処理内容によっては、呼出し側から受け取った文字列を、関数や手続き内部で退避する等の処理が必要になることがあります。



図6
実行結果
  • 「日本語」は DLL に渡した文字列
  • それ以降は DLL 内で追加した文字列

リスト3
PChar 型の文字列引数を持つ DLL の関数の例
library DllTest_PChar;

uses
  Windows,
  SysUtils;

//-----------------------------------------------------------------------------
//  DLL内の文字列を外部に渡す関数
//  第 2 引数を負の値にすると呼出し側で確保に必要な文字数だけをを返す
//  サイズが不足している場合は呼出し側で ReallocMemory 関数で再割り当てが必要
// この文字数には Null 終端文字の分も含まれる
//-----------------------------------------------------------------------------
function GetDllText(ABuff: PChar; ALen: Integer): DWORD; stdcall;
var
  LText : string;
  LLen  : DWORD;
begin
  // 引数から受け取った文字列を退避
  LText := ABuff;

  // DLL 内での文字列の処理例
  // ここでは単に引数から受け取った文字列と結合
  LText := LText + 'DLL内で取得した文字';

  // 処理の結果の文字列が引数の文字列よりも短くなった場合
  //  ゴミが入らないようにする
  if Length(LText) < StrLen(ABuff) then begin
    ZeroMemory(ABuff, StrLen(ABuff) * SizeOf(Char));
  end;

  //+1はnull終端文字の分
  LLen   := Length(LText) + 1;

  if ALen < 0 then begin
    Result := LLen;
  end else begin
    // 呼出し側で確保した領域に文字列をコピーする
    // ABuff := PChar(LText); のような直接代入はしないこと
    SysUtils.StrPLCopy(ABuff, LText, LLen);
    Result := Length(LText);
  end;
end;

//-----------------------------------------------------------------------------
exports
  GetDllText;

end.


上の DLL の関数を使用する例です。実行結果は上の図の通りです。



リスト4
上の DLL の関数を使用する例
implementation

{$R *.dfm}

function GetDllText(ABuff: PChar; ALen: Integer): DWORD;
  stdcall; external 'DllTest_PChar.dll';

//=============================================================================
//  DLL内の関数から文字列を取得
//  PChar 型の変数で取得する例
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LBuffer  : PChar;
  LTextLen : Integer;
  LText    : string;
  LLen     : Integer;
begin
  // Null 終端文字も含めて 200 文字分のメモリを確保
  LTextLen := 200;
  LBuffer  := AllocMem((LTextLen + 1) * SizeOf(Char));
  try
    // LBuffer (PChar 型) に直接文字列を代入しないこと
    StrPCopy(LBuffer, '日本語');

    // 必要な文字数を取得
    LLen := GetDllText(LBuffer, -1);
    if LLen > (LTextLen + 1) then begin
      // バッファが不足している場合は再確保
      ReallocMem(LBuffer, LLen * SizeOf(Char));
    end;
    LLen := GetDllText(LBuffer, LLen);

    // 取得結果の LBuffer (PChar 型) に直接文字列を代入しないこと
    LText := string(LBuffer);

    LText := LText + sLineBreak + '文字数 : ' + IntToStr(LLen);
    MessageBox(Handle, PChar(LText), '結果', MB_ICONINFORMATION);
  finally
    // 確保したメモリを解放
    FreeMem(LBuffer);
  end;
  // LBuffer (PChar 型) のメモリを解放したので LBuffer に直接文字列を代入してもよい
end;

//=============================================================================
//  DLL内の関数から文字列を取得
//  PChar 型の変数で取得する例
//  十分なメモリを確保している場合は必要な文字数のチェックは不要
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  LBuffer  : PChar;
  LTextLen : Integer;
  LText    : string;
  LLen     : Integer;
begin
  // Null 終端文字も含めて 200 文字分のメモリを確保
  LTextLen := 200;
  LBuffer  := AllocMem((LTextLen + 1) * SizeOf(Char));
  try
    // LBuffer (PChar 型) に直接文字列を代入しないこと
    StrPCopy(LBuffer, '日本語');

    LLen := GetDllText(LBuffer, LTextLen);

    // 取得結果の LBuffer (PChar 型) に直接文字列を代入しないこと
    LText := string(LBuffer);

    LText := LText + sLineBreak + '文字数 : ' + IntToStr(LLen);
    MessageBox(Handle, PChar(LText), '結果', MB_ICONINFORMATION);
  finally
    // 確保したメモリを解放
    FreeMem(LBuffer);
  end;
  // LBuffer (PChar 型) のメモリを解放したので LBuffer に直接文字列を代入してもよい
end;


[備考]
DLL 内の変数の値は、その DLL を使用するアプリ固有の値となります。この仕組みによって、同じ DLL 内の関数や変数を複数のプログラムから使用できるようになっています。あるプログラムで DLL 内の変数に値を設定しても、その値を別のプログラムから取得することはできません。





06_DLL 内で DLL 自身のフルパスを取得


DLL 内で DLL 自身のフルパスを取得する例です。
GetModuleFileName 関数を使用して取得します。GetModuleName 関数の引数を 0 にすると、この DLL の関数を呼び出した EXE のフルパスの取得になります。ParamStr(0) の値もこの DLL 内の関数を呼び出した EXE のフルパスの取得となります。これらの値も取得するようにしています。



図7
実行結果
  • 最初の値が DLL 自身のフルパス
  • 後の 2 つは DLL を呼び出した EXE のフルパス
  • 全て DLL 内で取得した値

リスト5
DLL 内で DLL 自身のフルパスを取得する関数の例
library DllTest_PChar;

uses
  Windows,
  SysUtils;

//-----------------------------------------------------------------------------
//  DLL自身のフルパスを取得する関数
//  ついでにこのDLLを呼び出したEXEのフルパスも取得
//  
//  引数のLenには十分サイズの値を呼び出し側で設定する
//-----------------------------------------------------------------------------
function GetDllPath(Buff: PChar; Len: DWORD): DWORD; stdcall;
var
  LText : string;
begin
  Result := 0;
  //このDLL自身のフルパスを取得
  //このifの条件は実際には不要(Libraryであることは間違いないので)
  if IsLibrary then begin
    LText  := GetModuleName(HInstance);
    Result := Length(LText);
  end;

  //このDLL呼び出したEXEのフルパスを取得して追加
  LText := LText + sLineBreak + GetModuleName(0);

  //このDLL呼び出したEXEのフルパスをParamStr(0)で取得した値も追加
  LText := LText + sLineBreak + ParamStr(0);

  //呼出し側で確保した(メモリまたは文字列の)領域に文字列をコピーする
  SysUtils.StrPLCopy(Buff, LText, Len);
end;

//-----------------------------------------------------------------------------
exports
  GetDllPath;

end.


上の DLL の関数を使用する例です。実行結果は上の図の通りです。



リスト6
上の DLL の関数を使用する例
implementation

{$R *.dfm}

function GetDllPath(Buf: PChar; Len: DWORD): DWORD; stdcall; external 'DllTest_PChar.dll';

//=============================================================================
//  DLL自身のフルパスを取得
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  LPChar : PChar;
  LLen   : DWORD;
begin
  LLen := 1025;
  //文字列の領域を確保
  LPChar := StrAlloc(LLen);
  try
    GetDllPath(LPChar, LLen);

    //取得した値を使用する処理
    MessageBox(Handle, LPChar, '結果', MB_ICONINFORMATION);
  finally
    StrDispose(LPChar);
  end;
end;




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


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

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



リスト7
テストに使用した 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 が格納されます。リソースを変更したら、プロジェクトをビルドまたは再構築します。


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

下に示すコードが 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);



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

リスト8
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.




08_リソースからロードした DLL を直接使用 - MemoryModule


リソースとして埋め込んだ 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) 上で実行しています。
下に示すのがテストに使用したコードです。実行結果は前項と同じです。

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



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

リスト9
リソースの 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.




09_リソースからロードした DLL を直接使用 - BTMemoryModule


下記の 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 イベント内で実行すれば例外は発生しません。


リスト10
リソースの 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.