当前位置: 首页 > news >正文

C++函数在库中的地址

本文讲述C++如何直接调用动态库dll或者so中的函数。

首先我们准备一个被调用库,这个库里面有两个函数,分别是C++98 与 C++11 下的,名称是run2和run1。

被调用库

相关介绍请看之前的文章《函数指针与库之间的通信讲解》。

//dll_ex_im.h
#ifndef __DLL_EX_IM_H__
#define __DLL_EX_IM_H__
#include <functional>
#include <string>
#include <iostream>
#include <thread>
#include <chrono>
#ifdef _WINDOWS
#ifdef DLLProvider
#define DLL_EXPORT_IMPORT		 __declspec(dllexport)  
#else  
#define DLL_EXPORT_IMPORT		 __declspec(dllimport)  
#endif  
#else
#define DLL_EXPORT_IMPORT __attribute__((visibility("default")))
#endif//typedef class DLL_EXPORT_IMPORT std::function< void(std::string)> output_to_caller;
DLL_EXPORT_IMPORT void run1(int a, std::function< void(std::string)> output);
DLL_EXPORT_IMPORT void run2(int a, void(*output)(std::string));#endif //__DLL_EX_IM_H__
//dll_ex_im.cpp
#include "dll_ex_im.h"void run1(int a, std::function< void(std::string)> output)
{std::cout << "run1" << std::endl;std::cout << "got parametrer: " << a << std::endl;std::cout << "return signal: " << std::endl;while (true) {output("run1: " + std::to_string(a++));std::this_thread::sleep_for(std::chrono::milliseconds(1000));}
}void run2(int a, void(*output)(std::string)) {std::cout << "run2" << std::endl;std::cout << "got parametrer: " << a << std:bian

编译一下,出来的动态库在Windows下是Reflection-DLL_TEST.dll,在Linux下是libReflection-DLL_TEST.so。

Windows下直接调用

我们用BinaryViewer这款二进制查看器看看函数run1和run2在Reflection-DLL_TEST.dll长什么样子。

查找函数run1的位置:

找到三个位置,前两个应该都是函数的名称指引(知道的同学可以介绍下前两个是做啥的)。
第三个是run1函数的地址:
在这里插入图片描述
我把这个run1地址写下来:

?run1@@YAXHV?$function@$$A6AXV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z@std@@@Z

原来的函数run1长这样:

void run1(int a, std::function< void(std::string)> output);

查找函数run2的位置:

找到三个位置,前两个应该都是函数的名称指引(知道的同学可以介绍下前两个是做啥的)。
第三个是run2函数的地址:
在这里插入图片描述
我把这个run2地址写下来:

?run2@@YAXHP6AXV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z@Z

原来的函数run2长这样:

void run2(int a, void(*output)(std::string));

地址解释

以上run1和run2函数在dll二进制文件中的地址是一个经过VS C++编译器名称修饰后的函数名,这种修饰是编译器用来区分具有相同名称但不同参数或返回类型的函数的方法。
由于我不是做编译器的,下面我用聊天机器人查了一下,仅给出以上run1函数地址的解释:
?run1@@:这是函数名run1的开头部分,其中?和@@是Microsoft编译器特有的名称修饰符号。
YAXH:这表示函数的返回类型和参数类型。在这个例子中,Y 表示返回类型为 void,AX 通常表示没有通过值传递的参数(但在这个特定情况下,由于后面有V,它实际上表示有一个通过引用或指针传递的参数),H 是参数列表的开始。不过,这里的AX和H的具体含义可能因编译器的具体实现而略有不同,重要的是理解整体结构。
V:这表示接下来的部分是一个通过引用或指针传递的参数。
?$function@…@std@@:这是对 std::function 模板的修饰表示,其中省略号(…)代表模板参数的具体类型,即 void(std::string)。
KaTeX parse error: Can't use function '$' in math mode at position 7: A6AXV?$̲basic_string@..…A6 是与调用约定相关的(可能是 __cdecl 的某种变体,但具体取决于编译器和平台),AXV 表示函数接受一个参数(V 表示通过引用或指针),?$basic_string@…@std@@@Z 是对 std::string 类型的修饰表示。

Windows调用程序

#include <iostream>
//#include <list>
#include <functional>
#ifdef _WINDOWS 
#include <shlwapi.h>
#include <Psapi.h> 
#include <codecvt> 
#else
#include <dlfcn.h>
#include <codecvt>
#endifvoid callback(std::string info) {std::cout << info << std::endl;
}void Run1(const std::string& dllpath, const std::string& initFuncName)
{std::string funName = initFuncName;
#ifdef _WINDOWStypedef void(_stdcall* pfnInitPlugin) (int, std::function< void(std::string)>);funName = "?" + funName + "@@YAXHV?$function@$$A6AXV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z@std@@@Z";auto module = LoadLibrary(dllpath.c_str());//寻找初始化函数,并执行pfnInitPlugin initfunc = (pfnInitPlugin)GetProcAddress(module, funName.c_str());if (initfunc){//常规方式//std::function< void(std::string)> cb = callback;//initfunc(4, cb);//lambda方式initfunc(4, [](std::string info) {std::cout << info << std::endl; });}else {std::cout << "未找到函数地址" << std::endl;}
#elsetypedef  void(__attribute__((__stdcall__))* pfnInitPlugin) (int, std::function< void(std::string));funName = "_Z22" + funName + "v";auto dp = dlopen(p.c_str(), RTLD_LAZY | RTLD_GLOBAL);if (dp){pfnInitPlugin initfunc = (pfnInitPlugin)dlsym(dp, funName.c_str());if (initfunc){initfunc(4, [](std::string info) {std::cout << info << std::endl;});}//dlclose(dp);}else {std::cout << "未找到函数地址" << std::endl;}
#endif
}void Run2(const std::string& dllpath, const std::string& initFuncName)
{std::string funName = initFuncName;
#ifdef _WINDOWStypedef void(_stdcall* pfnInitPlugin) (int, void(*output)(std::string));//run1funName = "?" + funName + "@@YAXHP6AXV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z@Z";auto module = LoadLibrary(dllpath.c_str());//寻找初始化函数,并执行pfnInitPlugin initfunc = (pfnInitPlugin)GetProcAddress(module, funName.c_str());if (initfunc){//void(*cb)(std::string);//cb = callback;//initfunc(4, cb);initfunc(4, [](std::string info) {std::cout << info << std::endl;});}else {std::cout << "未找到函数地址" << std::endl;}
#elsetypedef  void(__attribute__((__stdcall__))* pfnInitPlugin) (int, void(*output)(std::string));funName = "_Z22" + funName + "v";auto dp = dlopen(p.c_str(), RTLD_LAZY | RTLD_GLOBAL);if (dp){pfnInitPlugin initfunc = (pfnInitPlugin)dlsym(dp, funName.c_str());if (initfunc){initfunc(4, [](std::string info) {std::cout << info << std::endl;});}//dlclose(dp);}else {std::cout << "未找到函数地址" << std::endl;}
#endif
}int main(int argc, char* argv[]) {std::string argv1 = argv[1];if (argc == 2 && argv1 == "-h"){std::cout << "用法:\n【exe-name】【dll-path】【func-name】" << std::endl;return 0;}if (argc != 3){std::cerr << "传入的参数数量不对,应该是俩,检查检查!!" << std::endl;return -1;}std::string dllPath = argv[1];if (dllPath.find(".dll") == dllPath.npos){std::cerr << "传入的文件没有dll,检查检查!!" << std::endl;return -1;}std::string argv2 = argv[2];if (argv2 == "run1"){Run1(argv[1], "run1");}else if (argv2 == "run2") {Run2(argv[1], "run2");}else {std::cerr << "传入的函数名既不是 run1 也不是 run2 ,检查检查!!" << std::endl;return -1;}return 0;
}

在Windows下,核心代码是下面这几句:

	typedef void(_stdcall* pfnInitPlugin) (int, std::function< void(std::string)>);//run1funName = "?" + funName + "@@YAXHV?$function@$$A6AXV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z@std@@@Z";auto module = LoadLibrary(dllpath.c_str());//寻找初始化函数,并执行pfnInitPlugin initfunc = (pfnInitPlugin)GetProcAddress(module, funName.c_str());if (initfunc){//常规方式//std::function< void(std::string)> cb = callback;//initfunc(4, cb);//lambda方式initfunc(4, [](std::string info) {std::cout << info << std::endl; });}else {std::cout << "未找到函数地址" << std::endl;}

程序读入动态库,通过函数在动态库中的地址进行直接调用。
下面是调用结果:
在这里插入图片描述

Linux下直接调用

我们用BinaryViewer这款二进制查看器看看函数run1和run2在libReflection-DLL_TEST.so长什么样子。

查找函数run1的位置:

找到4个位置,前两个应该都是函数的名称指引(知道的同学可以介绍下前3个是做啥的)。
第4个是run1函数的地址:
在这里插入图片描述
我把这个run1地址写下来:

_Z4run1iSt8functionIFvNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEEE

原来的函数run1长这样:

void run1(int a, std::function< void(std::string)> output);

查找函数run2的位置:

找到4个位置,前两个应该都是函数的名称指引(知道的同学可以介绍下前3个是做啥的)。
第4个是run2函数的地址:
在这里插入图片描述
我把这个run2地址写下来:

_Z4run2iPFvNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEE

原来的函数run2长这样:

void run2(int a, void(*output)(std::string));

地址解释

以上run1和run2函数在so二进制文件中的地址是一个由 GCC编译器生成的 mangled(修饰)名称。这种名称用于在编译后的代码中唯一标识函数、变量等符号,同时包含类型信息。Mangled 名称对于人类来说通常是不直观的,但它们对于编译器和链接器来说是必要的,以确保在复杂的程序中正确地解析和链接符号。
由于我不是做编译器的,下面我用聊天机器人查了一下,仅给出以上run1函数地址的解释:
_Z 前缀是 GCC 编译器用于 mangled 名称的标识。
4run1i 部分是函数名称的编码,其中 run 是函数名,1 表示该函数接受一个参数,i 表示该参数的类型(在这个上下文中,它实际上是指接下来的类型信息,而不是直接的类型)。
St8functionIFvNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEEE 是参数类型的 mangled 表示。这个类型是一个 std::function,它包装了一个可调用对象,该对象接受一个 std::string 类型的参数(没有返回值,因为 Fv 表示一个函数类型,没有返回类型)。
St8function 表示 std::function。
IFv 表示一个函数(F)没有返回值(v,即 void),并且接下来是参数类型。
NSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE 是 std::__cxx11::basic_string<char, std::char_traits, std::allocator> 的 mangled 表示,即 std::string。

Linux调用程序

#include <iostream>
//#include <list>
#include <functional>
#ifdef _WINDOWS 
#include <shlwapi.h>
#include <Psapi.h> 
#include <codecvt> 
#else
#include <dlfcn.h>
#include <codecvt>
#endifvoid callback(std::string info) {std::cout << info << std::endl;
}void Run1(const std::string& dllpath, const std::string& initFuncName)
{std::string funName = initFuncName;
#ifdef _WINDOWStypedef void(_stdcall* pfnInitPlugin) (int, std::function< void(std::string)>);funName = "?" + funName + "@@YAXHV?$function@$$A6AXV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z@std@@@Z";auto module = LoadLibrary(dllpath.c_str());//寻找初始化函数,并执行pfnInitPlugin initfunc = (pfnInitPlugin)GetProcAddress(module, funName.c_str());if (initfunc){//常规方式//std::function< void(std::string)> cb = callback;//initfunc(4, cb);//lambda方式initfunc(4, [](std::string info) {std::cout << info << std::endl; });}else{std::cout<<"not find function name"<<std::endl;}
#elsetypedef  void(* pfnInitPlugin) (int, std::function< void(std::string)>);funName = "_Z4" + funName + "iSt8functionIFvNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEEE";auto dp = dlopen(dllpath.c_str(), RTLD_LAZY | RTLD_GLOBAL);if (dp){pfnInitPlugin initfunc = (pfnInitPlugin)dlsym(dp, funName.c_str());if (initfunc){initfunc(4, [](std::string info) {std::cout << info << std::endl;});}dlclose(dp);}else{std::cout<<"not find function name"<<std::endl;}
#endif
}void Run2(const std::string& dllpath, const std::string& initFuncName)
{std::string funName = initFuncName;
#ifdef _WINDOWStypedef void(_stdcall* pfnInitPlugin) (int, void(*output)(std::string));funName = "?" + funName + "@@YAXHP6AXV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z@Z";auto module = LoadLibrary(dllpath.c_str());//寻找初始化函数,并执行pfnInitPlugin initfunc = (pfnInitPlugin)GetProcAddress(module, funName.c_str());if (initfunc){//常规方式//void(*cb)(std::string);//cb = callback;//initfunc(4, cb);//lambda方式initfunc(4, [](std::string info) {std::cout << info << std::endl;});}else{std::cout<<"not find function name"<<std::endl;}
#elsetypedef  void(* pfnInitPlugin) (int, void(*output)(std::string));funName = "_Z4" + funName + "iPFvNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEE";auto dp = dlopen(dllpath.c_str(), RTLD_LAZY | RTLD_GLOBAL);if (dp){pfnInitPlugin initfunc = (pfnInitPlugin)dlsym(dp, funName.c_str());if (initfunc){initfunc(4, [](std::string info) {std::cout << info << std::endl;});}dlclose(dp);}else{std::cout<<"not find function name"<<std::endl;}
#endif
}int main(int argc, char* argv[]) {//C++ 98//Lambda回调函数//Run1("./Reflection-DLL_TEST.dll","run1");std::string argv1 = argv[1];if (argc == 2 && argv1 == "-h"){std::cout << "用法:\n【exe-name】【dll-path】【func-name】" << std::endl;return 0;}if (argc != 3){std::cerr << "传入的参数数量不对,应该是俩,检查检查!!" << std::endl;return -1;}std::string dllPath = argv[1];if (dllPath.find(".so") == dllPath.npos){std::cerr << "传入的文件没有so,检查检查!!" << std::endl;return -1;}std::string argv2 = argv[2];if (argv2 == "run1"){Run1(argv[1], "run1");}else if (argv2 == "run2") {Run2(argv[1], "run2");}else {std::cerr << "传入的函数名既不是 run1 也不是 run2 ,检查检查!!" << std::endl;return -1;}//system("pause");return 0;
}

在Linux下,核心代码是下面这几句:

	typedef  void(* pfnInitPlugin) (int, std::function< void(std::string)>);funName = "_Z4" + funName + "iSt8functionIFvNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEEE";auto dp = dlopen(dllpath.c_str(), RTLD_LAZY | RTLD_GLOBAL);if (dp){pfnInitPlugin initfunc = (pfnInitPlugin)dlsym(dp, funName.c_str());if (initfunc){initfunc(4, [](std::string info) {std::cout << info << std::endl;});}dlclose(dp);}else{std::cout<<"not find function name"<<std::endl;}

程序读入动态库,通过函数在动态库中的地址进行直接调用。
下面是调用结果:
在这里插入图片描述

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • java面向对象:构造方法
  • PMP--一模--解题--131-140
  • 感知器神经网络
  • 移情别恋c++ ദ്ദി˶ー̀֊ー́ ) ——12.二叉树(习题)
  • 【Kubernetes】常见面试题汇总(十)
  • 代码随想录训练营第34天|dp前置转移
  • 【C++ Primer Plus习题】16.3
  • PHP:强大的Web开发语言
  • 基于微信小程序的高校实验室管理系统的设计与实现
  • 数据结构之外部排序
  • ros学习笔记.4 Path Planning Part 2 (避障)
  • 【Linux基础】冯诺依曼体系结构操作系统的理解
  • 1.接口测试基础
  • 测试用例的了解
  • 【设计模式】创建型模式(四):建造者模式
  • 4个实用的微服务测试策略
  • Angular 2 DI - IoC DI - 1
  • Cookie 在前端中的实践
  • Fastjson的基本使用方法大全
  • HTML5新特性总结
  • MYSQL如何对数据进行自动化升级--以如果某数据表存在并且某字段不存在时则执行更新操作为例...
  • VuePress 静态网站生成
  • XForms - 更强大的Form
  • 从setTimeout-setInterval看JS线程
  • 基于游标的分页接口实现
  • 前端存储 - localStorage
  • 思否第一天
  • 腾讯优测优分享 | 你是否体验过Android手机插入耳机后仍外放的尴尬?
  • 在electron中实现跨域请求,无需更改服务器端设置
  • 终端用户监控:真实用户监控还是模拟监控?
  • ​软考-高级-系统架构设计师教程(清华第2版)【第20章 系统架构设计师论文写作要点(P717~728)-思维导图】​
  • ‌前端列表展示1000条大量数据时,后端通常需要进行一定的处理。‌
  • !! 2.对十份论文和报告中的关于OpenCV和Android NDK开发的总结
  • #70结构体案例1(导师,学生,成绩)
  • #QT项目实战(天气预报)
  • #我与Java虚拟机的故事#连载01:人在JVM,身不由己
  • $NOIp2018$劝退记
  • (1)SpringCloud 整合Python
  • (20)docke容器
  • (9)YOLO-Pose:使用对象关键点相似性损失增强多人姿态估计的增强版YOLO
  • (编程语言界的丐帮 C#).NET MD5 HASH 哈希 加密 与JAVA 互通
  • (附源码)spring boot校园拼车微信小程序 毕业设计 091617
  • (每日持续更新)信息系统项目管理(第四版)(高级项目管理)考试重点整理第3章 信息系统治理(一)
  • (面试必看!)锁策略
  • (四)c52学习之旅-流水LED灯
  • (五) 一起学 Unix 环境高级编程 (APUE) 之 进程环境
  • (转载)Linux 多线程条件变量同步
  • (自用)learnOpenGL学习总结-高级OpenGL-抗锯齿
  • *2 echo、printf、mkdir命令的应用
  • .Net Core 微服务之Consul(二)-集群搭建
  • .NET Framework杂记
  • .NET MVC、 WebAPI、 WebService【ws】、NVVM、WCF、Remoting
  • .Net Redis的秒杀Dome和异步执行
  • .Net Web窗口页属性
  • .NET 命令行参数包含应用程序路径吗?