1. 程式人生 > >Python搭建呼叫本地dll的Windows服務(瀏覽器可以訪問,附測試dll64位和32位檔案)

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