意外にはまってしまったのでメモしておきます。
背景
Windows において、python を1つ上げたら芋ずる式に他のプロセスを上げていきたい。
起動数は固定なので、shell32.dll の ShellExecute を使うことを考えた。
しかし python からこの DLL を利用するにはいくつか tips があるようだ。
問題のあるプログラム
最初は単純に ShellExecute を呼び出せばよいと思い、以下のようなプログラムを書いていました。
from ctypes import windll
shellexecute = windll.shell32.ShellExecuteA
err = shellexecute(None, "open", "./test.exe", None, None, 1)
print(err)
しかし結果は 31 エラーで撃沈。仕方がないので python の ctypes の項を読むと、ASCII 文字列は c_char_p で変換して渡す必要がありそう。さっそく実行。
from ctypes import windll, c_char_p
shellexecute = windll.shell32.ShellExecuteA
err = shellexecute(None, c_char_p(b"open"), c_char_p(b"./test.exe"), None, None, 1)
print(err)
が、これも 31 エラーで弾かれる。
もしかして、区切り記号?
途方に暮れていると、Windows ではフォルダの区切り記号が \ であることに気づいた。やってみよう!
from ctypes import windll, c_char_p
shellexecute = windll.shell32.ShellExecuteA
err = shellexecute(None, c_char_p(b"open"), c_char_p(b".\\test.exe"), None, None, 1)
print(err)
今度はうまく動きました。ShellExecuteW を使いたい場合は、
from ctypes import windll, c_wchar_p
shellexecute = windll.shell32.ShellExecuteW
err = shellexecute(None, c_wchar_p("open"), c_wchar_p(".\\test.exe"), None, None, 1)
print(err)
とすれば OK。
DLL 化して呼び出しを簡略化
でも一々 c_wchar_p を指定するのは面倒なので、DLL に組み込んでしまった。
mycommon.cc
#include <windows.h>
extern "C" __declspec(dllexport) int launch(LPCWSTR file, int mode) {
HINSTANCE hInst = ShellExecuteW(NULL, L"open", file, NULL, NULL, mode);
return GetLastError();
}
mycommon.cc をコンパイルし、mycommon.dll を作成すると、
from ctypes import cdll
launch = cdll["./mycommon"].launch
err = launch(".\\test.exe", 1)
という形で呼び出すことができた。