一、配置python环境问题

1.首先安装Python(版本无所谓),安装的时候选的添加python路径到环境变量中

安装之后的文件夹如下所示:

2.在VS中配置环境和库

右击项目->属性->VC++目录

1)包含目录:

Python安装路径/include

2)库目录:

Python安装路径/libs

右击项目->属性->连接器->输入->附加依赖库

debug下:
python安装目录/libs/python37_d.lib
release下:
python安装目录/libs/python37.lib

注意
1、debug配置的时候可能没有python37_d.lib,那就把python37.lib复制一个,然后重命名为python37_d.lib就可以啦
2、如果一直报错,但是包含头文件等都没有问题,那么你需要看一下你的python是32位还是64位的。然后根据python的环境去配置vs的环境。

二、C++调用python函数并输出返回值

首先可能有个坑!一定要保证这个python函数所在的文件能够正常运行!然后把python代码放到和C++代码同一目录下。

1、定义Python函数

#!python3
# -*- coding:utf-8 -*-
import base64
import  hmac
from hashlib import sha1

def hash_hmac(code , key):
    #sha1加密签名算法
    hmac_code = hmac.new(key.encode() , code.encode() , sha1).digest()
    return base64.b64encode(hmac_code).decode()

2、编写C++代码

#include <iostream>
#include<python.h>
using namespace std;
int main()
{

    Py_Initialize();//使用python之前,要调用Py_Initialize();这个函数进行初始化
    if (!Py_IsInitialized())
    {
        printf("初始化失败!");
        return 0;
    }

    PyRun_SimpleString("import sys");
    PyRun_SimpleString("sys.path.append('./')");//这一步很重要,修改Python路径


    PyObject* pModule = NULL;//声明变量
    PyObject* pFunc = NULL;// 声明变量

    pModule = PyImport_ImportModule("hash_hmac");//这里是要调用的文件名hash_hmac.py
    if (pModule == NULL)
    {
        cout << "没找到" << endl;
    }

    pFunc = PyObject_GetAttrString(pModule, "hash_hmac");//这里是要调用的函数名
    //两个字符串参数
    PyObject* pParams = Py_BuildValue("(ss)", "/oss/upload?bucket=test&filekey=test/image/3b/3ba9d94cab2f8868823d71c4445e125a.png\n" , "q4mJAS777BUbbdVpEqh2XRcZZqNyDweU4GRnM690");
    char* result;
    PyObject* pRet = PyObject_CallObject(pFunc, pParams);//调用函数

    int res = 0;
    PyArg_Parse(pRet, "s", &result);//转换返回类型

    cout << "res:" << result << endl;//输出结果

    Py_Finalize();//调用Py_Finalize,这个根Py_Initialize相对应的。

    return 0;
}

3、解释部分C++代码

PyObject* pParams = Py_BuildValue("(ss)", "/oss/upload?bucket=test&filekey=test/image/3b/3ba9d94cab2f8868823d71c4445e125a.png\n" , "q4mJAS777BUbbdVpEqh2XRcZZqNyDweU4GRnM690");

在这里我输入了两个字符串类型的参数,Py_BuildValue()函数的作用和PyArg_ParseTuple()的作用相反,它将C类型的数据结构转换成Python对象。

该函数可以和PyArg_ParseTuple()函数一样识别一系列的格式串,但是输入参数只能是值,而不能是指针。

它返回一个Python对象和PyArg_ParseTuple()不同的一点是PyArg_ParseTuple()函数它的第一个参数为元组,Py_BuildValue()则不一定会生成一个元组。它生成一个元组仅仅当格式串包含两个或者多个格式单元,如果格式串为空,返回NONE。
在下面的描述中,括号中的项是格式单元返回的Python对象类型,方括号中的项为传递的C的值的类型。
“s” (string) [char *] :将C字符串转换成Python对象,如果C字符串为空,返回NONE。
“s#” (string) [char *, int] :将C字符串和它的长度转换成Python对象,如果C字符串为空指针,长度忽略,返回NONE。
“z” (string or None) [char *] :作用同”s”。
“z#” (string or None) [char *, int] :作用同”s#”。
“i” (integer) [int] :将一个C类型的int转换成Python int对象。
“b” (integer) [char] :作用同”i”。
“h” (integer) [short int] :作用同”i”。
“l” (integer) [long int] :将C类型的long转换成Pyhon中的int对象。
“c” (string of length 1) [char] :将C类型的char转换成长度为1的Python字符串对象。
“d” (float) [double] :将C类型的double转换成python中的浮点型对象。
“f” (float) [float] :作用同”d”。
“O&” (object) [converter, anything] :将任何数据类型通过转换函数转换成Python对象,这些数据作为转换函数的参数被调用并且返回一个新的Python对象,如果发生错误返回NULL。
“(items)” (tuple) [matching-items] :将一系列的C值转换成Python元组。
“[items]” (list) [matching-items] :将一系列的C值转换成Python列表。
“{items}” (dictionary) [matching-items] :将一系类的C值转换成Python的字典,每一对连续的C值将转换成一个键值对。

例如:
Py_BuildValue(“”) None
Py_BuildValue(“i”, 123) 123
Py_BuildValue(“iii”, 123, 456, 789) (123, 456, 789)
Py_BuildValue(“s”, “hello”) ‘hello’
Py_BuildValue(“ss”, “hello”, “world”) (‘hello’, ‘world’)
Py_BuildValue(“s#”, “hello”, 4) ‘hell’
Py_BuildValue(“()”) ()
Py_BuildValue(“(i)”, 123) (123,)
Py_BuildValue(“(ii)”, 123, 456) (123, 456)
Py_BuildValue(“(i,i)”, 123, 456) (123, 456)
Py_BuildValue(“[i,i]”, 123, 456) [123, 456] Py_BuildValue(“{s:i,s:i}”, “abc”, 123, “def”, 456) {‘abc’: 123, ‘def’: 456}
Py_BuildValue(“((ii)(ii)) (ii)”, 1, 2, 3, 4, 5, 6) (((1, 2), (3, 4)), (5, 6))

3、运行C++程序

与Python代码的预期相同。

三、Python代码处理

在发布软件的时候,通常我们都不希望代码可以直接被别人看到。

以上的Debug目录中的exe要想能够单独运行,必须把python脚本拷过去。为了不让别人能直接看到我的代码,我拷过去的是生成的.pyc文件,实现了一个简单的python代码的加密。不过据说可以反编译,但是对我来说已经够了。

四、.py和.pyc的区别

原来Python的程序中,是把原始程序代码放在.py文件里,而Python会在执行.py文件的时候。将.py形式的程序编译成中间式文件(byte-compiled)的.pyc文件,这么做的目的就是为了加快下次执行文件的速度。

所以,在我们运行python文件的时候,就会自动首先查看是否具有.pyc文件,如果有的话,而且.py文件的修改时间和.pyc的修改时间一样,就会读取.pyc文件,否则,Python就会读原来的.py文件。

其实并不是所有的.py文件在与运行的时候都会产生.pyc文件,只有在import相应的.py文件的时候,才会生成相应的.pyc文件。

五、使用.py生成.pyc

在命令行下使用下列命令即可:

python -m py_compile test.py#单文件

python -m py_compile /root/src/{file1,file2}.py#多文件

(1)什么是钩子 我们可以首先从字面上了解钩子,钩子是干什么的呢?日常生活中,我们的钩子是用来钩住某种东西的,比如,说,鱼钩是用来钓鱼的,一旦鱼咬了钩,钩子就一直钩住鱼了,任凭鱼在水里怎么游,也逃不出鱼钩的控制。同样的,Windows的钩子Hook也是用来钩东西的,比较抽象的是他是用来钩Windows事件或者消息的。最常见的就是鼠标和键盘钩子,用Hook钩子钩住鼠标、键盘,当你的鼠标、键盘有任何操作时,通过Hook就能知道他们都做了什么了,多么形象啊,把老鼠Mouse钩住了,不管你干什么,都逃不过我钩子Hook的手掌心。 技术上讲,钩子(Hook)是Windows消息处理机制的一个很重要的内容,谁叫Windows是基于消息的呢。应用程序可以通过钩子机制截获处理Window消息或是其他一些特定事件。 我们可以在同一个钩子上挂很多东西。 想起参加工作前要求被体检的时候,当你被登记之后,按照你的登记表上的顺序,就等着到各个科室一个一个的去检查吧。每一个科室都有决定你是否继续的可能,只有通过了这个,你才可以到下一个去,如果没有通过,那么,你是看不到最后的大夫了,可以直接over回家了。 如果把体检比喻为事件的话,当事件发生时,应用程序(体检过程)可以在相应的钩子Hook上设置多个钩子子程序(Hook Procedures)(多个科室的检查),由其组成一个与钩子相关联的指向钩子函数的指针列表(钩子链表)(体检表,确定了你要走的顺序)。当钩子所监视的消息出现时(你拿着表格来体检了),Windows(导诊员)首先将其送到调用链表中所指向的第一个钩子函数中(体检表上第一个科室,一般是身高体重吧,呵呵),钩子函数将根据其各自的功能(每个科室检查的项目不一样啊)对消息进行监视(有的大夫就随便看看了事)、修改(碰到好心的大夫还可以帮你往好里添点呢,呵呵)和控制(有的大夫好严格啊),并在处理完成后(当然有的大夫就直接把你刷下了,回家吧,没有下一个了)把消息传递给下一钩子函数(下一个项目的科室,当然,也可以强制消息的传递,直接打发你回家)直至到达钩子链表的末尾(检查完了!)。在钩子函数交出控制权后,被拦截的消息最终仍将交还给窗口处理函数(好了,拿着表去上班吧)。 虽然钩子函数对消息的过滤将会略加影响系统的运行效率,但在很多场合下通过钩子对消息的过滤处理可以完成一些其他方法所不能完成的特殊功能。 有道词典的屏幕取词可能就是这么完成的。。。

(2)比较专业的对钩子的技术性理解 钩子(Hook),是Windows消息处理机制的一个平台,应用程序可以在上面设置子程以监视指定窗口的某种消息,而且所监视的窗口可以是其他进程所创建的。当消息到达后,在目标窗口处理函数之前处理它。钩子机制允许应用程序截获处理window消息或特定事件。 Windows系统是建立在事件驱动的机制上的,说穿了就是整个系统都是通过消息的传递来实现的。而钩子是Windows系统中非常重要的系统接口,用它可以截获并处理送给其他应用程序的消息,来完成普通应用程序难以实现的功能。钩子可以监视系统或进程中的各种事件消息,截获发往目标窗口的消息并进行处理。这样,我们就可以在系统中安装自定义的钩子,监视系统中特定事件的发生,完成特定的功能,比如截获键盘、鼠标的输入,屏幕取词,日志监视等等。可见,利用钩子可以实现许多特殊而有用的功能。 钩子实际上是一个处理消息的程序段,通过系统调用,把它挂入系统。每当特定的消息发出,在没有到达目的窗口前,钩子程序就先捕获该消息,亦即钩子函数先得到控制权。这时钩子函数即可以加工处理(改变)该消息,也可以不作处理而继续传递该消息,还可以强制结束消息的传递。 一个Hook都有一个与之相关联的指针列表,称之为钩子链表,由系统来维护。这个列表的指针指向指定的,应用程序定义的,被Hook子程调用的回调函数,也就是该钩子的各个处理子程。当与指定的Hook类型关联的消息发生时,系统就把这个消息传递到Hook子程。一些Hook子程可以只监视消息,或者修改消息,或者停止消息的前进,避免这些消息传递到下一个Hook子程或者目的窗口。最近安装的钩子放在链的开始,而最早安装的钩子放在最后,也就是后加入的先获得控制权。 Windows并不要求钩子子程的卸载顺序一定得和安装顺序相反。每当有一个钩子被卸载,Windows 便释放其占用的内存,并更新整个Hook链表。如果程序安装了钩子,但是在尚未卸载钩子之前就结束了,那么系统会自动为它做卸载钩子的操作。 大多数人或者网上文章认为全局钩子都要依赖于一个DLL才能正常工作的,常常会看到很多人在论坛上长期争论一个话题:“全局钩子一定要在DLL里面吗?”。实际上这里有一个概念的问题,究竟上面提到的全局钩子是指什么。通过对上面各种钩子的作用域的理解就会发现这个问题的答案。 上面一共提到了15种钩子。 钩子作用域: WH_JOURNALPLAYBACK,WH_JOURNALRECORD,WH_KEYBOARD_LL,WH_MOUSE_LL、WH_SYSMSGFILTER这5种钩子本身的作用域就是全局的,不管钩子是直接写在应用程序的代码里还是放在DLL中,他们都能够钩住系统的消息。剩下的10种钩子,他们的作用域既可以是线程的又可以是全局的,当将相应的钩子直接写在应用程序的代码中时,他们只能捕获当前线程上下文的消息。那么他们如何实现捕获全局消息的功能呢?当把钩子写入到一个单独的DLL中再引用后,系统自动将该DLL映射到受钩子函数影响的所有进程的地址空间中,即将这个DLL注入了那些进程,从而达到捕获全局消息的目的。相对来说,前面5种钩子本身就是全局的,是不需要注入的。 因此,对于前面问题的答案就是:要实现捕获全局消息功能的钩子,是否要写在单独的DLL里面,取决于钩子的类型以及相应的作用域。 如果对于同一事件既安装了线程勾子又安装了全局勾子,那么系统会自动先调用线程勾子,然后调用全局勾子。 下面我们开始实现用键盘钩子截获密码等键盘输入。

(1)使用VS创建一个MFC应用程序。

(2)编辑界面,一个开始Hook的按钮,一个结束Hook的按钮,以及一个取消按钮(可有可无)。

(3) 编辑KeyboardHookDlg.cpp,实现截取键盘按键的功能。 分别双击两个按钮,就可以生成它们的事件函数。 开始Hook的按钮事件函数:

void CKeyboardHookDlg::OnBnClickedButton1()
{
	// TODO: 在此添加控件通知处理程序代码
	string str = "start:";
	SaveFile << str << endl;

	//这里调用SetWindowsHookExA()函数,因为hook的实现不是在DLL中,而是直接在KeyboardHookDlg.cpp中实现,所有第4个参数使用GetModuleHandle(NULL)
	glhHook = SetWindowsHookExA(WH_KEYBOARD_LL, KeyboardProc, GetModuleHandle(NULL), NULL);
	if (glhHook != NULL) {
		//AfxMessageBox(L"StartHook成功!");//用于打桩测试,通过后注释掉,不然太麻烦
	}
	else {
		AfxMessageBox(L"StartHook失败!");
	}
}

结束Hook的按钮事件函数:

void CKeyboardHookDlg::OnBnClickedButton2()
{
	// TODO: 在此添加控件通知处理程序代码
	UnhookWindowsHookEx(glhHook);
}

同时需要在KeyboardHookDlg.cpp文件头部添加库函数和全局变量:

#include <iostream>  
#include <string>  
#include <fstream>
 
using namespace std;
 
//全局变量
HHOOK glhHook = NULL;			//安装的鼠标勾子句柄 
BOOL g_bCapsLock = FALSE;		//大小写锁定键	
BOOL g_bShift = FALSE;			//shift键
ofstream SaveFile("key.txt")

(4)编译并运行,然后点击Hook按钮,这时随便输入一个密码,再点击结束Hook按钮, 在目录下会生成“key.txt”,其中就记录着你的按键信息。

目前代码只实现了区分大小写字母、数字、数字小键盘输入,其他的按键如标点符号等暂时没有处理。

项目地址(VS2019):https://github.com/maplefan/EasyKeyboardHook