PyQt implements a simple license system (2)

    This article continues the previous article to explain "PyQt implements a simple license system", which mainly includes:

3) How to create a GUI with python.

4) How does python adjust the C DLL library.

5) Type handling in ctypes.


    The previous article simply converted the ui file into a py file and executed it to generate a primitive GUI. This article will use python coding to enrich the GUI on this basis.



1. Interface modification

    We hope to automatically obtain the system time when the interface is generated, convert it into a suitable format, and fill it with the GUI controls: StartDate, DueDate and CurrentDate columns. The implementation method is as follows:

1) Import the time library

Add a line of code at the top of mainwindow.py where relevant

from datetime import date
from datetime import timedelta

2) Get the system time and display it to the control

Add the following code to the "setupUi" function of the mainwindow.py file:

	#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)


Note: python's library is very rich, it is divided into standard library and external library. date is a standard library, which can be viewed through python's online documentation.

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


2. Add control response

    We need to add the response functions for the three buttons "Preview", "Encrypt" and "Decrypt" respectively.

1) Add the following code in the " setupUi " function (tail) of the mainwindow.py file:

        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)

    As shown above: the first and last lines are the code generated by PyQt, and we actually add the middle three lines.

Another thing to note is that the connect function can also be specified outside the mainwindow.py file.


2) Define the corresponding function

    The previous step bound three control response functions (equivalent to connect in Qt), this step is to specifically define the three response functions.

Add the following code block to the class Ui_MainWindow(object) in the mainwindow.py file:

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

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

This is the typical way of defining member functions of classes in python, which requires the "self" parameter, which is equivalent to this in C++.

关于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!")

Note: CDLL corresponds to "cdecl" and WinDLL corresponds to "stdcall"

The prototype of the called C function is:

        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");

    The above code involves the following types of conversions:

1) QString or string to char array (bytes), and then to char pointer

        plainText = bytes(self.plaintext.toPlainText(), encoding = "utf-8")
        cPlainText = c_char_p(plainText)
2) QString or string to wchar_t pointer

        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”的封装。


Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324818072&siteId=291194637