1. 程式人生 > >PyQt實現一個簡單的License系統(二)

PyQt實現一個簡單的License系統(二)

    本文接著上一篇繼續講解“PyQt實現一個簡單的License系統”,主要包括:

3)如何用python建立一個GUI。

4)python如何調C DLL庫。

5)ctypes中型別處理。


    上一篇文章只是簡單的將ui檔案轉換為py檔案,並執行,生成了一個原始的GUI。本文將在這個基礎上,運用python編碼豐富這個GUI。


一、介面修飾

    我們希望在介面生成的時候,自動獲取系統時間,並將它轉換為合適的格式,填充到GUI的控制元件:StartDate、DueDate和CurrentDate欄。實現方法如下:

1)匯入時間庫

在mainwindow.py的頂部相關位置新增一行程式碼

from datetime import date
from datetime import timedelta

2)獲取系統時間,並顯示到控制元件

在mainwindow.py檔案的“setupUi”函式中新增如下程式碼:

	#datetime today
        currDate = date.today().strftime('%Y/%m/%d')
        dueDate = (date.today() + timedelta(days=60)).strftime('%Y/%m/%d')
        self.lineEdit_6.setText(currDate)
        self.lineEdit_7.setText(dueDate)
        self.lineEdit_8.setText(currDate)

注意:python的庫非常豐富,它分為標準庫和外部庫。date就是一個標準庫,可以通過python的線上文件檢視。

https://docs.python.org/3/library/functions.html#bytearray


二、新增控制元件響應

    我們需要分別新增“Preview”、“Encrypt”和“Decrypt”三個按鈕的響應函式。

1)在mainwindow.py檔案的“setupUi”函式中(尾部)新增如下程式碼:

        self.retranslateUi(MainWindow)
        self.btnPreview.clicked.connect(self.showplaintext)
        self.btnEncrypt.clicked.connect(self.showencryptresult)
        self.btnDecrypt.clicked.connect(self.showerecovertext)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    如上所示:第一行和最後一行是PyQt生成的程式碼,我們實際新增的是中間那三行。

另外需要注意的是:connect函式也可以在mainwindow.py檔案外指定。

2)定義對應的函式

    上一步綁定了三個控制元件響應函式(等同與Qt中的connect),這一步就是要具體定義這個三個響應函式。

在mainwindow.py檔案的class Ui_MainWindow(object)中新增如下程式碼塊:

    def showplaintext(self):
        print("preview")

		
    def showencryptresult(self):
        print("encrypt")
  
  
    def showerecovertext(self):
        print("decrypt")

這個就是python中類的成員函式的典型定義方式,需要帶“self”引數,相當於C++中的this。

關於python的類和函式,也可以檢視python文件庫:

https://docs.python.org/3.6/tutorial/classes.html

三、GUI互動

    上面只是建立了基本的程式框架,還沒有具體細節。我們先來實現“showplainttext”函式:

    def showplaintext(self):
        print("preview")
        listLabel = [self.label_1, self.label_2, self.label_3, self.label_4,\
        self.label_5, self.label_6, self.label_7, self.label_8]
        listLineEdit = [self.lineEdit_1,self.lineEdit_2,self.lineEdit_3,self.lineEdit_4,\
        self.lineEdit_5,self.lineEdit_6,self.lineEdit_7,self.lineEdit_8]
        
        head = "f0f0,"
        strPlain = head
        
        for n in range(8):
        	strPlain = strPlain + listLabel[n].text();
        	strPlain = strPlain + listLineEdit[n].text();
        	strPlain = strPlain + ',';
        
        strPlain = strPlain + "0f0f";
        self.plaintext.setPlainText(strPlain)

這段程式碼演示了python的列表(list)、for迴圈、字串。

四、python調DLL

    由於“LicenseSystem”要用到加密演算法,而這個加密演算法是一個外部的C++實現的DLL庫,事實上,我對它進行了封裝,匯出兩個函式:一個加密,一個解密。也因此,我們要用到“python調DLL”的技術。

    “python調DLL”的幾種方式中,我推薦用“ctypes”庫。參考文件:

    https://docs.python.org/3/library/ctypes.html

此外,也可以參考部落格:https://zhuanlan.zhihu.com/p/20152309

                                        http://blog.csdn.net/magictong/article/details/3075478

                                        http://blog.csdn.net/magictong/article/details/3075478

    有了“ctypes”,在python中載入DLL庫還是比較簡單的,難點在於“引數和返回值”的型別轉換。在此,我折騰了好幾個小時,才把我需要的型別完全正確轉換完。目前還沒有很深的理解,故不展開說了,請大家仔細研究我上面給出的“ctypes”文件和google。我在此僅貼出程式碼,以供參考:

    def showencryptresult(self):
        print("encrypt")
        #lib = ctypes.WinDLL("EncryptorDll.dll")
        lib = CDLL("EncryptorDll.dll")
        plainText = bytes(self.plaintext.toPlainText(), encoding = "utf-8")
        cPlainText = c_char_p(plainText)
        fileName = self.lineEdit_3.text() + ".license"
        cFileName = c_wchar_p(fileName)
        bufKey = c_char_p(b"\x0A\x0B\x0C\x0D\x0E\x0F\x01\x02\x03\x04\x05\x06\x07\x08\x09\x10")
        bufIV = c_char_p(b"\x0F\x0E\x0D\x0C\x0B\x0A\x10\x09\x08\x07\x06\x05\x04\x03\x02\x01")
        #ret = lib.EncryptString2File(byref(cPlainText), byref(cFileName), byref(bufKey), byref(bufIV), 16)
        ret = lib.EncryptString2File(cPlainText, cFileName, bufKey, bufIV, 16)
        if 1 == ret:
            self.ciphertext.setPlainText("Encrypt succeed!")
        else:
            self.ciphertext.setPlainText("Encrypt failed!")
        
          
    def showerecovertext(self):
        print("decrypt")
        lib = CDLL("EncryptorDll.dll")
        fileName = self.lineEdit_3.text() + ".license"
        filesize = os.path.getsize(fileName)
        print(filesize)
        recover = create_string_buffer(b'\0'*filesize)
        print(recover)
        cRecover = c_char_p(recover.value)
        cFileName = c_wchar_p(fileName)
        bufKey = c_char_p(b"\x0A\x0B\x0C\x0D\x0E\x0F\x01\x02\x03\x04\x05\x06\x07\x08\x09\x10")
        bufIV = c_char_p(b"\x0F\x0E\x0D\x0C\x0B\x0A\x10\x09\x08\x07\x06\x05\x04\x03\x02\x01")
        ret = lib.DecryptFile2String(cFileName, cRecover, bufKey, bufIV, 16)
        if 1 == ret:
            self.recovertext.setPlainText(str(cRecover.value, encoding = "utf-8"))
        else:
            self.recovertext.setPlainText("Encrypt failed!")

注:CDLL對應“cdecl”,而WinDLL對應“stdcall”

被調的C函式原型為:

        bool(*pfnEncrypt)(const char *, const wchar_t *, unsigned char *, unsigned char *, int) = 
            (bool(*)(const char *, const wchar_t *, unsigned char *, unsigned char *, int))m_library.resolve("EncryptString2File");

        bool(*pfnDecrypt)(const wchar_t *, char *, unsigned char *, unsigned char *, int) =
            (bool(*)(const wchar_t *, char *, unsigned char *, unsigned char *, int))m_library.resolve("DecryptFile2String");

    上面的程式碼共涉及到如下幾種型別轉換:

1)QString或string轉char陣列(bytes),然後轉char指標

        plainText = bytes(self.plaintext.toPlainText(), encoding = "utf-8")
        cPlainText = c_char_p(plainText)
2)QString或string轉wchar_t指標
        fileName = self.lineEdit_3.text() + ".license"
        cFileName = c_wchar_p(fileName)

比較上面兩條可知:python預設的str是Unicode編碼,即寬字元編碼(wchar_t)

3)以0結尾的字串常量

        bufKey = c_char_p(b"\x0A\x0B\x0C\x0D\x0E\x0F\x01\x02\x03\x04\x05\x06\x07\x08\x09\x10")

注意,括號裡的“b”是將字串指定為char字元(位元組字元),而預設是Unicode字元。

    也有一種說法:

    這裡的“b”指的是“binary”(二進位制),也就是說,python中的bytes object是以二進位制的形式儲存的。而str是以“文字”形式儲存的,即“Unicode”。

4)返回值bool

        if 1 == ret:
            self.ciphertext.setPlainText("Encrypt succeed!")
        else:
            self.ciphertext.setPlainText("Encrypt failed!")


五、執行

    新增完上述程式碼,再將加密庫“EncryptorDll.dll”檔案與“mainwindow.py”檔案放在同一個目錄下,就已經是一個完整的“LicenseSystem”軟體了。

    同前一篇文章,在cmd中,用python執行“mainwindow.py”,即可啟動該軟體。

六、軟體架構

    儘管這個已經是一個完整的軟體了,但是從架構上來說,它並不是很好。我們是直接在“mainwindow.py”檔案中實現這個功能的,如果後續需要修改介面,即修改“mainwindow.ui”檔案,我們又需要重新編譯生成新的“mainwindow.py”檔案,然後再合併“mainwindow.py”檔案。這樣顯得很麻煩!

    更好的辦法是:保持“mainwindow.py”檔案不變,我們將功能在另外一個檔案中實現。

    這裡我新建了一個“start.py”檔案

import sys
from PyQt5 import QtCore, QtGui, QtWidgets
from mainwindow import Ui_MainWindow
 
class LicenseGui(Ui_MainWindow):
	def __init__(self, mainwindow):
		Ui_MainWindow.__init__(self)
		self.setupUi(mainwindow)
 
		# Connect "add" button with a custom function (addInputTextToListbox)
		#self.addBtn.clicked.connect(self.addInputTextToListbox)
	'''
	def addInputTextToListbox(self):
		txt = self.myTextInput.text()
		self.listWidget.addItem(txt)
	'''
if __name__ == '__main__':
	app = QtWidgets.QApplication(sys.argv)
	mainwindow = QtWidgets.QMainWindow()
 
	prog = LicenseGui(mainwindow)
 
	mainwindow.show()
	sys.exit(app.exec_())

    我們可以將其他功能在這個檔案中實現,而儲存“mainwindow.py”的原生態。然後,在程式執行的時候,從這個檔案啟動。

    從上面的程式可以看出:類LicenseGui實際上是對“mainwindow.py”中原生態的類“Ui_MainWindow”的封裝。