3流プログラマのメモ書き

元開発職→社内SE→派遣で営業支援の三流プログラマのIT技術メモ書き。 このメモが忘れっぽい自分とググってきた技術者の役に立ってくれれば幸いです。(jehupc.exblog.jpから移転中)

(.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 = "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:\"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);