喫茶XRAYトップページ


[12322] DLL内の関数で発生した例外の捕捉返信 削除
2018/9/22 (土) 07:37:53 Mr.XRAY
[DLL上の例外をコピーした文字列をFreeLibraryにアクセスとエラーになる]
http://madia.world.coocan.jp/cgi-bin/DelphiBBS/wwwlng.cgi?print+201809/18090005.txt

の関連ですが,taka さんが Lazarus でテストしたというので,
C++ で DLL を作成してテストしてみました.
DLL の作成には Visual C++ 2013 を使用しました.
Delphi では .dpr ファイルを 1 つ提供すれば,それをコンパイルして DLL が作成
できますが,VC++ ではそうはいかないので DLL を添付しています.
ランタイムライブラリもリンクしていますから,VC++ がインストールされていない
環境でもテスト可能です.以下は TestDll.cpp のコードです.

添付の TestDll.dll は y = 4; の例外発生なし,
TestDll_div0.dll は y = 0; の例外発生ありです.
 

#include "stdafx.h"

#ifdef _MANAGED
#pragma managed(push, off)
#endif

BOOL APIENTRY DllMain(HMODULE hModule,
        DWORD  ul_reason_for_call,
        LPVOID lpReserved
        )
{
        return TRUE;
}

EXPORT int TestFunc()
{
        int x;
        int y;

        x = 100;
        y = 0;
        return  x / y;
}

#ifdef _MANAGED
#pragma managed(pop)
#endif


DLL を使用する Delphi のテスト用コードです.
IDE から実行するとメモリアクセス違反の例外が発生しますが,
エラーの文字列は表示されます.
EXE 単独実行の場合は例外のダイアログは現れません.

動作確認は Windows 7 U64(SP1) + Delphi XE5(UP2) Pro です.
Ansi 版 (非ユニコード版) の Delphi でコンパイルすると表示文字が化けます.

procedure TForm1.Button1Click(Sender: TObject);
var
  DLLExecute   : function : Integer; stdcall;
  ModuleHandle : HModule;
  TPExecute    : TFarProc;
  LInt         : Integer;
  LErrStr      : string;
begin
  Memo1.Lines.Clear;

  ModuleHandle := LoadLibrary('TestDll_div0.DLL');    // 0除算用DLLのロード
//  ModuleHandle := LoadLibrary('TestDll.DLL');
  if ModuleHandle <> 0 then
  begin
    TPExecute  := GetProcAddress(ModuleHandle, 'TestFunc');
    if (TPExecute <> nil) then
    begin
      @DLLExecute := TPExecute;
      try
        LInt := DLLExecute;
        Memo1.Lines.Add(IntToStr(LInt));
      except
        on E: Exception do
        begin
          LErrStr := PChar(E.Message);
        end;
      end;
    end;
    Memo1.Lines.Add(LErrStr);
    FreeLibrary(ModuleHandle);
    Memo1.Lines.Add(LErrStr);
  end;
end;


http://mrxray.on.coocan.jp/Delphi/zip/QandA_20180920_dllException.zip




[12324] Re:DLL内の関数で発生した例外の捕捉返信 削除
2018/9/22 (土) 16:48:34 taka
▼ Mr.XRAYさん
>           LErrStr := PChar(E.Message);

PChar(E.Message);
だと
コンパイラの最適化方法に依存するので
LErrStr := E.Message;
などの代入後に
UniqueString(LErrStr);
をいれて確実に参照カウンタ1のユニークなアドレスを持つようにする必要があるとわかりました。

http://docwiki.embarcadero.com/RADStudio/Berlin/ja/%E6%96%87%E5%AD%97%E5%88%97%E5%9E%8B%EF%BC%88Delphi%EF%BC%89
文字列型(Delphi)
> 文字列が固有であることを保証するには、
> SetLength、SetString
> または UniqueString の各手続きを呼び出します。


LErrStr := E.Message;
UniqueString(LErrStr);



[12326] Re2:DLL内の関数で発生した例外の捕捉返信 削除
2018/9/22 (土) 16:56:33 Mr.XRAY
▼ takaさん

> PChar(E.Message);
> だと
> コンパイラの最適化方法に依存するので
> LErrStr := E.Message;
> などの代入後に
> UniqueString(LErrStr);
> をいれて確実に参照カウンタ1のユニークなアドレスを持つようにする必要があるとわかりました。
>
> http://docwiki.embarcadero.com/RADStudio/Berlin/ja/%E6%96%87%E5%AD%97%E5%88%97%E5%9E%8B%EF%BC%88Delphi%EF%BC%89


おおっ〜 !!
知りませんでした.いやはや.ありがとうございます.
早速コード修正して再 UP します.


[12329] UniqueString返信 削除
2018/9/22 (土) 17:25:21 Mr.XRAY
> UniqueString(LErrStr);

なんでもやってみると,ということでやってみました.
使用した DLL は以下の質問者が提示したコードです.

[DLL上の例外をコピーした文字列をFreeLibraryにアクセスとエラーになる]
http://madia.world.coocan.jp/cgi-bin/DelphiBBS/wwwlng.cgi?print+201809/18090005.txt

DLL を使用する Delphi のコードです.
UniqueString を使用しない場合です.

procedure TForm1.Button1Click(Sender: TObject);
var
  DLLExecute   : function : Integer; stdcall;
  ModuleHandle : HModule;
  TPExecute    : TFarProc;
  S            : string;
  LInt         : Integer;
begin
    ModuleHandle := LoadLibrary('DUMMY.DLL');
    if ModuleHandle <> 0 then
    begin
      TPExecute  := GetProcAddress(ModuleHandle, 'Execute');
      if (TPExecute <> nil) then
      begin
        @DLLExecute := TPExecute;
        try
          LInt := DLLExecute;
        except
          on E: Exception do
          begin
            S := E.Message;
          end;
        end;
      end;
      Memo1.Lines.Add(S);
      // 以下のコードが実行されないので,メモリリークが発生する
      FreeLibrary(ModuleHandle);
      Memo1.Lines.Add(S);
    end;
end;

デバッガ例外通知で [継続] をクリックすると何回かデバッガ例外通知となります.
最後に下図のように表示されます.
動作確認は Windows 7 U64(SP1) + Delphi XE5(UP2) Pro VCL-32 です.


[12330] Re:UniqueString返信 削除
2018/9/22 (土) 17:30:35 Mr.XRAY
続いて UniqueString を使用する場合です.

procedure TForm1.Button1Click(Sender: TObject);
var
  DLLExecute   : function : Integer; stdcall;
  ModuleHandle : HModule;
  TPExecute    : TFarProc;
  S            : string;
  LInt         : Integer;
begin
    ModuleHandle := LoadLibrary('DUMMY.DLL');
    if ModuleHandle <> 0 then
    begin
      TPExecute  := GetProcAddress(ModuleHandle, 'Execute');
      if (TPExecute <> nil) then
      begin
        @DLLExecute := TPExecute;
        try
          LInt := DLLExecute;
        except
          on E: Exception do
          begin
            S := E.Message;
            UniqueString(S);
          end;
        end;
      end;
      Memo1.Lines.Add(S);
      // 以下が実行されるのでメモリリークは発生しない
      FreeLibrary(ModuleHandle);
      Memo1.Lines.Add(S);
    end;
end;

デバッガ例外通知で [継続] をクリックすると下図のように表示されます.
例外通知は 1 回だけです.
EXE の直接起動では例外通知は表示されません.
動作確認は Windows 7 U64(SP1) + DelphiXE5(UP2) Pro VCL-32 です.


[12331] 一連のテストの注意返信 削除
2018/9/22 (土) 17:33:37 Mr.XRAY
テストプロジェクトをダウンロード可能にして,更にテストをしていますが,
そのようなことをしてもいいということではありません.
あくまでも,興味としてのテストです.


[12342] Re:一連のテストの注意返信 削除
2018/9/23 (日) 11:40:18 Mr.XRAY
> そのようなことをしてもいいということではありません.
> あくまでも,興味としてのテストです.


もし,某掲示板の質問者が作成した DLL を使用しなければならなくなった時,
その DLL 内での例外を捕捉できるか ?
という課題とも言えます.


[12327] Re2:DLL内の関数で発生した例外の捕捉返信 削除
2018/9/22 (土) 17:05:29 Mr.XRAY
▼ takaさん

> http://docwiki.embarcadero.com/RADStudio/Berlin/ja/%E6%96%87%E5%AD%97%E5%88%97%E5%9E%8B%EF%BC%88Delphi%EF%BC%89

恥ずかしながらこのページをまともに読んだことがありませんでした.
貴重なことが書いてありますね.


[12328] Re3:DLL内の関数で発生した例外の捕捉返信 削除
2018/9/22 (土) 17:25:11 taka
▼ Mr.XRAYさん
> ▼ takaさん
>
> > http://docwiki.embarcadero.com/RADStudio/Berlin/ja/%E6%96%87%E5%AD%97%E5%88%97%E5%9E%8B%EF%BC%88Delphi%EF%BC%89
>
> 恥ずかしながらこのページをまともに読んだことがありませんでした.
> 貴重なことが書いてありますね.


いまUnicodeでテストすると

UniqueString(LErrStr);
でアクセス違反になってしまいました。
うーん。。。

string型にはつかえないみたい

Win32
 procedure UniqueString(var str: string); overload;
 procedure UniqueString(var str: WideString); overload;

Unicode
 procedure UniqueString(var str: UnicodeString); overload;
 procedure UniqueString(var str: _WideStr);
 procedure UniqueString(var str: _AnsiStr);

対象の型が違うみたいですね

StrUtils.LeftStr(E.Message, Length(E.Message));
のほうがいいのかな・・・


[12332] Re4:DLL内の関数で発生した例外の捕捉返信 削除
2018/9/22 (土) 17:41:29 Mr.XRAY
▼ takaさん

> UniqueString(LErrStr);
> でアクセス違反になってしまいました。


このアクセス違反のダイアログの表示は 1 回だけのようです.
EXE を直接起動すると表示されませんでした.

デバッガで追跡したわけではないのですか,
IDE から実行すると DLL がメモリにロードされます
すると,デバッガの機能が働いて,DLL 側の (メモリ上の) エラーを検出するのでは
と思ってします.この DLL 側のアクセス違反は try - except で捕捉できないのではと.


[12333] Re5:DLL内の関数で発生した例外の捕捉返信 削除
2018/9/22 (土) 17:48:38 taka
▼ Mr.XRAYさん
> ▼ takaさん
>
> > UniqueString(LErrStr);
> > でアクセス違反になってしまいました。
>
> このアクセス違反のダイアログの表示は 1 回だけのようです.
> EXE を直接起動すると表示されませんでした.
>
> デバッガで追跡したわけではないのですか,
> IDE から実行すると DLL がメモリにロードされます
> すると,デバッガの機能が働いて,DLL 側の (メモリ上の) エラーを検出するのでは
> と思ってします.この DLL 側のアクセス違反は try - except で捕捉できないのではと.


dll2個と関数2個の4回チェックしていたので
コードを1回チェックに修正したらでなくなりました。
落ちていた原因がUniqueStringではなかったみたいです。
違うところで落ちてたみたいです。


     if (PChar(LErrStr) = PChar(E.Message)) then
         Memo1.Lines.Add(Format('%d %d', [Integer(PChar(LErrStr)),Integer(PChar(E.Message))]));
     UniqueString(LErrStr);

    Memo1.Lines.Add(Format('%d %s', [Integer(PChar(LErrStr)), LErrStr]));

を別々のところに差し込むとアドレスが変更されたのがわかります。


[12335] Re6:DLL内の関数で発生した例外の捕捉返信 削除
2018/9/22 (土) 17:56:23 Mr.XRAY
▼ takaさん

> コードを1回チェックに修正したらでなくなりました。
> 落ちていた原因がUniqueStringではなかったみたいです。
> 違うところで落ちてたみたいです。


ゴメンナさい.私のミスです.こちらでも例外発生しなくなりました.
プロジェクトのビルド (再構築) を忘れていました (汗)

>
>      if (PChar(LErrStr) = PChar(E.Message)) then
>          Memo1.Lines.Add(Format('%d %d', [Integer(PChar(LErrStr)),Integer(PChar(E.Message))]));
>      UniqueString(LErrStr);
>
>     Memo1.Lines.Add(Format('%d %s', [Integer(PChar(LErrStr)), LErrStr]));
>
> を別々のところに差し込むとアドレスが変更されたのがわかります。


なるほど,こういうコードにすればいいですね.


[12340] Re7:DLL内の関数で発生した例外の捕捉返信 削除
2018/9/22 (土) 18:21:43 taka
▼ Mr.XRAYさん
> ▼ takaさん
>
> > コードを1回チェックに修正したらでなくなりました。
> > 落ちていた原因がUniqueStringではなかったみたいです。
> > 違うところで落ちてたみたいです。
>


エラーになった原因は、 win32版のDLLを unicodeのexeに読み込んだため
例外時の内部的な文字列処理関係で アクセス違反になったようです。

いまのところ
 E.Messageをローカル変数代入で
  0による除算 では 読み込み違反は発生しない。
  StrToInt('NG')では、読み込み違反が発生する。
という現象があるのがわかっているので
システム関係のエラーはグローバル的な文字列定数から引っ張ってきているのかな?
と思って調べていますが わかりません。



[12336] Re6:DLL内の関数で発生した例外の捕捉返信 削除
2018/9/22 (土) 17:59:19 SO
▼ takaさん

UniqueStringって、某掲示板での「Copy on write」を強制させるやつなんですね。


[12338] Re7:DLL内の関数で発生した例外の捕捉返信 削除
2018/9/22 (土) 18:12:13 taka
▼ SOさん
> ▼ takaさん
>
> UniqueStringって、某掲示板での「Copy on write」を強制させるやつなんですね。


私も さっきまで この関数の存在をしりませんでした。(笑

[12341] Visual C++のランタイムライブラリ返信 削除
2018/9/22 (土) 19:09:35 Mr.XRAY
> ランタイムライブラリもリンクしていますから,VC++ がインストールされていない
> 環境でもテスト可能です.以下は TestDll.cpp のコードです.


ランタイムライブラリを DLL に含めていますから,DLL のサイズは,
Delphi で作成する DLL のように大きくなります.
ランタイムライブラリなしだと 30 KB 程度ですが,含めると 180 KB 程度です.

最近 EXE のサイズのことを話題にしたので参考として.

Visual C++ 2013 で DLL を作成する場合,デフォルトではランタイムライブラリを
DLL に含めない設定になっています.
ランタイムライブラリを含めないと,その DLL を利用する際,
その DLL を作成した同じバージョンの VC++ のランタイムライブラリが必要です.


INCM/CMT
Cyclamen v3.82