使用 Nuitka 将 Python 程序编译为 Windows 服务
原来 Nuitka 商业版通过插件已经实现了编译 Windows service 的功能,但开源版本不提供此功能。掏钱是不可能掏钱的。本人 fork 了 Nuitka 项目,加入了编译 Windows service 的功能,只要在编译时加入 --windows-service
参数就将 Python 程序能构建成 Windows service 了:
shellpip install nuitka-winsvc
nuitka --onefile --windows-service --windows-service-name=myservice main.py
在前文《Nuitka 编译时注入自定义 C 代码》中介绍了在 Nuitka 编译时注入自定义代码的方法。本文将介绍如何通过这种方法将 Python 程序编译成 Windows 服务。
首先,假设我们用下面的命令将 Python 程序编译成普通的 Onefile 可执行文件:
shellnuitka --standalone --output-dir=build --onefile main.py
接下来,需要修改 OnefileBootstrap.cpp 的代码。有两种方法:一是修改编译时创建的 build\main.onefile-build\static_src\OnefileBootstrap.cpp
;二是直接修改 Nuitka 源代码中的 nuitka/build/static_src/OnefileBootstrap.c 模板文件。定位到该文件的第 778 行(代码所在行数可能因不同版本的 Nuitka 而有所不同),代码如下:
c++#if defined(_WIN32)
int wmain(int argc, wchar_t **argv) {
#else
将这行代码替换为:
c++#pragma comment(lib, "advapi32.lib")
#define SERVICE_NAME L"YourServiceName"
SERVICE_STATUS_HANDLE hSvcStatus;
SERVICE_STATUS svcStatus;
int startup(int, wchar_t**);
DWORD PrintError(const wchar_t* fnName)
{
LPWSTR errorMessage = NULL;
DWORD errorCode = GetLastError();
FormatMessageW(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
errorCode,
MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US),
(LPWSTR)&errorMessage,
0, NULL);
wprintf(L"%s failed (%d): %s", fnName, errorCode, errorMessage);
LocalFree(errorMessage);
return errorCode;
}
DWORD SvcInstall(LPCWSTR progName)
{
SC_HANDLE hSCManager = OpenSCManagerW(NULL, NULL, SC_MANAGER_ALL_ACCESS);
if (NULL == hSCManager) {
return PrintError(L"OpenSCManager");
}
SC_HANDLE hService = CreateServiceW(
hSCManager,
SERVICE_NAME,
SERVICE_NAME,
SERVICE_ALL_ACCESS,
SERVICE_WIN32_OWN_PROCESS,
SERVICE_DEMAND_START,
SERVICE_ERROR_NORMAL,
progName,
NULL, NULL, NULL, NULL, NULL);
if (NULL == hService) {
CloseServiceHandle(hSCManager);
return PrintError(L"CreateService");
}
else wprintf(L"Service installed successfully\n");
CloseServiceHandle(hService);
CloseServiceHandle(hSCManager);
return NO_ERROR;
}
DWORD SvcUninstall()
{
SC_HANDLE hSCManager = OpenSCManagerW(NULL, NULL, SC_MANAGER_ALL_ACCESS);
if (NULL == hSCManager) {
return PrintError(L"OpenSCManager");
}
SC_HANDLE hService = OpenServiceW(hSCManager, SERVICE_NAME, DELETE);
if (hService == NULL) {
CloseServiceHandle(hSCManager);
return PrintError(L"OpenService");
}
if (!DeleteService(hService)) {
CloseServiceHandle(hService);
return PrintError(L"DeleteService");
}
else wprintf(L"Service uninstalled successfully\n");
CloseServiceHandle(hService);
CloseServiceHandle(hSCManager);
return NO_ERROR;
}
VOID ReportSvcStatus(DWORD dwCurrentState, DWORD dwWin32ExitCode, DWORD dwWaitHint)
{
static DWORD dwCheckPoint = 1;
svcStatus.dwCurrentState = dwCurrentState;
svcStatus.dwWin32ExitCode = dwWin32ExitCode;
svcStatus.dwWaitHint = dwWaitHint;
if (dwCurrentState == SERVICE_START_PENDING)
svcStatus.dwControlsAccepted = 0;
else svcStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
if ((dwCurrentState == SERVICE_RUNNING) ||
(dwCurrentState == SERVICE_STOPPED))
svcStatus.dwCheckPoint = 0;
else svcStatus.dwCheckPoint = dwCheckPoint++;
SetServiceStatus(hSvcStatus, &svcStatus);
}
VOID WINAPI SvcCtrlHandler(DWORD dwCtrl)
{
switch (dwCtrl) {
case SERVICE_CONTROL_STOP:
ReportSvcStatus(SERVICE_STOP_PENDING, NO_ERROR, 0);
cleanupChildProcess(true);
ReportSvcStatus(svcStatus.dwCurrentState, NO_ERROR, 0);
return;
case SERVICE_CONTROL_INTERROGATE:
break;
default:
break;
}
}
VOID WINAPI SvcMain(DWORD dwArgc, LPSTR* lpszArgv)
{
hSvcStatus = RegisterServiceCtrlHandlerW(SERVICE_NAME, SvcCtrlHandler);
if (!hSvcStatus) {
return;
}
svcStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
svcStatus.dwServiceSpecificExitCode = 0;
ReportSvcStatus(SERVICE_START_PENDING, NO_ERROR, 100);
ReportSvcStatus(SERVICE_RUNNING, NO_ERROR, 0);
startup(dwArgc, (wchar_t **)lpszArgv);
ReportSvcStatus(SERVICE_STOPPED, NO_ERROR, 0);
}
int wmain(int argc, wchar_t** argv)
{
if (argc == 2) {
if (wcscmp(argv[1], L"install") == 0) {
return SvcInstall(argv[0]);
}
else if (wcscmp(argv[1], L"uninstall") == 0) {
return SvcUninstall();
}
return wprintf(L"Invalid argument %s", argv[1]);
}
SERVICE_TABLE_ENTRYW serviceTable[] = {
{ (LPWSTR)SERVICE_NAME, (LPSERVICE_MAIN_FUNCTIONW)SvcMain },
{ NULL, NULL }
};
StartServiceCtrlDispatcherW(serviceTable);
}
int startup(int argc, wchar_t **argv) {
以上代码因为使用了 #pragam
指令,所以必须使用 MSVC 进行编译。可以按需求将 "YourServiceName"
修改成自己的服务名称。
最后,使用相同的 nuitka 命令参数进行编译,得到 main.exe 可执行文件。该程序有两个参数,分别是 install 和 uninstall 。无法向 Python 脚本传递原本的命令行参数,可以自行修改代码实现。
安装服务:
shell.\main.exe install
删除服务:
shell.\main.exe uninstall