一つ目の問題を直そうと思ったらEmacsのソースコードを修正してCreateProcessWを使うようにすべきでしょう。自分でビルドせず配布されているバイナリを使いたいのであれば諦めるよりありません。無理矢理何とかする方法も思いつかなくはないですが(他のプログラム、DLLを経由するとか、@でファイルの中身を引数に挿入する仕組みを使うとか)、止めておきます。
二つ目の問題は、事前に文字列をCP932に変換することで回避できます。
三つ目の問題は、 w32-quote-process-args
をnilにしてEmacsの問題のある引数処理を抑制しつつ自分で引数を処理することで回避できます。
一つ目はともかく、二つ目と三つ目はEmacs Lispのレベルで何とかできそうです。
Webを探したところ、次のようなページが見つかりました。
UTF-8 をベースとして利用するための設定 - NTEmacs @ ウィキ - atwiki(アットウィキ)
「UTF-8をベースとして」ということですが、CP932のままで使用しようとした場合も結局は同じ問題が発生します(特にダメ文字問題)。
というわけで、そのページの下の方で紹介されているコードを試したところ、概ね問題が解消しました。
「概ね」と書いたのは完全には解消していなかったからです。
そのコードを詳しく見てみると、肝心のコマンドライン引数処理の部分が非常にシンプルな正規表現置換でした。「はて、本当にそれで良いのかな?」と思って色々調べたところ少しだけ改善の余地がありました。
その辺りとともに全体的に自分なりに整理して沢山コメントを入れたのが次のコードです。
(defun my-procargfix-cygwin-program-p-no-cache (filename)
"FILENAMEがCygwinのプログラムならtを返します。(キャッシュ不使用)"
(with-temp-buffer
(let ((w32-quote-process-args nil))
(when (eq (call-process "ldd" nil t nil (concat "\"" filename "\""))
0)
(goto-char (point-min))
(number-or-marker-p
(re-search-forward "cygwin[0-9]+\.dll" nil t))))))
(defvar my-procargfix-fullpath-cache nil)
(defvar my-procargfix-ldd-cache nil)
(defun my-procargfix-cygwin-program-p (filename)
"FILENAMEがCygwinのプログラムならtを返します。(キャッシュ使用)"
(let ((abs-fname (and (stringp filename)
(or (cdr (assoc filename my-procargfix-fullpath-cache))
(cdar (push (cons filename (executable-find filename)) my-procargfix-fullpath-cache)))
)))
(when abs-fname
(or
(cdr (assoc abs-fname my-procargfix-ldd-cache))
(let ((cyg-p (my-procargfix-cygwin-program-p-no-cache abs-fname)))
(push (cons abs-fname cyg-p) my-procargfix-ldd-cache)
cyg-p)))))
(defun my-procargfix-clear-cache ()
(interactive)
(setq my-procargfix-fullpath-cache nil)
(setq my-procargfix-ldd-cache nil))
(defun my-procargfix-quote-for-cygwin (arg)
"Cygwinプログラムへの引数ARGを二重引用符で囲みます。"
(cond
((and (>= (length arg) 2)
(= (elt arg 0) ?\\)
(/= (elt arg 1) ?\\)
(/= (elt arg 1) ?\"))
(concat "\""
(substring arg 0 2)
(replace-regexp-in-string "[\\\\\"]" "\\\\\\&" arg t nil nil 2)
"\""))
((and (>= (length arg) 2)
(or (<= ?A (elt arg 0) ?Z)
(<= ?a (elt arg 0) ?z))
(= (elt arg 1) ?:))
(concat (substring arg 0 1)
"\""
(replace-regexp-in-string "[\\\\\"]" "\\\\\\&" arg t nil nil 1)
"\""))
(t
(concat "\""
(replace-regexp-in-string "[\\\\\"]" "\\\\\\&" arg t)
"\"")))
)
(defun my-procargfix-quote-for-windows (arg)
"通常のWindowsプログラムへの引数ARGを二重引用符で囲みます。"
(concat "\""
(replace-regexp-in-string "\\(\\\\*\\)\"" "\\1\\1\\\\\"" arg)
"\""))
(defun my-procargfix-convert-prog-args (prog-name prog-args)
"コマンドPROG-NAMEに引き渡す引数リストPROG-ARGSを変換します。"
(setq prog-args
(mapcar (if (my-procargfix-cygwin-program-p prog-name)
#'my-procargfix-quote-for-cygwin
#'my-procargfix-quote-for-windows)
prog-args))
(setq prog-args
(mapcar (lambda (arg)
(if (multibyte-string-p arg)
(encode-coding-string arg 'cp932)
arg))
prog-args))
prog-args)
(defun my-procargfix-apply (orig-fun fun-args prog-pos args-pos)
"子プロセスを呼び出す関数をコマンド引数部分を修正して呼び出します。
呼び出す関数はORIG-FUN、その関数に引き渡す引数はFUN-ARGS、プログ
ラム名はFUN-ARGSのPROG-POS番目、コマンド引数部分はFUN-ARGSの
ARGS-POS番目以降です。"
(when w32-quote-process-args
(setf (nthcdr args-pos fun-args)
(my-procargfix-convert-prog-args
(nth prog-pos fun-args)
(nthcdr args-pos fun-args))))
(when w32-quote-process-args
(let ((prog-name (nth prog-pos fun-args)))
(when (seq-contains-p prog-name ? #'eq)
(setf (nth prog-pos fun-args)
(or (w32-short-file-name prog-name) prog-name)))))
(let ((w32-quote-process-args nil))
(apply orig-fun fun-args)))
(defmacro my-procargfix-add-advice (target-func prog-pos args-pos)
(let ((ad-func
(intern (format "my-procargfix-advice--%s" target-func))))
`(progn
(defun ,ad-func (orig-fun &rest fun-args)
(my-procargfix-apply orig-fun fun-args ,prog-pos ,args-pos))
(advice-add (quote ,target-func)
:around
(quote ,ad-func)
'((depth . 99))))))
(my-procargfix-add-advice call-process 0 4)
(my-procargfix-add-advice call-process-region 2 6)
(my-procargfix-add-advice start-process 2 3)
テスト:
#include <iostream>
int main(int argc, char *argv[])
{
for(int i = 0; i < argc; ++i){
std::cout << i << ":" << argv[i] << std::endl;
}
}
(defun my-procargfix-test-exec (program arg)
"PROGRAMをコマンドライン引数としてARGを与えて起動する。表示された引数文字列を回収してリストで返す。"
(let (
(coding-system-for-read 'cp932-dos)
(buffer (get-buffer-create "*Output*")))
(with-current-buffer buffer
(delete-region (point-min) (point-max))
(call-process (expand-file-name program) nil buffer t arg)
(goto-char (point-min))
(cl-loop while (re-search-forward "^[0-9]+:\\(.*\\)$" nil t)
collect (match-string 1)))))
(defun my-procargfix-test-exec-exam (program arg)
(equal (cdr (my-procargfix-test-exec program arg))
(list arg)))
(let ((test-cases
'("abc"
"Program Files"
"abc\"\" \\\\\\\""
"abc\"def\"\"ghi\"\"\"jkl\"\"\"\"mno"
"abc\\def\\\\ghi\\\\\\jkl\\\\\\\\mno"
"abc'def''ghi'''jkl''''mno"
"\\sABC\\s"
"\\sABC"
"\\\"sABC"
"\\\\sABC"
"c:\\sABC\\s\"DEF\""
""
"表示"
"\\表\\示"
"*.el"
"~/"
))
(ok 0)
(ng 0))
(dolist (program '("echoargs-vc.exe" "echoargs-cyg.exe"))
(dolist (arg test-cases)
(if (my-exec-test program arg)
(cl-incf ok)
(cl-incf ng)
(message "Fail %s" arg))))
(message "OK:%s, NG:%s" ok ng))