Python搭建呼叫本地dll的Windows服務(瀏覽器可以訪問,附測試dll64位和32位檔案)
一、前言說明
部落格宣告:此文連結地址https://www.cnblogs.com/Vrapile/p/14113683.html,請尊重原創,未經允許禁止轉載!!!
1. 功能簡述
(1)本文提供生成好的測試dll檔案,提供用Python呼叫dll生成windows服務介面的方法,在瀏覽器可以開啟的樣例。
(2)網上有呼叫dll的文章,有生成dll的文章,如果僅僅是嘗試做python呼叫dll的開發,還需要花時間下載visual studio去生成dll
網上基本下載不到測試用的dll,因此整理此文章供讀者參閱
(3)參照1:Python之Windows服務,參照2:python實現編寫windows服務,參照3:vs2019生成64位dll(動態連結庫)並用python3.7呼叫
(4)提供我封裝好的dll路徑:點選下載dll(原文連結長期可以訪問,如果不能訪問很可能是未經允許轉載或者複製的)
2. 我的開發環境
(1)Python3.8語言,Pycharm工具,Windows10環境
二、介面原始碼設計
1. PyCharm介面及專案路徑
1. 檔案結構說明
(1)MyService是專案名字
(2)build、disk兩個資料夾編譯啟動服務會自動生成
(3)dll路徑資料夾,放dll檔案
(4)log路徑資料夾,放日誌檔案,沒有會自動建立
(5)service包資料夾,主要開發位置,呼叫dll和業務邏輯等都在此資料夾下
(6)test包資料夾,放測試程式碼等,可忽略
(7)util包資料夾,封裝一些工具類
(8)DllService.py,系統主入口檔案,只需要將service包資料夾下的內容引用到此檔案的"SvcStop"裡即可
(9)DllService.spec,編譯啟動服務會自動生成
(10)start.bat,編譯專案生成服務的啟動命令,啟動方法要用管理員許可權
cd進入檔案目錄,./start.bat執行指令碼,如下圖:
2. 執行成功效果如下圖:
3. 服務未成功生成,直接看cmd視窗報錯資訊,如果是生成的服務已停止,說明服務啟動報錯了,檢視報錯原因方法:
win10設定——控制面板——管理工具——事件檢視器——Windows日誌——應用程式——篩選當前日誌(勾選錯誤)
2. 檔案程式碼
(1)DllService.py,生成服務檔案,此檔案基本不用大改動
1 # -*- coding:utf-8 -*- 2 import win32timezone # 雖然看起來沒被應用,但是不能少,否則啟動服務會報錯 3 import win32serviceutil 4 import win32service 5 import win32event 6 import servicemanager 7 import os 8 import sys 9 from util import logger 10 11 12 class DllService(win32serviceutil.ServiceFramework): 13 _svc_name_ = 'DllService' # 服務名,服務啟動停止唯一標識 14 _svc_display_name_ = 'DllService' # 服務顯示名稱,可與服務名不同 15 _svc_description_ = 'DLL本地服務-測試瀏覽器呼叫' # 服務描述,可任意填寫 16 17 def __init__(self, args): 18 win32serviceutil.ServiceFramework.__init__(self, args) 19 self.stop_event = win32event.CreateEvent(None, 0, 0, None) 20 self.run = True 21 22 23 def SvcDoRun(self): 24 from service.TestService import TestService 25 logger.info(" Service is running ") 26 TestService.run(port=12345) 27 28 29 def SvcStop(self): 30 self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING) 31 win32event.SetEvent(self.stop_event) 32 self.ReportServiceStatus(win32service.SERVICE_STOPPED) 33 self.run = False 34 logger.info(" Service is stopped ") 35 36 37 if __name__ == '__main__': 38 if len(sys.argv) == 1: 39 try: 40 event_src_dll = os.path.abspath(servicemanager.__file__) 41 servicemanager.PrepareToHostSingle(DllService) # 此處是檔名 42 servicemanager.Initialize('DllService', event_src_dll ) # 此處是服務名 43 servicemanager.StartServiceCtrlDispatcher() 44 except win32service.error as details: 45 import winerror 46 if details == winerror.ERROR_FAILED_SERVICE_CONTROLLER_CONNECT: 47 win32serviceutil.usage() 48 else: 49 win32serviceutil.HandleCommandLine(DllService) # 此處是檔名
(2)TestService.py,具體服務檔案
CORS(TestService, supports_credentials=True) 解決瀏覽器訪問跨域問題
因為sys.path[0]地址為:E:\Python\MyService\dist\DllService\base_library.zip,因此需要返回3級回到主目錄boot_path
此處因為我本地Python環境為64位,因此只能呼叫64位dll,32位dll呼叫後續如果有人想看再更新
1 # -*- coding:utf-8 -*- 2 import os 3 import sys 4 from flask import Flask, request 5 from flask_cors import * 6 import traceback 7 import json 8 import ctypes 9 from util import ResponseMessage, logger 10 11 12 TestService = Flask(__name__) 13 CORS(TestService, supports_credentials=True) 14 15 boot_path = os.path.abspath(os.path.join(sys.path[0], "../../..")) 16 17 18 # ajaxJson["src"] = "http://127.0.0.1:12345/test"; 19 # ajaxJson["data"] = {"data":"{\"aaa\":123,\"bbb\":\"456\"}"}; 20 # JL.ajax(ajaxJson); 21 @TestService.route("/test", methods=['GET', 'POST']) 22 def test(): 23 # noinspection PyBroadException 24 try: 25 data = request.values.get('data') 26 data_dict = json.loads(data) 27 logger.info("request:" + str(data_dict)) 28 dll_path = os.path.join(os.path.join(boot_path, "dll"), "MyDll64.dll") 29 windll = ctypes.windll.LoadLibrary(dll_path) 30 result_add = windll.myAdd(int(data_dict["aaa"]), int(data_dict["bbb"])) 31 result_max = windll.myMax(int(data_dict["aaa"]), int(data_dict["bbb"])) 32 return_data = dict() 33 return_data["add"] = result_add 34 return_data["max"] = result_max 35 return_data["dll_path"] = dll_path 36 except: 37 logger.error("error:" + traceback.format_exc()) 38 return ResponseMessage.error(traceback.format_exc()) 39 logger.info("response:" + str(ResponseMessage.ok(return_data))) 40 return ResponseMessage.ok(return_data)
3. logger.py,日誌工具封裝檔案
如上文中引用方式:from util import logger, 使用方式:logger.info(" Service is stopped ")
此文已更改日誌儲存路徑為專案根路徑的log下,沒有會自動建立
1 #!/usr/bin/python 2 # -*- coding: utf-8 -*- 3 """ 4 Created on Feb 23, 2020 5 @author: Vrapile 6 """ 7 import sys 8 import os 9 import datetime 10 import logging 11 from logging.handlers import RotatingFileHandler 12 13 14 boot_path = os.path.abspath(os.path.join(sys.path[0], "../../..")) 15 16 logger = logging.getLogger(__name__) 17 logger.setLevel(level=logging.INFO) 18 log_path = os.path.join(boot_path, 'log') 19 if not os.path.exists(log_path): 20 os.makedirs(log_path) 21 handler = RotatingFileHandler(os.path.join(log_path, '%s.log' % datetime.date.today()), maxBytes=5 * 1024 * 1024, backupCount=10) 22 handler.setLevel(level=logging.INFO) 23 formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') 24 handler.setFormatter(formatter) 25 logger.addHandler(handler) 26 27 28 def debug(text): 29 logger.debug(text) 30 31 32 def info(text): 33 logger.info(text) 34 35 36 def warning(text): 37 logger.warning(text) 38 39 40 def error(text): 41 logger.error(text) 42 43 44 def critical(text): 45 logger.critical(text)
4. ResponseMessage.py提供標準化響應json資料,引用使用方式見TestService.py
1 #!/usr/bin/python3 2 # -*- coding: utf-8 -*- 3 """ 4 Created on Feb 23, 2020 5 @author: Vrapile 6 """ 7 import json 8 9 10 def ok(data, code=200): 11 return_dict = dict() 12 return_dict["code"] = code 13 return_dict["success"] = True 14 return_dict["data"] = data 15 return_dict["message"] = None 16 return_dict["flag"] = 0 17 return json.dumps(return_dict, ensure_ascii=False) 18 19 20 def error(message, code=500): 21 return_dict = dict() 22 return_dict["code"] = code 23 return_dict["success"] = False 24 return_dict["data"] = None 25 return_dict["message"] = message 26 return_dict["flag"] = 1 27 return json.dumps(return_dict, ensure_ascii=False) 28 29 30 def res(data): 31 if data[0]: 32 return ok(data[1]) 33 else: 34 return error(data[1])
5. start.bat檔案,封裝編譯啟動命令,需要管理員許可權的cmd,啟動方法上文已有說明
其中等待3秒是必須的,等待釋放檔案資源,否則刪除dist和build基本都會報錯
1 sc stop DllService 2 sc delete DllService 3 :: 等待3秒 4 TIMEOUT /T 3 5 rmdir /s/q dist 6 rmdir /s/q build 7 del DllService.spec 8 pyinstaller DllService.py 9 dist\DllService\DllService.exe install 10 sc start DllService 11 TIMEOUT /T 30
三、總結
1. 瀏覽器訪問地址:http://127.0.0.1:12345/test?data={"aaa":123,"bbb":456}
2. 通過Python呼叫dll,提供本地瀏覽器訪問是可以實現的,js訪問效果如下:
3. 主要解決現在很多銀行都是提供dll檔案支付,而公司業務又是瀏覽器系統,不能直接訪問本地dll,因此此文方案可以解決此類問題
&n