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


阅读更多
个人分类: Python
上一篇PyQt实现一个简单的License系统(一)
下一篇VS调试python中的DLL
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭