DLL上の例外をコピーした文字列をFreeLibraryにアクセスとエラーになる

解決


AAA  2018-09-20 09:02:07  No: 49506

DLL上で例外を発生させ
例外のテキストを文字変数にコピーした後
FreeLibraryの後にコピーした文字変数にアクセスすると
エラーが出るのですが

バグですかね?

var
    ModuleHandle : HModule;
    TPExecute: TFarProc;
    S: String;
begin
    ModuleHandle := LoadLibrary('DUMMY.DLL');
    if ModuleHandle <> 0 then
    begin
      TPExecute  := GetProcAddress(ModuleHandle, 'Execute');
      if (TPExecute <> nil) then
      begin
        @DLLExecute  := TPExecute;
        try
          DLLExecute;
        except
          on E: Exception do
          begin
            S := E.Message;
          end;
       end;
      end;
      Memo1.Lines.Add(S);
      FreeLibrary(ModuleHandle);
      Memo1.Lines.Add(S);        //エラーになる
    end;

---- DUMMY.DLL -----

library DUMMY;

uses
  System.SysUtils, System.Classes, Winapi.Windows;

function Execute: Integer;stdcall;
var
    S: String;
begin
    S := 'XXXX';
    StrToInt(S);
end;

exports
   Execute;

begin
end.

----- 以下の様にすればエラーは出なくなるのですが -----

var
    ModuleHandle : HModule;
    TPExecute: TFarProc;
    P : array[0..256] of Char;
begin
    ModuleHandle := LoadLibrary('DUMMY.DLL');
    if ModuleHandle <> 0 then
    begin
      TPExecute  := GetProcAddress(ModuleHandle, 'Execute');
      if (TPExecute <> nil) then
      begin
        @DLLExecute  := TPExecute;
        try
          DLLExecute;
          P := 'XXXXXX';
        except
          on E: Exception do
          begin
            StrPCopy(P,E.Message);
          end;
       end;
      end;
      Memo1.Lines.Add(P);
      FreeLibrary(ModuleHandle);
      Memo1.Lines.Add(P);
    end;


通りすがり  2018-09-20 15:22:41  No: 49507

EXEとDLLをまたいで例外を伝播させるのは不可ですね。
(EXEをDelphiで、DLLをVC++で作っているような状況を考えればあたりまえですよね)
そういうことをしたければDLLを実行時パッケージにしましょう。
基本的にDLLのエクスポート関数では例外を全て補足するのが一般的かと。


Mr.XRAY  2018-09-21 01:16:55  No: 49508

DLL は別プロセスで実行されます.アプリで言えば全く別のアプリです.
別プロセス (別のアプリ) の例外等のエラーは,そのアプリ内で処理するのが原則です.
Windows API の関数の多くは,実際の処理結果は引数で,エラー等は戻り値で取得するようになっています.

> S := E.Message;  // S は string 型の変数
> StrPCopy(P,E.Message);  // P は Char型の文字配列

StrPCopy(P,E.Message); で文字列が取得できるのは,DLL 内の関数の文字列の引数の扱いと同じですね.
DLL が発行するメッセージですから基本的に Null 終端文字列です.
また,メモリを仲介しないと正常に取得できません.例外が発生します.
つまり,

> Memo1.Lines.Add(S);        //エラーになる 

ではなく,S := E.Message; で例外が発生していることになります.
メモリを介するという意味では StrPCopy が良いと思います.
参考までに以下のようにも書けます (メモリを使用しているのを明確するための参考).

バグかという問題は,質問された方におまかせします.

//=============================================================================
//  DLL内の関数実行による例外を補足する例
//  別プロセスの例外,エラーはそのプロセスで処理するのが基本
//  このコードでの例外はDLLで発生している
//  したがってメッセージの文字列はNull終端文字列(基本的にPChar型)
//  別プロセスのメッセージなのでメモリを介さないと取得できない
//
//  短いコードでも手抜きはしない方針
//  以下は実際にコピペで動作確認可能なコード
//
//  動作確認は Windows 7 U64(SP1) + Delphi XE5(UP2) Pro
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  DLLExecute   : function : Integer; stdcall;
  ModuleHandle : HModule;
  TPExecute    : TFarProc;
  LStream      : TMemoryStream;
  LCharArry    : array[0..256] of Char;
begin
  Memo1.Lines.Clear;

  ModuleHandle := LoadLibrary('DUMMY.DLL');
  if ModuleHandle <> 0 then
  begin
    TPExecute  := GetProcAddress(ModuleHandle, 'Execute');
    if (TPExecute <> nil) then
    begin
      @DLLExecute := TPExecute;
      try
        DLLExecute;
      except
        on E: Exception do
        begin
          LStream := TMemoryStream.Create;
          try
            LStream.Write(PChar(E.Message)^, (Length(E.Message) + 1) * SizeOf(Char));
            LStream.Position := 0;
            LStream.Read(LCharArry, LStream.Size);
          finally
            FreeAndNil(LStream);
          end;
        end;
      end;
    end;
    Memo1.Lines.Add(LCharArry);
    FreeLibrary(ModuleHandle);
    Memo1.Lines.Add(LCharArry);
  end;
end;


AAA  2018-09-21 01:48:42  No: 49509

質問の意図が理解出来ていないようで・・・・

>DLL上の例外をコピーした文字列をFreeLibraryにアクセスとエラーになる

質問の内容は言い換えると
変数S が  FreeLibrary 後なぜかアクセスするとエラーになる
FreeLibrary 前にはアクセスしてもエラーにならない

FreeLibraryで変数Sが破壊される理由はなにか?

変数 S の実体が DLL 側にあるような気がするんですが

except 
  on E: Exception do 
  begin 
    S := E.Message; 
  end; 
end; 

でそのようなことが起きるのでしょうか?

>> Memo1.Lines.Add(S);        //エラーになる 
>ではなく,S := E.Message; で例外が発生していることになります. 

ならば ①で表示される事はないと思うのですが?

①     Memo1.Lines.Add(S); 
       FreeLibrary(ModuleHandle); 
②       Memo1.Lines.Add(S);        //エラーになる


Mr.XRAY  2018-09-21 02:11:49  No: 49510

>でそのようなことが起きるのでしょうか? 

申し訳ありませんが,私のスキルでは分かりません.

>ではなく,S := E.Message; で例外が発生していることになります. 

というのは間違いですね.
そのコードによりメモリへの不正なアクセスが生じて,
FreeLibrary(ModuleHandle); が正常に処理できないためと思われますが,
デバッガで追跡したわけではないので詳細は不明です.

メッセージの処理を正常に処理すれば問題ない,ということでご容赦ください.


tor  2018-09-21 02:52:44  No: 49511

> 変数 S の実体が DLL 側にあるような気がするんですが 
その通りのことが起こっているのではないですか?

Delphiの文字列には色々な種類がありますが、その中で最も一般的な「長い文字列」の場合、
  S1 := S2;
のように文字列を代入しても、文字列の中身はコピーされません。
両方が同じメモリ領域を共有するよう、ポインタが設定されます。
(S1もしくはS2の一部だけを書き換えようとすると、その時に初めてコピーが作られて
S1とS2が別々のメモリを指すようになります。Copy on write と呼ばれるテクニックですね。)

ところが、S2がDLL側で作られた文字列だと
exeとdllではメモリマネージャが別々に分かれているので、
S2が破棄されてもexe側ではそれを検知できません。
知らないところでメモリが破棄されて、
共有しているつもりの領域を指すポインタが無効になってしまうわけです。

このようなことを防ぐには、両方で共有メモリマネージャを使うようにしましょう。
ヘルプを "ShareMem" で検索!


Mr.XRAY  2018-09-21 06:41:01  No: 49512

>このようなことを防ぐには、両方で共有メモリマネージャを使うようにしましょう。 

質問された方が知りたいのは,メモリアクセス違反の例外が発生する理由でもなく,
メモリの不正アクセス (メモリアクセス違反の例外) を防ぐ方法でもないようです.

メモリの不正アクセス (メモリアクセス違反の例外が発生する状態) の時の [デバッガ例外通知] ダイアログで 
[継続] ボタンをクリックしてプログラムを継続した時に,

(1)    Memo1.Lines.Add(S); 
       FreeLibrary(ModuleHandle); 
(2)    Memo1.Lines.Add(S);        //エラーになる 

の (1) ではエラーが発生しないのに (2) では何故エラーとなるのかということのようです.


Mr.XRAY  2018-09-21 06:48:43  No: 49513

> の (1) ではエラーが発生しないのに (2) では何故エラーとなるのかということのようです.

ですので,メモリの不正アクセス (メモリアクセスの違反) をしないようにすれば,
そのような現象は発生しない.では納得できないということですね.


AAA  2018-09-21 06:58:42  No: 49514

そんなに質問の内容を理解できていなかった事を指摘されて
悔しいですか?


AAA  2018-09-21 07:18:47  No: 49515

torさん
ありがとうございました

文字列のコピーは実体のポインタのコピーのみとは知りませんでした。

 "ShareMem"はDELPHI以外でのDLL作成もあり得るので使えません。

そうなると例外の捕まえかも考えなければいけない気もしますが

except 
   on E: Exception do 
   begin 
     S := E.Message + 'XXXX'; 
   end; 
 end; 

のようにするとエラーは起きなくなりますね

DLL側の X のポインタと

    X := Exception.Create('XXXXXXXXX');
    X.Message := IntToStr(Integer(X));
    raise X;

EXEC側の E のポインタが

   except
     on E: Exception do
     begin
       Memo1.Lines.Add(IntToStr(Integer(E)));
       S := E.Message + '    ';
    end;
 end;

同じとは思いませんでした。


通りすがり  2018-09-21 15:40:18  No: 49516

いやだからEXEとDLLの境界を跨いで例外を投げんなってことですって。
理由は
> DELPHI以外でのDLL作成もあり得るので使えません。 
です。例外オブジェクトを生成するときに使われるメモリマネージャはDLL側で、except...endの最後で解放するメモリマネージャは
EXE側、なんてあり得ないでしょ?じゃあどうするって?
DLL側のエクスポートされた関数では成功、失敗をクラス型、文字列型ではないいわゆるPODな型で返す(普通はIntegerで、0は失敗、非0は成功)。
文字列を返したいなら、バッファを呼び出し元で確保して、そのポインタと有効な長さを渡し、呼び出し先でそこに文字列を格納する。
要はWin32APIのような作りにする、ということです。


通りすがり  2018-09-21 15:47:57  No: 49517

あと自分の無知と質問の拙さ、他の人の回答を読み解く力のなさを棚に上げて
> そんなに質問の内容を理解できていなかった事を指摘されて 
> 悔しいですか? 
とかいい大人が公開の場で書いちゃうのは恥ずかしいですよ?


通りすがり  2018-09-21 19:22:47  No: 49518

忘れてた。DLLを作るなら最低限でもメモリ管理と呼び出し規約の話は押さえておきましょうね。


au  2018-09-21 23:27:11  No: 49519

質問の本題とは関係ないですが1点
DLLとEXEは別のプロセスではなく
DLLはEXEのプロセスにロードされて同じメモリ空間で実行されるはずです。


AAA  2018-09-22 01:01:03  No: 49520

>あと自分の無知と質問の拙さ、他の人の回答を読み解く力のなさを棚に上げて 
どこを読み解いてませんか?
回答をきちんと読んだので、意図が伝わってないとわかったわけですが

>です。例外オブジェクトを生成するときに使われるメモリマネージャはDLL側で、except...endの最後で解放するメモリマネージャは 
>EXE側、なんてあり得ないでしょ?じゃあどうするって? 
>DLL側のエクスポートされた関数では成功、失敗をクラス型、文字列型ではないいわゆるPODな型で返す(普通はIntegerで、0は失敗、非0は成功)。 
>文字列を返したいなら、バッファを呼び出し元で確保して、そのポインタと有効な長さを渡し、呼び出し先でそこに文字列を格納する。 
>要はWin32APIのような作りにする、ということです。 
当然全部知ってるし メモリマネージャーが EXE と DLL が異なることも知ってる

ただ単に
        on E: Exception do 
           begin 
             S := E.Message; 
           end; 

で文字列コピーしてるのになんでエラーになるのかな?
と思っただけで
そしたらポインタコピーしてるだけだからだったって話で

torさんの回答が全てなんですが・・・


AAA  2018-09-22 01:07:52  No: 49521

Mr.XRAYさんと通りすがりさんの回答を
torさんの回答と比べてみてください

自分たちの答えがいかにずれているか

言ってる事はまちがってないんでしょうけど
質問に対してずれてるんですよ


take  2018-09-22 01:14:46  No: 49522

バグですか?という質問にバグでは無いですと返して原因を説明しているのに
「質問の意図が理解出来ていないようで・・・・ 」
と煽っておられるのですが・・・

最初の質問から  文字列型のメモリ管理方法が知りたかったって読み取るのは
エスパーでも無理じゃ無いでしょうか?

DLLもそうですがサービスとして裏で動作している実行ファイルとかと
データをやりとりするのはかなりハードルが高いので
ここで挙げられている例は、今後役立つものになるかと思われます。


AAA  2018-09-22 02:39:17  No: 49523

>バグですか?という質問にバグでは無いですと返して原因を説明しているのに 
>「質問の意図が理解出来ていないようで・・・・ 」 
>と煽っておられるのですが・・・ 

その原因といっているのがまちがってるじゃないですか
あきらかに

>EXEとDLLをまたいで例外を伝播させるのは不可ですね。 

>StrPCopy(P,E.Message); で文字列が取得できるのは,DLL 内の関数の文字列の引数の扱いと同じですね. 
>DLL が発行するメッセージですから基本的に Null 終端文字列です. 
>また,メモリを仲介しないと正常に取得できません.例外が発生します. 
>つまり, 

この2つ
FreeLibraryの前は取得できてるのにそこ無視してますよね
あきらかにソースみてないですよね

で次に
>そのコードによりメモリへの不正なアクセスが生じて, 
>FreeLibrary(ModuleHandle); 
>が正常に処理できないためと思われますが, 

で torさんが正解を答えてくれたのに

>の (1) ではエラーが発生しないのに (2) では何故エラーとなるのかということのようです. 
煽ってるつもりなのでしょうけど
そうなんですよ  質問の内容 

torさんの答えを見て理解できたのでしょうか?

>ですので,メモリの不正アクセス (メモリアクセスの違反) をしないようにすれば, 
>そのような現象は発生しない.では納得できないということですね. 

でこれですよ

> Memo1.Lines.Add(S);        //エラーになる 

>最初の質問から  文字列型のメモリ管理方法が知りたかったって読み取るのは 
>エスパーでも無理じゃ無いでしょうか? 

メモリの管理方法が知りたかったわけではありませんよ?
結果的に文字列のメモリの管理方法だったというだけで

torさんは理解できたみたいですけど

エスパーはいるみたいです


AAA  2018-09-22 02:39:46  No: 49524

解決にしておきます


某感謝O  2018-09-22 08:37:14  No: 49525

> AAA 2018/09/20(木) 22:18:47 書込者ID:[ 26FSmWST1pEdhB1L06XZlQ ]
> "ShareMem"はDELPHI以外でのDLL作成もあり得るので使えません。 

> AAA 2018/09/21(金) 16:01:03 書込者ID:[ Hyj8kQaJhYqJEk42Q3slGw ]
> 当然全部知ってるし メモリマネージャーが EXE と DLL が異なることも知ってる 

ほんとうに、大丈夫なんでしょうか?

たまたまDelphiで作成したDLLだから、例外キャッチできただけなのに、
Delphi以外で作成されたDLLだと、例外キャッチすらできないんじゃないんでしょうか?
私は、Delphi以外の環境がありませんので、検証はできません。

文字列を変更してしまえば、別アドレスにコピーされることをいいことに、
DLLで発生する例外を、exeでキャッチするソースを残したままにしませんよね?

AAAさんは、知識があって(理由をわかった上で)残したとしても、ほかの方が
そのソースを保守する場面になったとき、

>      FreeLibrary(ModuleHandle); 
>      Memo1.Lines.Add(P); 
のような記述をしない保証はないでしょう。

いや、ただ、心配になっただけなのです。

そして、このスレを流し読みして、

「DLLで発生する例外を、exeでキャッチする」ことが例外なく可能

という、勘違いをする方が現れたりしないことを願います。


通りすがり  2018-09-22 19:47:05  No: 49526

> 当然全部知ってるし メモリマネージャーが EXE と DLL が異なることも知ってる 
のに、
> で文字列コピーしてるのになんでエラーになるのかな? 
> と思っただけで 
> そしたらポインタコピーしてるだけだからだったって話で 
ってんじゃあわかってないですよね。文字列のメモリ管理を理解してないじゃないですか。


AAA  2018-09-23 01:47:32  No: 49527

>> で文字列コピーしてるのになんでエラーになるのかな? 
>> と思っただけで 
>> そしたらポインタコピーしてるだけだからだったって話で 
>ってんじゃあわかってないですよね。文字列のメモリ管理を理解してないじゃないですか。 

>>最初の質問から  文字列型のメモリ管理方法が知りたかったって読み取るのは 
>>エスパーでも無理じゃ無いでしょうか? 

>メモリの管理方法が知りたかったわけではありませんよ? 
>結果的に文字列のメモリの管理方法だったというだけで 

こう書いちゃったんであれですが
これはメモリ管理の方法の話ではないんです

torさんの回答をみればわかりますが


通りすがり  2018-09-23 04:04:40  No: 49528

文字列のCORはメモリ管理の話ですが?


Mr.XRAY  2018-09-23 20:31:50  No: 49529

>DLLとEXEは別のプロセスではなく 
>DLLはEXEのプロセスにロードされて同じメモリ空間で実行されるはずです。

ですね.遅くなりましたが,au さんありがとうございます.
その次の文章も以下のように書き換える必要がありますね.

「DLL の例外等のエラーは,その DLL 内で処理するのが原則です」

オンラインヘルプでは以下のページに記述があります.
( ページ内を「例外」で検索 )

[パッケージと DLL の作成] 
http://docwiki.embarcadero.com/RADStudio/Tokyo/ja/%E3%83%91%E3%83%83%E3%82%B1%E3%83%BC%E3%82%B8%E3%81%A8_DLL_%E3%81%AE%E4%BD%9C%E6%88%90


Mr.XRAY  2018-09-23 23:14:21  No: 49530

一応念のためにお断りしておきますが,上の私の書き込みは,

>そんなに質問の内容を理解できていなかった事を指摘されて 
>悔しいですか? 

だからではありません.
単に私の発言の間違いを訂正したただけず.


通りすがり  2018-09-24 06:00:04  No: 49531

おっと恥ずかしい
× COR
○ COW


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








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