以前の受信したデータをファイル化するには?の続きです。
受信したデータ(POP3)のファイル化まではうまくいったのですが
現在のコードだと受信メニューをクリックすると最初に受信した
ファイルが何個も出来てしまうという事が発生してしまいます。
前回の中でにしのさんが言っていたUIDが一緒のメールをサーバから
落とさなくすることなどを実装したいのですが受信するコードのどこに
どのようなコードを書くと良いのでしょうか?
またリストビューをクリックしたらファイル化したデータを読み込む
ように直したいのですがどう直せばよろしいでしょうか?
リストの読み込みのコードは以下のとおりです。
procedure TMainForm.MailListViewClick(Sender: TObject);
var
Subject: array [0..HEADER_MAX] of Char;
Date: array [0..HEADER_MAX] of Char;
From: array [0..HEADER_MAX] of Char;
Header: array [0..HEADER_MAX] of Char;
Body: PChar;
Size: Integer;
begin
mSave.Enabled := False;
mReply.Enabled := False;
mDelete.Enabled := False;
mPopupReply.Enabled := False;
mPopupDelete.Enabled := False;
ToolButton3.Enabled := False;
ToolButton5.Enabled := False;
with MailListView do
begin
// 選択されているか?
if SelCount > 0 then
begin
// 見てわかりますが、毎回受信のたびに接続からやってます。
// 本来であれば ID やパスワードを入力後、一度に受信し、
// サーバー上から受信したメールを削除し、ファイル等に
// 保存しておき、題名一覧クリックでは、保存してあるメール
// を読み込む処理が入るべきでしょう。
// POP3 サーバーに接続
S := NMailPop3Connect(PChar(Pop3Name));
if S <> INVALID_SOCKET then
begin
// 選択されている番号が、メールの数より少ないか?
if Selected.Index < NMailPop3Authenticate(S, PChar(Id), PChar(Password), ApopFlag) then
begin
// メールのサイズを得て、メモリを確保
Size := NMailPop3GetMailSize(S, Selected.Index + 1);
GetMem(Body, Size);
// メールを読み込む
NMailPop3GetMail(S, Selected.Index + 1, subject, date, from, header, Body, nil, nil);
// 表示用の Memo をクリアして受信内容を追加。
BodyMemo.Lines.Clear;
BodyMemo.Lines.Add(Strpas(Body));
// カーソルを先頭に移動
BodyMemo.SelStart := 0;
BodyMemo.SelLength := 0;
// 確保したメモリを開放
FreeMem(Body);
end;
end;
// 接続終了
NMailPop3Close(S);
S := INVALID_SOCKET;
mSave.Enabled := True;
mReply.Enabled := True;
mDelete.Enabled := True;
mPopupReply.Enabled := True;
mPopupDelete.Enabled := True;
ToolButton3.Enabled := True;
ToolButton5.Enabled := True;
end;
end;
end;
まずは、詳細設計をされてはどうですか?
このままだと、ずるずる聞いて書くだけになってしまいますよ。
小規模ならば問題ないかもしれませんが、メールソフトとなると、中規模だと思います。
ある程度、設計してからプログラミングしないと、後で行き詰まります。
# 最悪、作り直す羽目になります
UIDLに関しては、前に書いたとおりです。
保存したメールのUIDLは、別途保存する必要があります。
保存したUIDLに、現在読み込もうとするメールのUIDLが存在するか見て、存在すればメールを読み飛ばす、とするだけです。
APIはNMailPop3GetUidlですね。
前に書いたものと同じであれば、選択されたListItemのDataにファイル名があるので、それを読み込みます。
# 前回の中に間違いがありました。ヘッダと本文の間に空行がありませんでした。追加した方が便利です
ヘッダや本文はすでにSJIS化されているので、文字コードの心配はなさそうです。
変換に関しては、
http://www02.so-net.ne.jp/~hat/mailer/rfc.ja.html
このあたりを参考にしてください。
詳細仕様はまとまってプログラムをしていて、そこで分からないので
質問していると言うところです。特にファイル化の部分と
そのファイル化したデータの読み込み部分が分からないと言うのが
この質問だったりします。
NMailPop3GetUidlで取得しようとしたのですが
またEAccsessViolationクラスの例外生成がされてしまいました。
コードとしては以下のようなものを考えてみたのですが・・・
try
NMailPop3GetUidl(S, NMAIL_GET_UIDL_ALL, strUIDL, StrLen(strUIDL));
UIDLFs := TFileStream.Create('uidu.lst', fmCreate);
UDILFs.WriteBuffer(strUDIL^, StrLen(Header));
finally
if UDILFs <> nil then UDILFs.Free;
end;
ちょっと変数名をミスしてしまいました。
> 詳細仕様はまとまってプログラムをしていて、そこで分からないので
> 質問していると言うところです。特にファイル化の部分と
> そのファイル化したデータの読み込み部分が分からないと言うのが
> この質問だったりします。
それは失礼しました。
前回、今回が繋がっているように見えたので。
早計でした。すみません。
いま環境がないので帰ってから細かいことは書きますが、
strUIDLの型はPCharですよね。Stringでなくarray of charかPCharです。
メモリの確保もしてありますか。
GetMailの手前でGetUidlしてますよね。
# NMailPop3Authenticateした直後、ループの手前です
この時点では、ファイルにせずに、メモリ上に保持し、前回保存してあるUIDLと比較して、前回保存していないUIDLだけとっておきます。
それから(ここからがループ内部)、1つずつGetUidlして、前回保存していないUIDLだったら、メールを取得するようにします。
(ループを抜けた後)全メールを取得し終えたら、今回のUIDLを、前回保存したUIDLに追加します。
この場合は、
var
strUIDL: array [0..HEADER_MAX] of Char;
UIDLFs: TFileStream;
としておいて、
UIDLFs := nil;
try
NMailPop3GetUidl(S, NMAIL_GET_UIDL_ALL, strUIDL, Length(strUIDL));
UIDLFs := TFileStream.Create('uidu.lst', fmCreate);
UIDLFs.WriteBuffer(strUIDL[0], StrLen(strUIDL));
finally
if UIDLFs <> nil then UIDLFs.Free;
end;
こんな形になります。
strUIDLの長さをHEADER_MAX(8KB)としていますが、メールがたまった状態ではこれ以上になる可能性もあります。
APIには、UIDLの大きさを知る方法はないので、1通ずつのUIDLを取るか、あらかじめ大きく取って、それ以上を仕様とする必要が出てきます。
ファイルにしないのであれば、strUIDLに入れた時点で、TStringListなどに入れて管理すれば楽です。
今このコードを入力してみたのですが、生成されたuidu.lstは0バイトで
何も入っていませんでした。strUIDL[0]^ではないのでしょうか?
また過去のと比較する場合は問ういう風に行えばよろしいでしょうか?
実際はVC++/SDKで作っていたメーラなのですが同じ部分で躓いてしまい
新しくDelphiと言う言語を選んでメーラを作ろうと思っていたので
機能仕様などはある程度完成していました。
今デバッグしながらファイルを見ていたのですが、UIDLと関係のないような
バイナリのようなNULL文字が出力されるファイルになっていました。
また^でやってみたらコンパイラに止められました。
strUIDLを、静的に確保してあれば、
Length(strUIDL)
で問題ないです。
動的に確保したのであれば、Length(strUIDL)を、確保したサイズにしてください。
UIDLFs.WriteBuffer(strUIDL[0], StrLen(strUIDL));
の部分も、静的に確保した場合です。
動的に確保したのであれば、
UIDLFs.WriteBuffer(strUIDL^, StrLen(strUIDL));
になります。
ごめんなさい、上の書込みですがちゃんとリストになって出力されました。(>_<)
過去のUIDLのファイルと新しく取得したUIDL(引き込んでメモリ上に展開した方が良い?)を
比べ、該当するメールは受信しないようにするにはどのようにすれば良いでしょうか?
10時の書き込みの状態からぜんぜん進めません(>_<)
ちなみに私は仕様設計が出来ていても分かるプログラム以外は
結構苦手だったりするので人に聞いてしまいがちです。(T_T)
前の質問に書いてあったものの一部です。
==ここ
・サーバにあるメッセージの全てのUIDLを取得
・現在ローカルに保存してあるメッセージに、サーバ側メッセージのUIDLがあれば、ダウンロードしないようにするチェック
・サーバにあるメッセージで、ローカルにないメッセージを1つずつ読み込む
==ここ
・サーバ側メッセージを削除する設定なら、メッセージを削除
→これは以下のコードで一応解決しています。
// サーバにメールを残すのフラグがFalseだったらメールを削除
if DeleteFlag = False then NMailPop3DeleteMail(S, No);
どうやろうとして、どうできなかったんでしょうか。
前回までの所でUIDLのリストを保存しましたが、UIDLを比較するには
メモリ上に新しいUIDLを取り込んで過去に取り込んだUIDLのリストと
比べなくてはならないのですが、その部分でどうやって一時的に
メモリに新しいUIDLを引き込んで過去のUIDLのリストと比べて
同じメールは取り込まずに新規メールだけを取り込んで、その上
UIDLのリストを更新すれば良いのかが分からないんです。
一度同じ位置付近にTMemoryStreamを使いメモリ上に新しい
UIDLを展開しようと思ったのですが失敗してしまいました。(>_<)
ちなみに考えてみたリストです。
var
// 追加変数
strMUIDL: array [0..HEADER_MAX] of Char; // メモリ用UIDL引き込み変数
UIDLMs: TMemoryStream; // メモリストリーム
// メモリ上に新しいUIDLを読み込む
// 現在UIDL.lstを生成している所に記述?
NMailPop3GetUidl(S, NMAIL_GET_UIDL_ALL, strMUIDL, Length(strMUIDL));
UIDLMs := TMemoryStream.Create;
UIDLMs.WriteBuffer(strMUIDL, Length(strMUIDL));
// メモリ上の新しいUIDLと過去のUIDL.lstと比較して古いメールは
// POP3サーバから読み込まない処理が入る(分からないため×)
// ループ内記述?
// メール受信終了後メモリ上のUIDLをUIDL.lstに書き込む
// UIDL.lstはここで生成する?
UIDLFs := nil;
UIDLFs := TFileStream.Create('uidl.lst', fmCreate); // uidl.lstを開く
try
UIDLFs.WriteBuffer(strMUIDL, Length(strMUIDL)); // リストの更新
finally
if UIDLFs <> nil then UIDLFs.Free; // ファイルの解放
end;
保存したUIDLは、TStringListで読み込むと楽です。
savedUidl := TStringList.Create;
try
savedUidl.LoadFromFile('uidl.lst');
except
;
end;
これだけで読み込めます。あとは、savedUidl[0]で1行目が読めます。
使い方はヘルプを見てください。
NMailPop3GetUidlしたあとのデータも、同じようにTStringListに入れてしまいます。
nowUidl := TStringList.Create;
nowUidl.Text := strMUIDL;
これだけです。
あとは、savedUidlの各UIDLと、nowUidlの各UIDLを比較して、同じ値があれば、nowUidlの方を消します。
最後に、savedUidlに、nowUidlを追加して保存します。
このような感じに記述するのでしょうか?
UIDL.lstの読み込みに失敗(例外が発生)した時は何も入れない方が
良いのでしょうか?
また比較はどの辺りで行えばよろしいのでしょうか?
procedure TMainForm.AddMailList(S: TSocket);
var
No, Count: integer;
Subject: array [0..HEADER_MAX] of Char;
Date: array [0..HEADER_MAX] of Char;
From: array [0..HEADER_MAX] of Char;
Header: PChar;
I: TListItem;
DataName: String; // NMLファイルのファイル名
Seq: Integer; // シーケンス番号
Body: PChar; // Bodyのデータ用
BodySize: Integer; // Bodyのファイルサイズ
Fs: TFileStream; // ファイルストリーム
strDate: String; // 日付の取得
strUIDL: array [0..HEADER_MAX] of Char; // UIDLのリスト
UIDLFs: TFileStream; // ファイルストリーム
strMUIDL: array [0..HEADER_MAX] of Char; // メモリ用のリスト格納
UIDLMs: TMemoryStream; // メモリストリーム
SavedUIDL: TStringList; // 保存したUIDLの格納
NowUIDL: TStringList; // メモリ側の新しいUIDLの格納
begin
// メール本文表示をクリア
BodyMemo.Lines.Clear;
// ListView もクリア
HeaderDataClear;
MailListView.Items.Clear;
// POP3 サーバに接続する
S := NMailPop3Connect(PChar(Pop3Name));
if S <> INVALID_SOCKET then
begin
if (Id = '') or (Password = '') then
begin
// ID とパスワード入力
if InputForm.ShowModal = mrOk then
begin
Id := InputForm.IdEdit.Text;
Password := InputForm.PasswordEdit.Text;
end;
end;
// メールの数を得る
No := NMailPop3Authenticate(S, PChar(Id), PChar(Password), ApopFlag);
// 保存したUIDLをTStringListに格納する
SavedUIDL := TStringList.Create;
try
// 保存したUIDL.lstをSavedUIDLに格納
SavedUIDL.LoadFromFile('uidl.lst');
except
// ファイルが読み込めなかった場合はUIDL.lstを作成
UIDLFs := nil;
try
NMailPop3GetUidl(S, NMAIL_GET_UIDL_ALL, strUIDL, Length(strUIDL));
UIDLFs := TFileStream.Create('uidu.lst', fmCreate);
UIDLFs.WriteBuffer(strUIDL[0], StrLen(strUIDL));
finally
if UIDLFs <> nil then UIDLFs.Free;
end;
SavedUIDL.LoadFromFile('uidl.lst');
end;
if No >= 0 then
begin
for Count := 1 to No do
begin
// 順番にヘッダの内容を読み、ListView に追加
GetMem(Header, HEADER_MAX);
NMailPop3GetMailStatus(S, Count, Subject, Date, From, Header, False);
strDate := StrPas(Date);
DataName := 'NML' + Copy(strDate, 1, 4) // yyyy
+ Copy(strDate, 6, 2) // mm
+ Copy(strDate, 9, 2) // dd
+ Copy(strDate, 12, 2) // hh
+ Copy(strDate, 15, 2) // mm
+ Copy(strDate, 18, 2); // ss
Seq := 0;
while FileExists(RcvFolder + 'RcvMail\' + DataName + '_' + IntToStr(Seq) + '.nml') do Inc(Seq);
try
BodySize := NMailPop3GetMailSize(S, Count);
GetMem(Body, BodySize);
NMailPop3GetMail(S, Count, Subject, Date, From, Header, Body, nil, nil);
Fs := TFileStream.Create(RcvFolder + 'RcvMail\' + DataName + '_' + IntToStr(Seq) + '.nml', fmCreate);
Fs.WriteBuffer(Header^, StrLen(Header));
Fs.WriteBuffer(#13#10, StrLen(#13#10));
Fs.WriteBuffer(Body^, StrLen(Body));
finally
I := MailListView.Items.Add;
I.Caption := StrPas(Subject);
I.SubItems.Add(StrPas(Date));
I.SubItems.Add(StrPas(From));
if DeleteFlag = False then NMailPop3DeleteMail(S, No);
if Fs <> nil then Fs.Free;
if Body <> nil then FreeMem(Body);
end;
end;
TreeView1.Select(TreeView1.Items.Item[1]);
TreeView1.SetFocus;
end
else
begin
// サーバーからのエラーメッセージを取得
NMailGetMessage(NMAIL_GET_ERROR_MESSAGE, Subject, HEADER_MAX);
MessageDlg('ログインエラー:' + StrPas(Subject), mtError, [mbOK], 0);
end;
NMailPop3Close(S);
S := INVALID_SOCKET;
end;
end;
全コード載せてしまってごめんなさいです。
> UIDL.lstの読み込みに失敗(例外が発生)した時は何も入れない方が
> 良いのでしょうか?
初期状態では、uidl.lstがないはずなので、読み込み時に例外が発生します。
でもこれは失敗とは違いますよね。
だから例外処理をとばす意味で、tryでくくっています。
noには、現在サーバにあるメッセージ数が帰ってきているはずなので、これら一連の操作を、
if No >= 0 then
の後に入れた方がいいかもしれません。
何かしらエラーが発生してログインできなかったときにも、UIDLを取得しに移行としてしまいますから。
あとは、savedUidlとnowUidlが揃ったら、すぐにそれを比較します。
ループの中で、残ったnowUidlを見ながら、メッセージを取得します。
nowUidlには、
'No Uidl'
という形で、格納されていますから、このNoをメッセージ番号として、メッセージを読み込めばいいのです。
と言うことはexcept分岐の部分には何も記述しなくて良いのですね?
それとMemoryStreamにもファイル名をつけなくてはならないのでしょうか?
それと比較はこんな感じに行ってOKなのでしょうか?
// ループ内で行う?
if SaveUidl[No] not NowUidl[No] then
begin
// メールを取り込みnmlファイルに保存する処理
// 順番にヘッダの内容を読み、ListView に追加
GetMem(Header, HEADER_MAX);
NMailPop3GetMailStatus(S, Count, Subject, Date, From, Header, False);
strDate := StrPas(Date);
DataName := 'NML' + Copy(strDate, 1, 4) // yyyy
+ Copy(strDate, 6, 2) // mm
+ Copy(strDate, 9, 2) // dd
+ Copy(strDate, 12, 2) // hh
+ Copy(strDate, 15, 2) // mm
+ Copy(strDate, 18, 2); // ss
Seq := 0;
while FileExists(RcvFolder + 'RcvMail\' + DataName + '_' + IntToStr(Seq) + '.nml') do Inc(Seq);
try
BodySize := NMailPop3GetMailSize(S, Count);
GetMem(Body, BodySize);
NMailPop3GetMail(S, Count, Subject, Date, From, Header, Body, nil, nil);
Fs := TFileStream.Create(RcvFolder + 'RcvMail\' + DataName + '_' + IntToStr(Seq) + '.nml', fmCreate);
Fs.WriteBuffer(Header^, StrLen(Header));
Fs.WriteBuffer(#13#10, StrLen(#13#10));
Fs.WriteBuffer(Body^, StrLen(Body));
finally
I := MailListView.Items.Add;
I.Caption := StrPas(Subject);
I.SubItems.Add(StrPas(Date));
I.SubItems.Add(StrPas(From));
if DeleteFlag = False then NMailPop3DeleteMail(S, No);
if Fs <> nil then Fs.Free;
if Body <> nil then FreeMem(Body);
end
else
begin
I := MailListView.Items.Add;
I.Caption := StrPas(Subject);
I.SubItems.Add(StrPas(Date));
I.SubItems.Add(StrPas(From));
end;
// UIDLの再保存?
> それとMemoryStreamにもファイル名をつけなくてはならないのでしょうか?
MemoryStreamを使う必然性がないと思いますが…。
MemoryStreamのCreateをヘルプで見てください。TFileStreamとは違います。
MemoryStreamでファイル名が必要となるのは、SaveToFileかLoadFromFileくらいでは?
> それと比較はこんな感じに行ってOKなのでしょうか?
だめです。
たとえば、前回3通のメールを取得し、uidl.lstに保存されたUIDL一覧が、
1 thismailis1st
2 thismailis2nd
3 thismailis3rd
だったとします。
それで、2通目(thismailis2nd)を削除し、4通目(thismailis4th)が届いた状態で、またメールを取得しに行くと2通になりますよね。
そのときのUIDL一覧は、
1 thismailis1st
2 thismailis3rd
3 thismailis4th
となります。
上で保存した情報で比較すると、新しく届いたと認識するメールは、thismailis4thだけでなく、thismailis3rdも含まれてしまいます。
一覧を取得したときの記事番号と、UIDLを分離しなければなりません。
分離した上で、UIDLのみ比較します。
# 上の場合は、thismailis1st, thismailis3rdが保存されているから、新規に保存しなければならないのはthismailis4thのみ
訂正。
> それで、2通目(thismailis2nd)を削除し、4通目(thismailis4th)が届いた状態で、またメールを取得しに行くと2通になりますよね。
「3通になりますよね」です。
申し訳ない。
そうですね。>普通にSavedUIDL[No] = NowUIDL[No]で比べる
メモリストリームの方は理解できました。>ファイル名をつける必要がない
ちなみに今までに作ったメール受信関数部分にコメントをつけてみました。
以下のリストで見てどの部分でメモリストリームに新しいUIDLを引き込み
古いUIDLの比較をどこで行うか教えていただけるとうれしいです。
// POP3からのメール受信
procedure TMainForm.AddMailList(S: TSocket);
var
No, Count: integer; // メールの件数用変数
Subject: array [0..HEADER_MAX] of Char; // 件名を配列で定義
Date: array [0..HEADER_MAX] of Char; // 時刻を配列で定義
From: array [0..HEADER_MAX] of Char; // 差出人を配列で定義
Header: PChar; // ヘッダ用変数
I: TListItem; // リスト用変数
DataName: String; // NMLファイルのファイル名
Seq: Integer; // シーケンス番号
Body: PChar; // Bodyのデータ用
BodySize: Integer; // Bodyのファイルサイズ
Fs: TFileStream; // ファイルストリーム
strDate: String; // 日付の取得
strUIDL: array [0..HEADER_MAX] of Char; // UIDLのリスト
UIDLFs: TFileStream; // ファイルストリーム
strMUIDL: array [0..HEADER_MAX] of Char; // メモリ用のリスト格納
UIDLMs: TMemoryStream; // メモリストリーム
SavedUIDL: TStringList; // 保存したUIDLの格納
NowUIDL: TStringList; // メモリ側の新しいUIDLの格納
begin
// メール本文表示をクリア
BodyMemo.Lines.Clear;
// ListView もクリア
HeaderDataClear;
MailListView.Items.Clear;
// POP3 サーバに接続する
S := NMailPop3Connect(PChar(Pop3Name));
// ソケットがエラーでない時はPOP3受信の準備
if S <> INVALID_SOCKET then
begin
// ユーザ名またはパスワードが空白の時は入力ダイアログを表示
if (Id = '') or (Password = '') then
begin
// ユーザ名とパスワード入力
if InputForm.ShowModal = mrOk then // 入力ダイアログのOKボタンが押された時
begin
// IDとPasswordにユーザ名とパスワードを設定する
Id := InputForm.IdEdit.Text;
Password := InputForm.PasswordEdit.Text;
end;
end;
// メールの数を得る(APOP対応なの〜)
No := NMailPop3Authenticate(S, PChar(Id), PChar(Password), ApopFlag);
// 保存したUIDLをTStringListに格納する
SavedUIDL := TStringList.Create;
try
// 保存したUIDL.lstをSavedUIDLに格納
SavedUIDL.LoadFromFile('uidl.lst');
except
; // 何もしない?
end;
// メールの件数が0件以上の場合
if No >= 0 then
begin
for Count := 1 to No do // Countが1からNoまでループ
begin
// 順番にヘッダの内容を読み、ListView に追加
GetMem(Header, HEADER_MAX);
NMailPop3GetMailStatus(S, Count, Subject, Date, From, Header, False);
// strDateにDateの内容を渡す(NMLファイルの名前に使用する)
strDate := StrPas(Date);
// DataNameにNMLYYYYMMDDHHMMSSを格納する(例NML20021101091022)
DataName := 'NML' + Copy(strDate, 1, 4) // yyyy
+ Copy(strDate, 6, 2) // mm
+ Copy(strDate, 9, 2) // dd
+ Copy(strDate, 12, 2) // hh
+ Copy(strDate, 15, 2) // mm
+ Copy(strDate, 18, 2); // ss
// Seqに0を代入する
Seq := 0;
// 受信フォルダに同名のファイル(例NML20021101091022_0.nml)が
// 存在する場合はSeq番号を1インクリメントする(例NML20021101091022_1.nml)
while FileExists(RcvFolder + 'RcvMail\' + DataName + '_' + IntToStr(Seq) + '.nml') do Inc(Seq);
try
// 本文の大きさを取得してBodySizeに格納する
BodySize := NMailPop3GetMailSize(S, Count);
GetMem(Body, BodySize); // BodySize分のメモリを確保する
// POP3からメールを受信する
NMailPop3GetMail(S, Count, Subject, Date, From, Header, Body, nil, nil);
// DataName + _ + seq + .nmlファイルを作成する(例NML20021101091022_0.nml)
Fs := TFileStream.Create(RcvFolder + 'RcvMail\' + DataName + '_' + IntToStr(Seq) + '.nml', fmCreate);
Fs.WriteBuffer(Header^, StrLen(Header)); // ヘッダをファイルに書き出す
Fs.WriteBuffer(#13#10, StrLen(#13#10)); // \r\nをファイルに書き出す
Fs.WriteBuffer(Body^, StrLen(Body)); // 本文をファイルに書き出す
finally
I := MailListView.Items.Add; // リストビューに表示する準備
I.Caption := StrPas(Subject); // 件名をリストに表示
I.SubItems.Add(StrPas(Date)); // 時刻をリストに追加
I.SubItems.Add(StrPas(From)); // 差出人をリストに追加
// メールをサーバに残すチェックボタンがチェックされていない時は
// Noのメールをサーバから削除する
if DeleteFlag = False then NMailPop3DeleteMail(S, No);
// FsがNULLの時はFsを解放する
if Fs <> nil then Fs.Free;
// BodyがNULLの時はBodyのメモリを解放する
if Body <> nil then FreeMem(Body);
end;
end; // Countのループの終わり
// 終了時に新しいUIDLを保存する(一時的に処理をコメント行化)
{UIDLFs := nil;
try
NMailPop3GetUidl(S, NMAIL_GET_UIDL_ALL, strUIDL, Length(strUIDL));
UIDLFs := TFileStream.Create('uidl.lst', fmCreate);
UIDLFs.WriteBuffer(strUIDL[0], StrLen(strUIDL));
finally
if UIDLFs <> nil then UIDLFs.Free;
end;}
end
else // 接続できなかった場合
begin
// サーバーからのエラーメッセージを取得
NMailGetMessage(NMAIL_GET_ERROR_MESSAGE, Subject, HEADER_MAX);
MessageDlg('POPサーバに接続できませんでした。' + #13#10 + StrPas(Subject), mtError, [mbOK], 0);
end;
// POP3サーバから切断する
NMailPop3Close(S);
// SocketをINVALID_SOCKETにする
S := INVALID_SOCKET;
end;
end;
前準備(サーバのメッセージと、保存済みメッセージのUIDLを比較して、保存していないUIDLの一覧を得る)は、
// メールの件数が0件以上の場合
if No >= 0 then
begin
の後ですね。
savedUidl := TStringList.Create;
nowUidl := TStringList.Create;
NMailPop3GetUidl(S, NMAIL_GET_UIDL_ALL, strUIDL, Length(strUIDL));
try
savedUidl.LoadFromFile('uidl.lst');
except
;
end;
nowUidl.Text := StrPas(strUIDL);
UidlDelete(nowUidl, savedUidl);
というようにします。
UidlDeleteは、別途用意したもので、次のようなプロシージャです。
# 内部で、noとuidlをばらすUidlSplitプロシージャ(これも準備)を呼び出しています。
// 123 AAAAAを、no=123, Uidl=AAAAAに切り分ける
procedure UidlSplit(Line: string; var no, Uidl: string);
var
p: integer;
begin
p := Pos(' ', Line);
if p = 0 then
begin
// 半角スペースがない
no := '';
Uidl := Line;
end
else
begin
no := Trim(Copy(Line, 1, p));
Uidl := Trim(Copy(Line, p, Length(Line)));
end;
end;
procedure UidlDelete(nowUidl, savedUidl: TStringList);
var
i, j: integer;
no1, Uidl1: string;
no2, Uidl2: string;
begin
for i := nowUidl.Count - 1 downto 0 do
begin
UidlSplit(nowUidl[i], no1, Uidl1);
for j := 0 to savedUidl.Count - 1 do
begin
UidlSplit(savedUidl[j], no2, Uidl2);
if Uidl1 = Uidl2 then
begin
nowUidl.Delete(i);
break;
end;
end;
end;
end;
UidlSplitは、メールを取得するときにも使います。
保存していなくて、かつ、サーバに存在するメッセージのUIDLと、現在読み込もうとしているメッセージのUIDLを比較するためです。
ループに
for Count := 1 to No do
としていますが、これを、
for Count := 1 to nowUidl.Count - 1 do
に変更し、
その中で、
UidlSplit(nowUidl[Count], no, uidl);
と、noとuidlに分解。
そのループ内で、countを渡していたところに、StrToInt(no)を渡します。
今上の関数を入れたのですがそのままだと未定義の拡張子
上のprivateの部分に両方の関数の定義をすると別のエラーが
発生したのですがどうすればよいでしょうか?
あと最後に行うUIDLの保存はどうすればよいでしょうか?
よろしくお願いします。
エラーの内容がわからなければ答えようがありません。
UIDLの保存は、TStringListのSaveToFileを参照してください。
エラーの内容は以下の通りです。(ヒントと警告は除きました)
[エラー] testmain.pas(769): 未定義の識別子 : 'UidlSplit'
[エラー] testmain.pas(93): forward または external 宣言された 'TMainForm.UidlDelete' が見つかりません
ちなみにUidlDeleteもUidlSplitもprivate宣言の所に置きました。
>型の宣言文
UIDLの保存はループを抜けた直後で良いのでしょうか?
>保存の仕方はNowUidl.SaveToFile('uidl.lst');ですね?
あともうひとつこのようなエラーも発見しました。
[ヒント] testmain.pas(94): UidlSplit:private 部で宣言されていますが、クラス内でまったく使用されていません
未定義の識別子と出るのは、UidlSplitを使用する前に、UidlSplitの定義または実体がないからです。
2つ目のエラーも似たような理由です。
ソースの書き方はわかっていますか?
Cをやっていたのであれば、だいたいわかると思いますが、implementsより前が、C言語で言う関数宣言です。
たとえば、
int main(void)
{
TStringList *a, *b;
uidlSplit(a, b);
}
void uidlSplit(TStringList *a, TStringList *b);
だと、mainでuidlSplitを使用した時点では、uidlSplitが定義されていないので警告になりますが、
void uidlSplit(TStringList *a, TStringList *b);
int main(void)
{
TStringList *a, *b;
uidlSplit(a, b);
}
ならば、先に定義されているから警告は出ませんね。
同じことを、implementsより前にしているわけです。
今回は、おそらく別のソースからuidlSplit,uidlDeleteを使用することはないと思います。
なので、implementsより前に宣言を用意する必要はありません。
ただし、使う時点より前に実体がないと、使う時点でそのプロシージャが存在するか否かの判断ができません。
つまり、使用しているプロシージャより前に、実体を置かないといけません。
UIDLの保存はいつでもいいですよ。
NowUidlが決定した時点で、savedUidlにnowUidlを加えて保存します。
加えたり保存したりするには、TStringListの関数・プロシージャを参照してください。
簡単です。
Cの考え通りに上の方で関数の型を定義して下の方で
その関数の定義を行っています。(C言語もやっているので)
private
{ Private 宣言 }
{中略}
procedure UidlDelete(NowUidl, SavedUidl: TStringList);
procedure UidlSplit(Line: string; var no, Uidl: string);
関数の実体はend.より上の部分で定義をしています
// UIDLを削除する
procedure UidlDelete(NowUidl, SavedUidl: TStringList);
var
i, j: integer;
no1, Uidl1: string;
no2, Uidl2: string;
begin
for i := NowUidl.Count - 1 downto 0 do
begin
UidlSplit(NowUidl[i], no1, Uidl1);
for j := 0 to savedUidl.Count - 1 do
begin
UidlSplit(SavedUidl[j], no2, Uidl2);
if Uidl1 = Uidl2 then
begin
NowUidl.Delete(i);
break;
end;
end;
end;
end;
// UIDLのリスト表示の1行を切り分ける(例123 AAAAAを、no=123, Uidl=AAAAAに切り分ける)
procedure UidlSplit(Line: string; var no, Uidl: string);
var
p: integer;
begin
p := Pos(' ', Line);
if p = 0 then
begin
// 半角スペースがない
no := '';
Uidl := Line;
end
else
begin
no := Trim(Copy(Line, 1, p));
Uidl := Trim(Copy(Line, p, Length(Line)));
end;
end;
end.
やはりこれでは動かないのでしょうか?
その場合はどの辺りまでこの関数を上に移動すればよいでしょうか?
昔こういうのをやっても確かエラーは出ませんでした。
#include <stdio.h>
int plus(int, int);
int main(void){
int x, y, z;
x = 1;
y = 2;
z = plus(x, y);
printf("%d+%d=%d\n", x, y, z);
return(0);
}
int plus(int a, int b){
return(a + b);
}
なるほど。
CはやっていてもC++はやっていませんね。
クラス内関数の実体には、クラス名が必要です。
interface
type
TClassName=class
private
procedure Proc1;
end;
implements
procedure TClassName.Proc1;
begin
end;
というように。
MFCは少々やっていたんですが・・・(>_<)
ほとんど自前の関数を作ることがなかったんでこんな考えに
なってしまうようです。(VBは関数の型の宣言は必要ないでしたっけ?)
なのでまだちょっとどこに置いて良いのか分からない状態です。
クラスの宣言の、privateに関数を宣言してあるのなら、同じソース内のどこに置いてもOKですよ。
ただ、実体のほうにはクラス名がなければ駄目なのです。
たとえば、
interface
type
TClassName1=class
private
procedure Proc1;
end;
TClassName2=class
private
procedure Proc1;
end;
implements
・・・
とあったとき、
procedure Proc1;
とだけ見てTClassName1とTClassName2のどちらのプロシージャかわかりますか?
それを見分けるために、
procedure TClassName1.Proc1;
や
procedure TClassName2.Proc1;
というようにクラス名をつけるんです。
ヘルプの、ObjectPascal言語ガイドを参照してください。
> クラスの宣言の、privateに関数を宣言してあるのなら、同じソース内のどこに置いてもOKですよ。
語弊がありますね。
実体はimplementation以下に書きます。
Pascal言語の許す範囲で、定義してください。
詳しくはObjectPascal言語ガイドを。
上の件は{$R *.DFM}の下に書いて解決しました。
でも今度は以下の事を実行しようと記述をしたらエラーが発生しました。
>UidlSplitは、メールを取得するときにも使います。
>保存していなくて、かつ、サーバに存在するメッセージのUIDLと、現在読み込もう>としているメッセージのUIDLを比較するためです。
>ループに
>for Count := 1 to No do
>としていますが、これを、
>for Count := 1 to nowUidl.Count - 1 doに変更し、
>その中で、UidlSplit(nowUidl[Count], no, uidl);と、noとuidlに分解。
>そのループ内で、countを渡していたところに、StrToInt(no)を渡します。
これを実装したコードは以下の通りです。
// POP3からのメール受信
procedure TMainForm.AddMailList(S: TSocket);
var
No, Count: integer; // メールの件数用変数
Subject: array [0..HEADER_MAX] of Char; // 件名を配列で定義
Date: array [0..HEADER_MAX] of Char; // 時刻を配列で定義
From: array [0..HEADER_MAX] of Char; // 差出人を配列で定義
Header: PChar; // ヘッダ用変数
I: TListItem; // リスト用変数
DataName: String; // NMLファイルのファイル名
Seq: Integer; // シーケンス番号
Body: PChar; // Bodyのデータ用
BodySize: Integer; // Bodyのファイルサイズ
Fs: TFileStream; // ファイルストリーム
strDate: String; // 日付の取得
strUIDL: array [0..HEADER_MAX] of Char; // UIDLのリスト
UIDLFs: TFileStream; // ファイルストリーム
strMUIDL: array [0..HEADER_MAX] of Char; // メモリ用のリスト格納
UIDLMs: TMemoryStream; // メモリストリーム
SavedUIDL: TStringList; // 保存したUIDLの格納
NowUIDL: TStringList; // メモリ側の新しいUIDLの格納
sUIDL: String; // 分解したUIDLの格納
begin
// メール本文表示をクリア
BodyMemo.Lines.Clear;
// ListView もクリア
HeaderDataClear;
MailListView.Items.Clear;
// POP3 サーバに接続する
S := NMailPop3Connect(PChar(Pop3Name));
// ソケットがエラーでない時はPOP3受信の準備
if S <> INVALID_SOCKET then
begin
// ユーザ名またはパスワードが空白の時は入力ダイアログを表示
if (Id = '') or (Password = '') then
begin
// ユーザ名とパスワード入力
if InputForm.ShowModal = mrOk then // 入力ダイアログのOKボタンが押された時
begin
// IDとPasswordにユーザ名とパスワードを設定する
Id := InputForm.IdEdit.Text;
Password := InputForm.PasswordEdit.Text;
end;
end;
// メールの数を得る(APOP対応なの〜)
No := NMailPop3Authenticate(S, PChar(Id), PChar(Password), ApopFlag);
// 保存したUIDLをTStringListに格納する
SavedUIDL := TStringList.Create;
NowUIDL := TStringList.Create;
try
// 保存したUIDL.lstをSavedUIDLに格納
SavedUIDL.LoadFromFile('uidl.lst');
except
; // 何もしない?
end;
// 新しいUIDLのテキストにstrUIDLの内容を代入する
NowUidl.Text := StrPas(strUIDL);
// 新しいUIDLと比較して古いUIDLの情報を削除する
UidlDelete(nowUidl, savedUidl);
// メールの件数が0件以上の場合
if No >= 0 then
begin
for Count := 1 to NowUidl.Count - 1 do // Countが1からnowUidl.Count - 1までループ
begin
// UIDLのリストを切り分ける
UidlSplit(NowUidl[Count], No, sUIDL);
// 順番にヘッダの内容を読み、ListView に追加
GetMem(Header, HEADER_MAX);
NMailPop3GetMailStatus(S, StrToInt(No), Subject, Date, From, Header, False);
// strDateにDateの内容を渡す(NMLファイルの名前に使用する)
strDate := StrPas(Date);
// DataNameにNMLYYYYMMDDHHMMSSを格納する(例NML20021101091022)
DataName := 'NML' + Copy(strDate, 1, 4) // yyyy
+ Copy(strDate, 6, 2) // mm
+ Copy(strDate, 9, 2) // dd
+ Copy(strDate, 12, 2) // hh
+ Copy(strDate, 15, 2) // mm
+ Copy(strDate, 18, 2); // ss
// Seqに0を代入する
Seq := 0;
// 受信フォルダに同名のファイル(例NML20021101091022_0.nml)が
// 存在する場合はSeq番号を1インクリメントする(例NML20021101091022_1.nml)
while FileExists(RcvFolder + 'RcvMail\' + DataName + '_' + IntToStr(Seq) + '.nml') do Inc(Seq);
try
// 本文の大きさを取得してBodySizeに格納する
BodySize := NMailPop3GetMailSize(S, StrToInt(No));
GetMem(Body, BodySize); // BodySize分のメモリを確保する
// POP3からメールを受信する
NMailPop3GetMail(S, StrToInt(No), Subject, Date, From, Header, Body, nil, nil);
// DataName + _ + seq + .nmlファイルを作成する(例NML20021101091022_0.nml)
Fs := TFileStream.Create(RcvFolder + 'RcvMail\' + DataName + '_' + IntToStr(Seq) + '.nml', fmCreate);
Fs.WriteBuffer(Header^, StrLen(Header)); // ヘッダをファイルに書き出す
Fs.WriteBuffer(#13#10, StrLen(#13#10)); // \r\nをファイルに書き出す
Fs.WriteBuffer(Body^, StrLen(Body)); // 本文をファイルに書き出す
finally
I := MailListView.Items.Add; // リストビューに表示する準備
I.Caption := StrPas(Subject); // 件名をリストに表示
I.SubItems.Add(StrPas(Date)); // 時刻をリストに追加
I.SubItems.Add(StrPas(From)); // 差出人をリストに追加
// メールをサーバに残すチェックボタンがチェックされていない時は
// Noのメールをサーバから削除する
if DeleteFlag = False then NMailPop3DeleteMail(S, No);
// FsがNULLの時はFsを解放する
if Fs <> nil then Fs.Free;
// BodyがNULLの時はBodyのメモリを解放する
if Body <> nil then FreeMem(Body);
end;
end; // Countのループの終わり
// 終了時に新しいUIDLを保存する(一時的に処理をコメント行化)
{UIDLFs := nil;
try
NMailPop3GetUidl(S, NMAIL_GET_UIDL_ALL, strUIDL, Length(strUIDL));
UIDLFs := TFileStream.Create('uidl.lst', fmCreate);
UIDLFs.WriteBuffer(strUIDL[0], StrLen(strUIDL));
finally
if UIDLFs <> nil then UIDLFs.Free;
end;}
end
else // 接続できなかった場合
begin
// サーバーからのエラーメッセージを取得
NMailGetMessage(NMAIL_GET_ERROR_MESSAGE, Subject, HEADER_MAX);
MessageDlg('POPサーバに接続できませんでした。' + #13#10 + StrPas(Subject), mtError, [mbOK], 0);
end;
// POP3サーバから切断する
NMailPop3Close(S);
// SocketをINVALID_SOCKETにする
S := INVALID_SOCKET;
end;
end;
そしてエラー内容は以下の通りです。
[エラー] testmain.pas(406): 変数実パラメータと変数仮パラメータとは同一の型でなければなりません
[エラー] testmain.pas(410): 'String' と 'Integer' には互換性がありません
[エラー] testmain.pas(432): 'String' と 'Integer' には互換性がありません
[エラー] testmain.pas(436): 'String' と 'Integer' には互換性がありません
[致命的エラー] nmail.dpr(11): 'testmain.pas' ユニットはコンパイルできませんでした
それは聞くよりソースを見直した方がいいですよ。
そのエラーの原因がわからないと言うことは、Delphiのソースがわからないということになります。
ケアレスミスです。
今の所はエラーになった部分をコメント化または前のコードに直しています。
以下にエラーの部分を書いておきます。
[エラー] testmain.pas(406): 変数実パラメータと変数仮パラメータとは同一の型でなければなりません
// UIDLのリストを切り分ける
UidlSplit(NowUidl[Count], No, sUIDL);
NMailPop3GetMailStatus(S, StrToInt(No), Subject, Date, From, Header, False);
while FileExists(RcvFolder + 'RcvMail\' + DataName + '_' + IntToStr(Seq) + '.nml') do Inc(Seq);
// 本文の大きさを取得してBodySizeに格納する
BodySize := NMailPop3GetMailSize(S, StrToInt(No));
// POP3からメールを受信する
NMailPop3GetMail(S, StrToInt(No), Subject, Date, From, Header, Body, nil, nil);
Fs := TFileStream.Create(RcvFolder + 'RcvMail\' + DataName + '_' + IntToStr(Seq) + '.nml', fmCreate);
下のところに書くのを忘れてしまいました。>エラー内容
[エラー] testmain.pas(410): 'String' と 'Integer' には互換性がありません
[エラー] testmain.pas(432): 'String' と 'Integer' には互換性がありません
[エラー] testmain.pas(436): 'String' と 'Integer' には互換性がありません
NMailPop3GetMailStatus(S, StrToInt(No), Subject, Date, From, Header, False);
while FileExists(RcvFolder + 'RcvMail\' + DataName + '_' + IntToStr(Seq) + '.nml') do Inc(Seq);
// 本文の大きさを取得してBodySizeに格納する
BodySize := NMailPop3GetMailSize(S, StrToInt(No));
// POP3からメールを受信する
NMailPop3GetMail(S, StrToInt(No), Subject, Date, From, Header, Body, nil, nil);
Fs := TFileStream.Create(RcvFolder + 'RcvMail\' + DataName + '_' + IntToStr(Seq) + '.nml', fmCreate);
ちょっと埒があかなくなってきたので一度少し前の状態に戻してみました。(>_<)
また一度uidl.lstを消して受信を試みましたがuidl.lstを開けない
例外で止まってしまいました。
// POP3からのメール受信
procedure TMainForm.AddMailList(S: TSocket);
var
No, Count: integer; // メールの件数用変数
Subject: array [0..HEADER_MAX] of Char; // 件名を配列で定義
Date: array [0..HEADER_MAX] of Char; // 時刻を配列で定義
From: array [0..HEADER_MAX] of Char; // 差出人を配列で定義
Header: PChar; // ヘッダ用変数
I: TListItem; // リスト用変数
DataName: String; // NMLファイルのファイル名
Seq: Integer; // シーケンス番号
Body: PChar; // Bodyのデータ用
BodySize: Integer; // Bodyのファイルサイズ
Fs: TFileStream; // ファイルストリーム
strDate: String; // 日付の取得
strUIDL: array [0..HEADER_MAX] of Char; // UIDLのリスト
UIDLFs: TFileStream; // ファイルストリーム
strMUIDL: array [0..HEADER_MAX] of Char; // メモリ用のリスト格納
UIDLMs: TMemoryStream; // メモリストリーム
SavedUIDL: TStringList; // 保存したUIDLの格納
NowUIDL: TStringList; // メモリ側の新しいUIDLの格納
sUIDL: String; // 分解したUIDLの格納
begin
// メール本文表示をクリア
BodyMemo.Lines.Clear;
// ListView もクリア
HeaderDataClear;
MailListView.Items.Clear;
// POP3 サーバに接続する
S := NMailPop3Connect(PChar(Pop3Name));
// ソケットがエラーでない時はPOP3受信の準備
if S <> INVALID_SOCKET then
begin
// ユーザ名またはパスワードが空白の時は入力ダイアログを表示
if (Id = '') or (Password = '') then
begin
// ユーザ名とパスワード入力
if InputForm.ShowModal = mrOk then // 入力ダイアログのOKボタンが押された時
begin
// IDとPasswordにユーザ名とパスワードを設定する
Id := InputForm.IdEdit.Text;
Password := InputForm.PasswordEdit.Text;
end;
end;
// メールの数を得る(APOP対応なの〜)
No := NMailPop3Authenticate(S, PChar(Id), PChar(Password), ApopFlag);
// 保存したUIDLをTStringListに格納する
SavedUIDL := TStringList.Create;
NowUIDL := TStringList.Create;
try
// 保存したUIDL.lstをSavedUIDLに格納
SavedUIDL.LoadFromFile('uidl.lst');
except
; // 何もしない?
end;
// 新しいUIDLのテキストにstrUIDLの内容を代入する
NowUidl.Text := StrPas(strUIDL);
// 新しいUIDLと比較して古いUIDLの情報を削除する
UidlDelete(NowUidl, SavedUidl);
// メールの件数が0件以上の場合
if No >= 0 then
begin
for Count := 1 to No do // Countが1からnowUidl.Count - 1までループ
begin
// UIDLのリストを切り分ける
{UidlSplit(NowUidl[Count], No, sUIDL);}
// 順番にヘッダの内容を読み、ListView に追加
GetMem(Header, HEADER_MAX);
NMailPop3GetMailStatus(S, Count, Subject, Date, From, Header, False);
// strDateにDateの内容を渡す(NMLファイルの名前に使用する)
strDate := StrPas(Date);
// DataNameにNMLYYYYMMDDHHMMSSを格納する(例NML20021101091022)
DataName := 'NML' + Copy(strDate, 1, 4) // yyyy
+ Copy(strDate, 6, 2) // mm
+ Copy(strDate, 9, 2) // dd
+ Copy(strDate, 12, 2) // hh
+ Copy(strDate, 15, 2) // mm
+ Copy(strDate, 18, 2); // ss
// Seqに0を代入する
Seq := 0;
// 受信フォルダに同名のファイル(例NML20021101091022_0.nml)が
// 存在する場合はSeq番号を1インクリメントする(例NML20021101091022_1.nml)
while FileExists(RcvFolder + 'RcvMail\' + DataName + '_' + IntToStr(Seq) + '.nml') do Inc(Seq);
try
// 本文の大きさを取得してBodySizeに格納する
BodySize := NMailPop3GetMailSize(S, Count);
GetMem(Body, BodySize); // BodySize分のメモリを確保する
// POP3からメールを受信する
NMailPop3GetMail(S, Count, Subject, Date, From, Header, Body, nil, nil);
// DataName + _ + seq + .nmlファイルを作成する(例NML20021101091022_0.nml)
Fs := TFileStream.Create(RcvFolder + 'RcvMail\' + DataName + '_' + IntToStr(Seq) + '.nml', fmCreate);
Fs.WriteBuffer(Header^, StrLen(Header)); // ヘッダをファイルに書き出す
Fs.WriteBuffer(#13#10, StrLen(#13#10)); // \r\nをファイルに書き出す
Fs.WriteBuffer(Body^, StrLen(Body)); // 本文をファイルに書き出す
finally
I := MailListView.Items.Add; // リストビューに表示する準備
I.Caption := StrPas(Subject); // 件名をリストに表示
I.SubItems.Add(StrPas(Date)); // 時刻をリストに追加
I.SubItems.Add(StrPas(From)); // 差出人をリストに追加
// メールをサーバに残すチェックボタンがチェックされていない時は
// Noのメールをサーバから削除する
if DeleteFlag = False then NMailPop3DeleteMail(S, No);
// FsがNULLの時はFsを解放する
if Fs <> nil then Fs.Free;
// BodyがNULLの時はBodyのメモリを解放する
if Body <> nil then FreeMem(Body);
end;
end; // Countのループの終わり
// 終了時に新しいUIDLを保存する(一時的に処理をコメント行化)
{UIDLFs := nil;
try
NMailPop3GetUidl(S, NMAIL_GET_UIDL_ALL, strUIDL, Length(strUIDL));
UIDLFs := TFileStream.Create('uidl.lst', fmCreate);
UIDLFs.WriteBuffer(strUIDL[0], StrLen(strUIDL));
finally
if UIDLFs <> nil then UIDLFs.Free;
end;}
end
else // 接続できなかった場合
begin
// サーバーからのエラーメッセージを取得
NMailGetMessage(NMAIL_GET_ERROR_MESSAGE, Subject, HEADER_MAX);
MessageDlg('POPサーバに接続できませんでした。' + #13#10 + StrPas(Subject), mtError, [mbOK], 0);
end;
// POP3サーバから切断する
NMailPop3Close(S);
// SocketをINVALID_SOCKETにする
S := INVALID_SOCKET;
end;
end;
何度か原因を考えてみたのですが、やっぱり分からない状態です。
>前回のミスについて(コードを印刷してみましたが分かりませんでした。)
現在開発環境がない場所にいるので何も出来ないのですが
休み明けからまた続きをしようと思っているのでよろしくお願いします。
プログラマ志望なのにこんなのじゃだめですね・・・(>_<)
変数の型について、理解していますか?
たとえば、
var
Num: Integer;
begin
Num := '12345';
これがエラーになる原因はわかりますか?
エラーの原因は、たいていエラーが起きている部分と、エラーが起きている部分で使っている変数が定義されているところ、関数が定義されているところの3つがわかればわかります。
# ソースを全掲するのはよくないです。
今回の場合は、
[変数の定義]
No, Count: integer; // メールの件数用変数
NowUIDL: TStringList;
sUIDL: String;
[関数の定義]
procedure UidlSplit(Line: string; var no, Uidl: string);
[エラー箇所]
UidlSplit(NowUidl[Count], No, sUIDL);
がわかれば、エラーの原因・対処法がわかります。
C言語を知っているようですので、C言語の例を出してみます。
#include<stdio.h>
#include<stdlib.h>
void UidlSplit(char *line, char *no, char *uidl)
{
/* 省略 */
}
int main(void)
{
char[] localLine = "1 uidl0001";
char localUidl[128];
int localNo;
UidlSplit(localLine, localNo, localUidl);
return 0;
}
これがエラーになる原因はわかりますか?
同じ理由です。
これがわからないのであれば、Delphiの型について勉強し直した方がいいです。
# 初歩的な間違いですから
ちなみに、プログラマ志望ということですが、ある程度できれば問題ないと思いますよ。
学生(または日曜プログラマ)として勉強していた内容が、仕事ではあまり使えませんから。
基本だけは変わりませんが、仕事の場合はそれに加えて納期と工数が最重要項目になります。
# 会社によって方針は違うでしょうけどね
納期を守るためには、今まで美しいソースを目指して書いていた物が、気がついたら他の人に読めないソースになっていたり、工数が足りなくなって中途半端(といっても動作する)なまま納品したり…。
>int main(void)
>{
> char[] localLine = "1 uidl0001";
> char localUidl[128];
> int localNo;
>
> UidlSplit(localLine, localNo, localUidl);
>
> return 0;
>}
もちろんこのエラーについては分かります。
型についてはVBも高校ぐらいからやっていたので型変換で
直るようなのですが、以下のコードでは何も変換について
述べていなかったのでそのまま記述したらStringとIntegerには
互換性がないというエラーが出てしまいました。
>UidlSplitは、メールを取得するときにも使います。
>保存していなくて、かつ、サーバに存在するメッセージのUIDLと、現在読み込もう>としているメッセージのUIDLを比較するためです。
>ループに
>for Count := 1 to No do
>としていますが、これを、
>for Count := 1 to nowUidl.Count - 1 doに変更し、
>その中で、UidlSplit(nowUidl[Count], no, uidl);と、noとuidlに分解。
>そのループ内で、countを渡していたところに、StrToInt(no)を渡します。
でもStrToIntでString(文字列型)をInterger(整数)型に変換しているのに
どうして互換性のないというエラーが出たのかがいまいち分かりません。
SplitUIDLの引数を見たらすぐに分かったのですが
noは文字列定義されているのにこのプロシージャ内で
使われているNoは整数型で取られているからエラーが起こるんですね。
でもこの場合どうすればこの状況を回避できるか分からないです。(>_<)
Noは1つしか定義できないですから。(C++だとオーバーロードが使えるのですが・・・)
単純に、Noを文字列型に定義すればいいのでは?
オーバーロードは、Delphiにももちろん存在します。
ヘルプ「ObjectPascal言語ガイド」の、「メソッドのオーバーロード」を参照してください。
>「ObjectPascal言語ガイド」の、「メソッドのオーバーロード」を参照
変数のオーバーロードではなくて関数のオーバロードみたいです。
でもメソッドではなくて変数の二重定義(この場合StringとInteger)って
出来るのでしょうか?
出来なければStringの方はmNoとして別定義しようと思います。
これが終わったら次は保存するかしないかの分岐なのですが・・・。
変数の二重定義はエラーになるのでmNoと言うString型の定義を作り
Countを入れていたところにStrToInt(mNo)を入れてみたら問題なく
動作しました。でも新たなエラーとしてNowUIDLのStringListの
内容がないため例外エラーが発生して受信できないで止まってしまいました。
最初の方でNowUIDLに現在の新しいUIDLをNMailPop3GetUidlで取得して
NowUIDLに渡すにはどうすればよいでしょうか?
また問題点コードリストを載せます。(毎回長文でごめんなさい)
今このコードで動かすとメールをぜんぜん受信しません。
あと必ずUIDL.lstを削除すると読み込み例外が出るので
空のファイルを作って読み込ませた方が良さそうなのですが
やり方はどうすれば良いでしょうか?
TFileStream.WriteBuffer('', 0);を使って空のファイルは
作れるのでしょうか?
// POP3からのメール受信
procedure TMainForm.AddMailList(S: TSocket);
var
No, Count: integer; // メールの件数用変数
mNo: String; // SplitUidl用の変数
Subject: array [0..HEADER_MAX] of Char; // 件名を配列で定義
Date: array [0..HEADER_MAX] of Char; // 時刻を配列で定義
From: array [0..HEADER_MAX] of Char; // 差出人を配列で定義
Header: PChar; // ヘッダ用変数
I: TListItem; // リスト用変数
DataName: String; // NMLファイルのファイル名
Seq: Integer; // シーケンス番号
Body: PChar; // Bodyのデータ用
BodySize: Integer; // Bodyのファイルサイズ
Fs: TFileStream; // ファイルストリーム
strDate: String; // 日付の取得
strUIDL: array [0..HEADER_MAX] of Char; // UIDLのリスト
UIDLFs: TFileStream; // ファイルストリーム
SavedUIDL: TStringList; // 保存したUIDLの格納
NowUIDL: TStringList; // メモリ側の新しいUIDLの格納
sUIDL: String; // 分解したUIDLの格納
nUIDL: TMemoryStream; // メモリストリーム
nUIDLs: array [0..HEADER_MAX] of Char; // 新しいUIDLのリスト
begin
// メール本文表示をクリア
BodyMemo.Lines.Clear;
// ListView もクリア
HeaderDataClear;
MailListView.Items.Clear;
// POP3 サーバに接続する
S := NMailPop3Connect(PChar(Pop3Name));
// ソケットがエラーでない時はPOP3受信の準備
if S <> INVALID_SOCKET then
begin
// ユーザ名またはパスワードが空白の時は入力ダイアログを表示
if (Id = '') or (Password = '') then
begin
// ユーザ名とパスワード入力
if InputForm.ShowModal = mrOk then // 入力ダイアログのOKボタンが押された時
begin
// IDとPasswordにユーザ名とパスワードを設定する
Id := InputForm.IdEdit.Text;
Password := InputForm.PasswordEdit.Text;
end;
end;
// メールの数を得る(APOP対応)
No := NMailPop3Authenticate(S, PChar(Id), PChar(Password), ApopFlag);
nUIDL := TMemoryStream.Create;
NMailPop3GetUidl(S, NMAIL_GET_UIDL_ALL, nUIDLs, Length(nUIDLs));
nUIDL.WriteBuffer(nUIDLs, StrLen(nUIDLs));
// 保存したUIDLをTStringListに格納する
SavedUIDL := TStringList.Create;
NowUIDL := TStringList.Create;
try
// 保存したUIDL.lstをSavedUIDLに格納
SavedUIDL.LoadFromFile('uidl.lst');
except
; // 何もしない?(例外発生)
end;
// 新しいUIDLのテキストにstrUIDLの内容を代入する
NowUidl.Text := StrPas(strUIDL);
// 新しいUIDLと比較して古いUIDLの情報を削除する
UidlDelete(NowUidl, SavedUidl);
// メールの件数が0件以上の場合
if No >= 0 then
begin
for Count := 1 to nowUidl.Count{No} - 1 do // Countが1からnowUidl.Count - 1までループ
begin
// UIDLのリストを切り分ける
UidlSplit(NowUidl[Count], mNo, sUIDL);
// 順番にヘッダの内容を読み、ListView に追加
GetMem(Header, HEADER_MAX);
NMailPop3GetMailStatus(S, StrToInt(mNo){Count}, Subject, Date, From, Header, False);
// strDateにDateの内容を渡す(NMLファイルの名前に使用する)
strDate := StrPas(Date);
// DataNameにNMLYYYYMMDDHHMMSSを格納する(例NML20021101091022)
DataName := 'NML' + Copy(strDate, 1, 4) // yyyy
+ Copy(strDate, 6, 2) // mm
+ Copy(strDate, 9, 2) // dd
+ Copy(strDate, 12, 2) // hh
+ Copy(strDate, 15, 2) // mm
+ Copy(strDate, 18, 2); // ss
// Seqに0を代入する
Seq := 0;
// 受信フォルダに同名のファイル(例NML20021101091022_0.nml)が
// 存在する場合はSeq番号を1インクリメントする(例NML20021101091022_1.nml)
while FileExists(RcvFolder + 'RcvMail\' + DataName + '_' + IntToStr(Seq) + '.nml') do Inc(Seq);
try
// 本文の大きさを取得してBodySizeに格納する
BodySize := NMailPop3GetMailSize(S, StrToInt(mNo){Count});
GetMem(Body, BodySize); // BodySize分のメモリを確保する
// POP3からメールを受信する
NMailPop3GetMail(S, StrToInt(mNo){Count}, Subject, Date, From, Header, Body, nil, nil);
// DataName + _ + seq + .nmlファイルを作成する(例NML20021101091022_0.nml)
Fs := TFileStream.Create(RcvFolder + 'RcvMail\' + DataName + '_' + IntToStr(Seq) + '.nml', fmCreate);
Fs.WriteBuffer(Header^, StrLen(Header)); // ヘッダをファイルに書き出す
Fs.WriteBuffer(#13#10, StrLen(#13#10)); // \r\nをファイルに書き出す
Fs.WriteBuffer(Body^, StrLen(Body)); // 本文をファイルに書き出す
finally
I := MailListView.Items.Add; // リストビューに表示する準備
I.Caption := StrPas(Subject); // 件名をリストに表示
I.SubItems.Add(StrPas(Date)); // 時刻をリストに追加
I.SubItems.Add(StrPas(From)); // 差出人をリストに追加
// メールをサーバに残すチェックボタンがチェックされていない時は
// Noのメールをサーバから削除する
if DeleteFlag = False then NMailPop3DeleteMail(S, No);
// FsがNULLの時はFsを解放する
if Fs <> nil then Fs.Free;
// BodyがNULLの時はBodyのメモリを解放する
if Body <> nil then FreeMem(Body);
end;
end; // Countのループの終わり
// 終了時に新しいUIDLを保存する(一時的に処理をコメント行化)
UIDLFs := nil;
try
NMailPop3GetUidl(S, NMAIL_GET_UIDL_ALL, strUIDL, Length(strUIDL));
UIDLFs := TFileStream.Create('uidl.lst', fmCreate);
UIDLFs.WriteBuffer(strUIDL[0], StrLen(strUIDL));
finally
if UIDLFs <> nil then UIDLFs.Free;
end;
end
else // 接続できなかった場合
begin
// サーバーからのエラーメッセージを取得
NMailGetMessage(NMAIL_GET_ERROR_MESSAGE, Subject, HEADER_MAX);
MessageDlg('POPサーバに接続できませんでした。' + #13#10 + StrPas(Subject), mtError, [mbOK], 0);
end;
// POP3サーバから切断する
NMailPop3Close(S);
// SocketをINVALID_SOCKETにする
S := INVALID_SOCKET;
end;
end;
> 変数のオーバーロードではなくて関数のオーバロードみたいです。
C言語でも変数をオーバーロードすることはできませんよ。
何がやりたいのでしょうか。
長文を載せる前に、なぜ問題点のみに絞って載せないのですか?
以前提示した関数を使えば、この問題は解決できます。
まずは、なぜ解決できないのかを自力で調べてみてはどうですか?
提示されたものでできないから、新しい方法でやってみたがそれも駄目、となると、わからないことの山積みで終わってしまいますよ。
そうですね、結構わがままなところがあるので大きく載せて
答えて下さいと言ってしまう所があるので気をつけます。>にしのさん
一つずつ解決すると言うことでまずは新しいUIDLを取り込む所が
どうやって橋渡しすればよいか分からないのですがどうやれば
良いのでしょう?またuidl.lstがない時にexceptに行かずに
エラーで止まってしまうのですがこれはどうしてでしょうか?
・・・これでは2つの質問になってしまいました。ごめんなさいです。
// メールの数を得る(APOP対応)
No := NMailPop3Authenticate(S, PChar(Id), PChar(Password), ApopFlag);
// NowUIDLのリストに現在のUIDLを引き込む?
nUIDL := TMemoryStream.Create;
NMailPop3GetUidl(S, NMAIL_GET_UIDL_ALL, nUIDLs, Length(nUIDLs));
nUIDL.WriteBuffer(nUIDLs, StrLen(nUIDLs));
// 保存したUIDLをTStringListに格納する
SavedUIDL := TStringList.Create;
NowUIDL := TStringList.Create;
try
// 保存したUIDL.lstをSavedUIDLに格納
SavedUIDL.LoadFromFile('uidl.lst');
except
try
// 初回起動(またはuidl.lstファイルがない)時に空のファイルを作って読み込む
UIDLFs := TFileStream.Create('uidl.lst', fmCreate); // 何もしない?(例外発生)
UIDLFs.WriteBuffer('', StrLen(''));
finally
if UIDLFs <> nil then UIDLFs.Free;
end;
SavedUIDL.LoadFromFile('uidl.lst');
end;
// 新しいUIDLのテキストにstrUIDLの内容を代入する
NowUidl.Text := StrPas(strUIDL);
> 一つずつ解決すると言うことでまずは新しいUIDLを取り込む所が
> どうやって橋渡しすればよいか分からないのですがどうやれば
> 良いのでしょう?
これは、
// 新しいUIDLのテキストにstrUIDLの内容を代入する
NowUidl.Text := StrPas(strUIDL);
ですよ。
TStringListクラスの、Textプロパティを見てください。
ここに文字列を入れると、Stringsプロパティに各行ごと切り分けられます。
つまり、
StringList.Text := 'abc'#13#10'def'#13#10'ghi';
は、
StringList.Clear; // 内容を破棄
StringList.Add('abc'); //一行('abc')追加
StringList.Add('def'); //一行('def')追加
StringList.Add('ghi'); //一行('ghi')追加
と同じです。
> またuidl.lstがない時にexceptに行かずに
> エラーで止まってしまうのですがこれはどうしてでしょうか?
>
デバッグオプションの初期値では、例外が発生すると止まります。
実際のEXEを直接起動すれば出ないはずです。
デバッグするわけだから、例外の発生がわからないと困ることがありますよね。そのためです。
いらないのであれば、デバッグオプションを変更してください。
# この辺は僕も詳しくないです。ヘルプを見た方が早いかも
SavedUIDL.LoadFromFile('uidl.lst');
も例外を発生するので、tryでくくる必要があります。
ただし、ファイルがなくても空なのは変わりないので、
SavedUIDL.Clear;
try
SavedUIDL.LoadFromFile('uidl.lst');
except
;
end;
でOKです。
と言うことは1つ目の答えは以下の通りで良いのですか?
// メールの数を得る(APOP対応)
No := NMailPop3Authenticate(S, PChar(Id), PChar(Password), ApopFlag);
// 新しいUIDLを取得する
NMailPop3GetUidl(S, NMAIL_GET_UIDL_ALL, strUIDL, Length(strUIDL));
// 新しいUIDLと保存したUIDLを格納するTStringListを作る
SavedUIDL := TStringList.Create;
NowUIDL := TStringList.Create;
try
// 保存したUIDL.lstをSavedUIDLに格納
SavedUIDL.LoadFromFile('uidl.lst');
except
try
// 初回起動(またはuidl.lstファイルがない)時に空のファイルを作って読み込む
UIDLFs := TFileStream.Create('uidl.lst', fmCreate); // 何もしない?(例外発生)
UIDLFs.WriteBuffer('', StrLen(''));
finally
if UIDLFs <> nil then UIDLFs.Free;
end;
SavedUIDL.LoadFromFile('uidl.lst');
end;
// 新しいUIDLのStringListのテキストにstrUIDL(新しいUIDL)の内容を代入する
NowUidl.Text := StrPas(strUIDL);
2つ目はEXEから実行すれば問題なくファイルは出来るようです。
次の質問は分かり次第書きます。
えっと次の質問なのですが、最後の新しいUIDLの保存部分なのですが
以下のようなもので良いのでしょうか?(ちなみにループ後の処理です)
// 終了時に新しいUIDLを保存する(一時的に処理をコメント行化)
UIDLFs := nil;
try
NMailPop3GetUidl(S, NMAIL_GET_UIDL_ALL, strUIDL, Length(strUIDL));
UIDLFs := TFileStream.Create('uidl.lst', fmCreate);
UIDLFs.WriteBuffer(strUIDL[0], StrLen(strUIDL));
finally
if UIDLFs <> nil then UIDLFs.Free;
end;
ファイルがなければ作成、の部分は少し冗長ですね。
ない場合は読み込んでも空ですから、再度読み込む必要はありません。
try
// 保存したUIDL.lstをSavedUIDLに格納
SavedUIDL.LoadFromFile('uidl.lst');
except
// ファイルが読めない(存在しない?)
try
// 空のファイルを作成
SavedUIDL.SaveToFile('uidl.lst');
except
ShowMessage('保存できません');
end;
end;
だけでいいと思います。
最後に、新しいUIDLを「追加して」保存する処理は、
savedUidl.AddStrings(nowUidl);
try
savedUidl.SaveToFile('uidl.lst');
except
ShowMessage('保存できません');
end;
こんな感じです。
TStringListクラスの、AddStringsメソッドを見てください。
この状態だと新しいUIDLを保存しているのか古いUIDLを保存しているかが
分からないのでこのような質問をしています。(>_<)
一応次の質問です。
一度受信したあと新規送信でメールを作って送信すれば新しいメールが
1通追加されてその新しいメールが受信されるはずなのに何も受信しないのは
どうしてなのでしょうか?>前からの通りUIDLの比較は行っています
また受信したメールのリスト(リストビュー)をクリックしたのですが
リストビューに表示された件名の内容とMemoに表示されるメールが
違うものになってしまうのはどうしてですか?
>この状態だと新しいUIDLを保存しているのか古いUIDLを保存しているかが
>分からないのでこのような質問をしています。(>_<)
こちらは解決しましたが、次の質問〜は解決してない状態です。(>_<)
次の問題点(質問)です。
>一度受信したあと新規送信でメールを作って送信すれば新しいメールが
>1通追加されてその新しいメールが受信されるはずなのに何も受信しないのは
>どうしてなのでしょうか?>前からの通りUIDLの比較は行っています
>
>また受信したメールのリスト(リストビュー)をクリックしたのですが
>リストビューに表示された件名の内容とMemoに表示されるメールが
>違うものになってしまうのはどうしてですか?
>また受信したメールのリスト(リストビュー)をクリックしたのですが
>リストビューに表示された件名の内容とMemoに表示されるメールが
>違うものになってしまうのはどうしてですか?
この質問の追加情報なのですが、1つずつ件名と本文がずれて
表示されてしまっているようです。(実際の件数-1の本文が表示される)
一度受信すると新しいメールが来ても受信しないの方は
全然分からない(解決できない)状態です。(>_<)
一番怪しいのはループの条件に使われているnowUidl.Count - 1だと
思うのですがにしのさんの書いてくださったコードには
for Count := 1 to nowUidl.Count - 1 doと書いてあるので
間違えじゃないと思っているのですがこれはどうなのですか?
>私の両方の質問
デバッグすると新しいメールが1件来てもCountの値は0(-1される)になるため
このループを抜けてしまうようです。また10件のメールが来ていても
9件しかメールを取り込むことが出来ないため一件ずれてしまうのかなぁって
思うのですが・・・。
>一番怪しいのはループの条件に使われているnowUidl.Count - 1だと
>思うのですがにしのさんの書いてくださったコードには
>for Count := 1 to nowUidl.Count - 1 doと書いてあるので
>間違えじゃないと思っているのですがこれはどうなのですか?
誰だって間違えることはある。
あくまでヒントとして考えなくては。
作っているのはあなたですよ。
>デバッグすると新しいメールが1件来てもCountの値は0(-1される)になるため
>このループを抜けてしまうようです。また10件のメールが来ていても
>9件しかメールを取り込むことが出来ないため一件ずれてしまうのかなぁって
>思うのですが・・・。
場所が特定されているんだったら、修正してやってみればいい
ちなみにfor Count := 1 to nowUidl.Count doで行った所、リストの範囲を
超えるエラーが発生して止まってしまいました。
nowUidl.Countを追加しているところを確認してみれば、間違いが発見できるんじゃないの?
>nowUidl.Countを追加しているところを確認してみれば、間違いが発見できるんじゃないの?
あんまり見当がつかないです。(>_<)
文章が長過ぎて読む気になれないのではっきりしたことはいえないけど、
新着メールを確認するところが起動した時だけとかになってないのかな?
>新着メールを確認するところが起動した時だけとかになってないのかな?
メールの受信を行っているのは起動時ではなくてメニューの受信か
ツールボタンの受信ボタンで行う以外は受信の作業は行っていません。
なので絶対に違うと思います。
ちゃんとした名前で投稿して欲しいかもです。
>やりすぎ&ちゃんと考えた?さん(同一人物?)
昨日の最後の状態から一歩も抜け出せていないです。>最後に出した質問
この解決が出来たら次は本題のファイル化したメールを受信するには?に
入ることが出来そうです。
みなさんへ
文章は短くといって長々とコードを載せてしまうため毎回迷惑をかけちゃって
本当にごめんなさい。出来るだけ短くコードも掲載するように心がけます。
for Count := 0 to NowUidl.Count - 1 doに変更したところ
リストにちゃんと全部のメールが表示されたのですが、にしのさんの
書いてくれたので合っていたのでしょうか?
僕は神ではありませんよ…。
もう少し頭を柔らかくして考えてください。普通はわかると思うのですが。
僕もちょっと今回のことに書き込みすぎましたね。
少し興味のある分野だったので。
# この種のソフトを、ある程度作ったりもしましたし
今後はアドバイス程度にします。
最後に、「ファイル化したメールを表示するには?」についてだけ、アドバイス致します。
ざっと説明すると、まずTFileStreamを使って読み込み、ヘッダ本文を切り分け、本文をSJISに変換し、TStringListに納めます。
TFileStream,TStringListについてはヘルプを参照してください。
SJISに変換するには、NKF32.DLLというDLLがありますし、DelphiコンポーネントにもSJISに変換するものがあります。
ここはもう力業だけです。難しいところは1つもありません。
これがわからないのであれば、別のプログラミング言語を選ぶか、作成をあきらめた方がよいと思います。
以上で、僕はこの件から手を引きます。
にしのさん本当に申し訳ありませんでした。(>_<)
ファイル化したデータなのですが、既に(nMail.dllによって)
Sift-JISに変換されているのですがこの場合はどうすれば良いのでしょうか?
VBやVC++でするとまたこの部分で悩んで長くなりそうなので
ここでがんばって完成まで行きたいと思っています。
ごめんなさい、for Count := 0 to NowUidl.Count - 1 doで
うまくいったと思ったのですが、一度アプリケーションを終了すると
リストに入っていた件名等が消えてしまい新しいメールが来ても
1件しかメールが現れず、リストをクリックすると最初のメールが
メモに表示される事態になってしまいました。
アプリケーション終了時にリストの内容を保存して、起動時に読み込む
のに使うおくのはやはりTStringListが良いのでしょうか?
また他のやり方があるのでしょうか?
下記の事と上記の事、誰かアドバイスだけでよろしいのでお願いします。(>_<)
#選択されたListItemのDataにファイル名があるので、それを読み込みます。
上記の事を試そうとしているのですが、ファイル名が入っていないようで
毎回AccessViolationの例外が発生します。これはどうしてですか?
Listをクリックしたらファイルを読み込むのコードはシンプルにヘッダも
付いたファイルをMemoに読み込むと言うもので以下のコードです。
BodyMemo.Lines.LoadFromFile(String(MailData.Data^));
ツイート | ![]() |