人気ブログランキング | 話題のタグを見る
(.Net,C++)Process.Start()やShellExecuteEx()で引数の文字列が長すぎるとエラーになる
メールアドレスを表示するグリッドで CheckboxColumn でチェックされたメールを、Process.Start() を使い一括メールを送るというようにしていました。

ところが、この送信先メールアドレスの文字列が長くなると例外が発生したのです。
(ちなみに、普通は大量のメールアドレスに送ろうとするとプロバイダやメールサーバで蹴られるですが、仕様が既に決められてるのでこの方式にしてます)

例外を起こしたのは、Process.Start(String) でした。
例外内容は下記の通りです。

System.ComponentModel.Win32Exception が発生しました。
ErrorCode=-2147467259
Message="システム コールに渡されるデータ領域が小さすぎます。"
NativeErrorCode=122
Source="System"
StackTrace:
場所 System.Diagnostics.Process.StartWithShellExecuteEx(ProcessStartInfo startInfo)

ちなみに、XP だとアクセス拒否的なエラーになってしまうようです。

それで MSDN 見ると、ProcessStartInfo.Arguments(アプリケーションの引数)は2003文字という制限があるようですが、今回指定しているのは、ProcessStartInfo.FileName のほうなので、この影響は受けないはずです。

で、MSから.Netのソースを落としてきてデバッガでおっかけるとどうやら、Process.Start() は WindowsAPI の ShellExecuteEx() をラッパしているだけということが分かりました。

ということで、C++ のプロジェクトを作り ShellExecuteEx() の挙動を調べてみました。
下記のような感じです。

//shellテスト
SHELLEXECUTEINFO shellexe;
memset( &shellexe, 0, sizeof(SHELLEXECUTEINFO) );
shellexe.cbSize = sizeof(SHELLEXECUTEINFO);
shellexe.fMask = SEE_MASK_NOCLOSEPROCESS;
shellexe.hwnd = HWND_DESKTOP;
shellexe.lpParameters = NULL;
shellexe.lpDirectory = NULL;
shellexe.nShow = SW_SHOWNORMAL;
shellexe.lpFile = "mailto:aaaa.ne@ne.jp;";
int i = 0;
CString add =shellexe.lpFile ;
for (i = 0 ; i < 2020 ; i++){
add = add + "x" ;
}
shellexe.lpFile = add ;
DWORD err = 0;
ShellExecuteEx( &shellexe );
err = GetLastError();

やはり、 ShellExecuteEx() でも2000文字強を超えると Process.Start と同じ例外が発生しました。

ということで、ShellExecuteEx() は2000文字超のファイル名は扱えないみたいです。
(エラー内容がシステムコール云々とかあるので、OSの深いところと関係があるんでしょうか。)


さて、ShellExecuteEx() や Process.Start() が使えないことはわかったので、代替策を考えないといけません。


そこで、CreateProcess() を使って見ることにしました。
ただ、CreateProcess() は mailto: を解釈してメーラーを立ち上げたりすることはできません。
純粋に指定されたプロセスを立ち上げることしかできません。
しかし、大抵のメーラーは引数で mailto: を含む文字列を渡されるとそれに基づいてメール作成画面を開いてくれます。

この引数の書式は、レジストリから各メーラーの値を取得すれば分かります。
(このとき、Vista だと既定のメーラーか mailto: 実行時のメーラーかという問題がでます。詳しくはhttp://jehupc.exblog.jp/9727243/で書いてます。)

ということで、CreateProcess() を使ってメール作成画面を立ち上げるコードです。
なお、メーラーの exe へのパスは埋め込んでます。本来はここはレジストリから取り出した値を使うべきですね。

int i = 0;
CString exe = "C:\\Program Files\\Windows Mail\\WinMail.exe";
CString args = " /mailurl:\"mailto:aaaaa.ne@ne.jp;";
for (i = 0 ; i < 2020 ; i++){
args = args + "x";
}
args = args + "\"";
//CString から LPTSTR への変換
LPTSTR lpsz = new TCHAR[args.GetLength()+1];
_tcscpy(lpsz, args);
delete lpsz;
 
BOOL bRet;
STARTUPINFO si;
PROCESS_INFORMATION pi;

/* 前準備 */
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
 
/* メーラーを起動 */
bRet = CreateProcess( exe,
lpsz,
NULL,
NULL,
FALSE,
0,
NULL,
NULL,
&si,
&pi);
DWORD err = 0;
if(bRet == FALSE){
_tprintf(_T("CreateProcess Error: %d\n"),
GetLastError());
err = GetLastError();
}

/* スレッドとプロセスのハンドルを閉じる */
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);

by jehoshaphat | 2009-03-03 22:06 | .Net開発


<< Android をノートPC(... (JavaScript)マウス... >>