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;
EXEとDLLをまたいで例外を伝播させるのは不可ですね。
(EXEをDelphiで、DLLをVC++で作っているような状況を考えればあたりまえですよね)
そういうことをしたければDLLを実行時パッケージにしましょう。
基本的にDLLのエクスポート関数では例外を全て補足するのが一般的かと。
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;
質問の意図が理解出来ていないようで・・・・
>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); //エラーになる
>でそのようなことが起きるのでしょうか?
申し訳ありませんが,私のスキルでは分かりません.
>ではなく,S := E.Message; で例外が発生していることになります.
というのは間違いですね.
そのコードによりメモリへの不正なアクセスが生じて,
FreeLibrary(ModuleHandle); が正常に処理できないためと思われますが,
デバッガで追跡したわけではないので詳細は不明です.
メッセージの処理を正常に処理すれば問題ない,ということでご容赦ください.
> 変数 S の実体が DLL 側にあるような気がするんですが
その通りのことが起こっているのではないですか?
Delphiの文字列には色々な種類がありますが、その中で最も一般的な「長い文字列」の場合、
S1 := S2;
のように文字列を代入しても、文字列の中身はコピーされません。
両方が同じメモリ領域を共有するよう、ポインタが設定されます。
(S1もしくはS2の一部だけを書き換えようとすると、その時に初めてコピーが作られて
S1とS2が別々のメモリを指すようになります。Copy on write と呼ばれるテクニックですね。)
ところが、S2がDLL側で作られた文字列だと
exeとdllではメモリマネージャが別々に分かれているので、
S2が破棄されてもexe側ではそれを検知できません。
知らないところでメモリが破棄されて、
共有しているつもりの領域を指すポインタが無効になってしまうわけです。
このようなことを防ぐには、両方で共有メモリマネージャを使うようにしましょう。
ヘルプを "ShareMem" で検索!
>このようなことを防ぐには、両方で共有メモリマネージャを使うようにしましょう。
質問された方が知りたいのは,メモリアクセス違反の例外が発生する理由でもなく,
メモリの不正アクセス (メモリアクセス違反の例外) を防ぐ方法でもないようです.
メモリの不正アクセス (メモリアクセス違反の例外が発生する状態) の時の [デバッガ例外通知] ダイアログで
[継続] ボタンをクリックしてプログラムを継続した時に,
(1) Memo1.Lines.Add(S);
FreeLibrary(ModuleHandle);
(2) Memo1.Lines.Add(S); //エラーになる
の (1) ではエラーが発生しないのに (2) では何故エラーとなるのかということのようです.
> の (1) ではエラーが発生しないのに (2) では何故エラーとなるのかということのようです.
ですので,メモリの不正アクセス (メモリアクセスの違反) をしないようにすれば,
そのような現象は発生しない.では納得できないということですね.
そんなに質問の内容を理解できていなかった事を指摘されて
悔しいですか?
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;
同じとは思いませんでした。
いやだからEXEとDLLの境界を跨いで例外を投げんなってことですって。
理由は
> DELPHI以外でのDLL作成もあり得るので使えません。
です。例外オブジェクトを生成するときに使われるメモリマネージャはDLL側で、except...endの最後で解放するメモリマネージャは
EXE側、なんてあり得ないでしょ?じゃあどうするって?
DLL側のエクスポートされた関数では成功、失敗をクラス型、文字列型ではないいわゆるPODな型で返す(普通はIntegerで、0は失敗、非0は成功)。
文字列を返したいなら、バッファを呼び出し元で確保して、そのポインタと有効な長さを渡し、呼び出し先でそこに文字列を格納する。
要はWin32APIのような作りにする、ということです。
あと自分の無知と質問の拙さ、他の人の回答を読み解く力のなさを棚に上げて
> そんなに質問の内容を理解できていなかった事を指摘されて
> 悔しいですか?
とかいい大人が公開の場で書いちゃうのは恥ずかしいですよ?
忘れてた。DLLを作るなら最低限でもメモリ管理と呼び出し規約の話は押さえておきましょうね。
質問の本題とは関係ないですが1点
DLLとEXEは別のプロセスではなく
DLLはEXEのプロセスにロードされて同じメモリ空間で実行されるはずです。
>あと自分の無知と質問の拙さ、他の人の回答を読み解く力のなさを棚に上げて
どこを読み解いてませんか?
回答をきちんと読んだので、意図が伝わってないとわかったわけですが
>です。例外オブジェクトを生成するときに使われるメモリマネージャはDLL側で、except...endの最後で解放するメモリマネージャは
>EXE側、なんてあり得ないでしょ?じゃあどうするって?
>DLL側のエクスポートされた関数では成功、失敗をクラス型、文字列型ではないいわゆるPODな型で返す(普通はIntegerで、0は失敗、非0は成功)。
>文字列を返したいなら、バッファを呼び出し元で確保して、そのポインタと有効な長さを渡し、呼び出し先でそこに文字列を格納する。
>要はWin32APIのような作りにする、ということです。
当然全部知ってるし メモリマネージャーが EXE と DLL が異なることも知ってる
ただ単に
on E: Exception do
begin
S := E.Message;
end;
で文字列コピーしてるのになんでエラーになるのかな?
と思っただけで
そしたらポインタコピーしてるだけだからだったって話で
torさんの回答が全てなんですが・・・
Mr.XRAYさんと通りすがりさんの回答を
torさんの回答と比べてみてください
自分たちの答えがいかにずれているか
言ってる事はまちがってないんでしょうけど
質問に対してずれてるんですよ
バグですか?という質問にバグでは無いですと返して原因を説明しているのに
「質問の意図が理解出来ていないようで・・・・ 」
と煽っておられるのですが・・・
最初の質問から 文字列型のメモリ管理方法が知りたかったって読み取るのは
エスパーでも無理じゃ無いでしょうか?
DLLもそうですがサービスとして裏で動作している実行ファイルとかと
データをやりとりするのはかなりハードルが高いので
ここで挙げられている例は、今後役立つものになるかと思われます。
>バグですか?という質問にバグでは無いですと返して原因を説明しているのに
>「質問の意図が理解出来ていないようで・・・・ 」
>と煽っておられるのですが・・・
その原因といっているのがまちがってるじゃないですか
あきらかに
>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/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でキャッチする」ことが例外なく可能
という、勘違いをする方が現れたりしないことを願います。
> 当然全部知ってるし メモリマネージャーが EXE と DLL が異なることも知ってる
のに、
> で文字列コピーしてるのになんでエラーになるのかな?
> と思っただけで
> そしたらポインタコピーしてるだけだからだったって話で
ってんじゃあわかってないですよね。文字列のメモリ管理を理解してないじゃないですか。
>> で文字列コピーしてるのになんでエラーになるのかな?
>> と思っただけで
>> そしたらポインタコピーしてるだけだからだったって話で
>ってんじゃあわかってないですよね。文字列のメモリ管理を理解してないじゃないですか。
>>最初の質問から 文字列型のメモリ管理方法が知りたかったって読み取るのは
>>エスパーでも無理じゃ無いでしょうか?
>メモリの管理方法が知りたかったわけではありませんよ?
>結果的に文字列のメモリの管理方法だったというだけで
こう書いちゃったんであれですが
これはメモリ管理の方法の話ではないんです
torさんの回答をみればわかりますが
文字列のCORはメモリ管理の話ですが?
>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
一応念のためにお断りしておきますが,上の私の書き込みは,
>そんなに質問の内容を理解できていなかった事を指摘されて
>悔しいですか?
だからではありません.
単に私の発言の間違いを訂正したただけず.
おっと恥ずかしい
× COR
○ COW
ツイート | ![]() |