• 从SmartPay dll学到的内容 宏定义 单件模式 迭代 日志记录函数进入与出来


    日志记录函数进入与出来:利用C++的反初始化来记录退出

    示例 :记录了函数进出,进程与线程,日期与时间等

    20/02 14:44:33 [32256-20068] FUNC -> ComTransmit_ConnectDevice()
      
    20/02 14:44:33 [32256-20068] FUNC <- ComTransmit_ConnectDevice()
    
    • 1
    • 2
    • 3
    
    #ifdef LOG_FUNC
    	#define FUNC_IN(func)	        MessageLoggerFuncInOut msgloggerinout_##func(#func)
    #else
    	#define FUNC_IN(func) //关闭log
    #endif
    
    void Refund(long amount, const char *orderNo, const char *originalNo, FGLCallback callback)
    {
    	FUNC_IN(Refund);
    	//...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    日志类

    static volatile u4 g_eslog_FuncLevel = 0;
    
    MessageLoggerFuncInOut::MessageLoggerFuncInOut(const char* pszFuncName, bool IsService)
    {
    	m_pdwRet = NULL;
    	m_IsService = IsService;
    	m_IsShowErr = false;
    	m_szFuncName[0] = 0;
    #if defined(WIN32)
    	m_ulStartTime = GetTickCount();
    #endif
    	if (!m_IsService)
    		InterlockedIncrement((u4*)&g_eslog_FuncLevel);
    	m_level = g_eslog_FuncLevel;
    	if (m_IsService)
    		m_level++;
    	if (m_level > 4)
    		m_level = 4;
    
    	const char* pszFuncNameToKeep = NULL;
    	if (NULL == pszFuncName || 0 == strlen(pszFuncName))
    		pszFuncNameToKeep = "Function_Name_Not_Define";
    	else
    		pszFuncNameToKeep = pszFuncName;
    
    	strcpy(m_szFuncName, pszFuncNameToKeep);
    
    	char ss[MAX_PATH] ={0};
    	for (long i = 1; i < m_level; ++i)
    		strcat(ss, "    ");
    
    	char ss1[MAX_PATH] ={0};
    	sprintf(ss1, "[%d-%lu] FUNC %s-> %s()\n", GetCurrentProcessId(), GetCurrentThreadId(), ss, m_szFuncName);
    #if defined(WIN32)
    	m_tMutex.Lock();
    	get_msg_logger()._DoLog(((char*)ss1));
    	m_tMutex.Unlock();
    #else	
    	ESLOG_FUNC(((char*)ss1));
    #endif
    
    	//	ESLOG_FUNC(("FUNC %s-> %s()", ss, m_szFuncName))
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    函数运行记时、调用次数统计等

    宏定义

    配置里的宏

    在这里插入图片描述

    WIN32;NDEBUG;_WINDOWS;_USRDLL;SMARTPAY_PGLDLL_192787_EXPORTS;ESLOG_RELEASE;HAVE_STRUCT_TIMESPEC;%(PreprocessorDefinitions)
    
    • 1

    减少代码耦合 关闭日志等

    在这里插入图片描述

    多线程

    支持多平台

    #if defined(WIN32)
    			//			tcout << szTime;
    			//std::cout << strLog << std::endl;
    			//			tcout << szLog << std::endl;
    #elif defined(linux) || defined(macintosh)
    			if (ESLOG_LEVEL_ERROR == m_ulLevel)
    			{
    				// 红色背景,黄色前景
    				std::cerr << '\033' << "[1;33;41m";
    				std::cerr << szTime;
    				std::cerr << szLog;
    				std::cerr << '\033' << "[0m";
    				std::cerr << std::endl;
    			}
    			else
    			{
    				std::cout << szTime;
    				//std::cout << strLog << std::endl;
    				std::cout << szLog << std::endl;
    			}
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    多编译配置 结合宏定义输出不同版本。

    在这里插入图片描述

    多途径输出

    文件、DebugView、控制台等。

    OutputDebugStringA(szLog);
    
    • 1
    std::cout << strLog << std::endl;
    
    • 1

    https://blog.csdn.net/chenhao0568/article/details/132870969?spm=1001.2014.3001.5501

    其它

    说了半天,还是没明白!!!

    在C++中,volatile是一个关键字,用于告诉编译器不要对带有volatile修饰符的变量进行优化。volatile通常用于标记那些在程序的执行过程中可能会被意外修改的变量,以防止编译器对其进行优化,确保每次都从内存中读取或写入变量的值。

    主要用途包括:

    1. 硬件寄存器访问: volatile常常用于访问硬件寄存器,因为这些寄存器的值可以在程序执行过程中被外部设备更改。通过将变量标记为volatile,可以确保编译器不会将读写这些寄存器的操作优化掉。

      volatile int *hwRegister = (volatile int *)0x1234; // 访问硬件寄存器
      int value = *hwRegister; // 从寄存器中读取值
      
      • 1
      • 2
    2. 多线程环境: 在多线程编程中,volatile可以用来确保线程之间对共享变量的访问不会被编译器优化掉,从而避免潜在的并发问题。

      volatile int sharedVariable = 0; // 共享变量
      
      // 线程1
      sharedVariable = 1;
      
      // 线程2
      int value = sharedVariable;
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7

    需要注意的是,volatile只提供了有关变量在内存中的读写行为的信息,但它并不提供线程安全性。在多线程环境中,通常需要使用更强大的同步机制,如互斥锁或原子操作,来确保正确的并发访问。

    另外,从C++11开始,C++引入了std::atomic类型,它提供了更强大的原子操作和内存模型,用于处理多线程编程中的共享数据。在许多情况下,std::atomic更适合用来替代volatile来处理多线程问题。

    错误消息

    您提供的代码片段使用了Windows API中的FormatMessageW函数,该函数用于将系统错误代码转换为相应的错误消息文本。下面是对您提供的代码片段的解释:

    FormatMessageW(
        FORMAT_MESSAGE_ALLOCATE_BUFFER |      // 分配缓冲区以存储消息
        FORMAT_MESSAGE_FROM_SYSTEM |          // 从系统中检索错误消息
        FORMAT_MESSAGE_IGNORE_INSERTS,        // 忽略消息中的插入字符串
        NULL,                                 // 所报告的错误代码
        dwErrCode,                            // 错误代码,通常使用GetLastError()获取
        MAKELANGID(LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED), // 语言标识,这里是简体中文
        (LPTSTR) &lpMsgBuf,                    // 用于存储错误消息的缓冲区指针
        0,                                    // 缓冲区大小,0表示自动分配
        NULL                                 // 指向插入字符串的指针(在这里不使用)
    );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    这段代码的目的是将Windows系统中的错误代码(dwErrCode)转换为相应的错误消息文本,并将结果存储在lpMsgBuf所指向的缓冲区中。在这里,您指定了语言标识为简体中文,以获取简体中文的错误消息。

    一旦FormatMessageW函数成功执行,lpMsgBuf将包含错误消息的文本,您可以使用它来显示错误信息或进行其他处理。请注意,在使用完lpMsgBuf后,您需要使用LocalFree函数来释放分配的内存,以避免内存泄漏。

    这是一种常见的用法,用于在Windows应用程序中将系统错误代码转换为人类可读的错误消息,以便用户或开发人员能够更好地理解发生的问题。

    void ShowSystemError(IN u4 dwErrCode) 
    {
    #if defined(WIN32)
    	//Show error message of GetLastError() 
    	if (dwErrCode==-1)
    	{
    		dwErrCode = GetLastError();
    	}
    	LPVOID lpMsgBuf = NULL; 
    	FormatMessageW( 
    		FORMAT_MESSAGE_ALLOCATE_BUFFER |
    		FORMAT_MESSAGE_FROM_SYSTEM | 
    		FORMAT_MESSAGE_IGNORE_INSERTS, 
    		NULL, 
    		dwErrCode/*GetLastError()*/, 
    		MAKELANGID(LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED), // Default language 
    		(LPTSTR) &lpMsgBuf, 
    		0, 
    		NULL 
    		); 
    	WCHAR pbuffer[1024*2] = {0};
    	wsprintf(pbuffer,_T("Err[0x%x]:%s"),dwErrCode,(WCHAR*)lpMsgBuf);
    	LocalFree( lpMsgBuf ); 
    #elif defined(linux)|| defined(macintosh)
    	WCHAR pbuffer[1024*2] = {0};
    	swprintf(pbuffer, sizeof(pbuffer), L"Err[0x%x]:%s",errno, L"/n");
    #endif
    	ESLOG_INF(2,_T("%s"),pbuffer);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    单件模式

    MessageLogger& get_msg_logger(void)
    {
    	static MessageLogger message_logger;
    	return message_logger;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    迭代

    这行代码定义了一个名为 iter 的迭代器,该迭代器用于遍历和操作一个名为 std::map 的关联容器,其中键的类型是整数 int,值的类型是 FGLCallbackstd::map 是C++标准库中的关联容器,用于存储键-值对,并根据键进行排序。

    以下是代码的解释:

    std::map<int, FGLCallback>::iterator iter;
    
    • 1
    • std::map:这部分定义了一个std::map 容器,其中键的类型是整数 int,值的类型是 FGLCallback。这表示你可以使用整数作为键,并将 FGLCallback 对象与每个整数相关联。

    • ::iterator:: 操作符用于访问 std::map 的内部类型 iterator,它是一个迭代器类型,用于遍历 std::map 中的元素。

    • iter:这是迭代器的名称,你可以使用它来遍历 std::map 中的元素,例如使用 iter 来访问、修改或删除 std::map 中的键-值对。

    要使用这个迭代器,你可以使用循环来遍历 std::map 中的元素,例如:

    std::map<int, FGLCallback> myMap;
    // 填充 myMap
    
    for (std::map<int, FGLCallback>::iterator iter = myMap.begin(); iter != myMap.end(); ++iter) {
        int key = iter->first;            // 获取键
        FGLCallback value = iter->second; // 获取值
    
        // 在这里执行操作,如访问或修改键值对
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    这个迭代器用于遍历 std::map 中的所有键-值对,并允许你对它们进行操作。

    智能指针

    这行代码定义了一个名为 d 的智能指针,它指向类型为 FGLDevice 的对象。具体来说,它是一个 std::shared_ptr,这是C++标准库中的智能指针类型,用于管理动态分配的内存资源,确保资源在不再需要时被正确释放,以避免内存泄漏。

    以下是代码的解释:

    std::shared_ptr<FGLDevice> d;
    
    • 1
    • std::shared_ptr:这部分定义了一个 std::shared_ptr,它可以管理指向 FGLDevice 类型对象的指针。std::shared_ptr 是一种共享所有权的智能指针,允许多个 shared_ptr 共享同一个对象,当最后一个 shared_ptr 不再需要对象时,对象的内存将自动释放。

    • d:这是智能指针的名称,你可以使用它来访问 FGLDevice 对象,以及对智能指针的其他操作,如复制、释放等。

    通常,你可以使用 std::make_shared 函数来创建一个 std::shared_ptr,并初始化它指向的对象。例如:

    #include 
    
    // 创建一个 std::shared_ptr 指向 FGLDevice 对象
    std::shared_ptr<FGLDevice> d = std::make_shared<FGLDevice>();
    
    • 1
    • 2
    • 3
    • 4

    这将创建一个 std::shared_ptr,指向一个新创建的 FGLDevice 对象,并自动管理对象的生命周期。当不再需要 d 时,它将负责释放关联的 FGLDevice 对象的内存。

    分离线程

    这段代码是使用 POSIX 线程库(通常称为 pthreads)在多线程环境中创建一个分离线程。下面是对这段代码的解释:

    pthread_attr_t attr; // 创建线程属性对象
    pthread_attr_init(&attr); // 初始化线程属性对象
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); // 设置线程为分离状态
    pthread_t pid; // 创建线程的标识符
    pthread_create(&pid, &attr, TransThread::thread_close, (void *) this); // 创建线程
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • pthread_attr_t attr;:这行代码定义了一个线程属性对象 attr,用于指定线程的属性。线程属性包括线程的状态、调度策略等信息。

    • pthread_attr_init(&attr);:这行代码初始化线程属性对象 attr,以确保它处于一个可用的初始状态。

    • pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);:这行代码设置线程属性,将线程状态设置为分离状态,这意味着线程结束后将自动释放其资源,而不需要显式调用 pthread_join 函数来等待线程的结束。

    • pthread_t pid;:这行代码声明一个线程标识符 pid,它将用于标识新创建的线程。

    • pthread_create(&pid, &attr, TransThread::thread_close, (void *) this);:这行代码创建一个新的线程,并将其设置为分离状态。具体解释如下:

      • &pid:传入指向 pid 变量的指针,以便 pthread_create 函数将新线程的标识符存储在 pid 变量中。
      • &attr:传入指向线程属性对象 attr 的指针,以指定新线程的属性。
      • TransThread::thread_close:这是新线程的入口点(线程函数)。在这个例子中,它似乎是一个静态成员函数或类方法,用于执行线程的实际工作。
      • (void *) this:这是传递给线程函数的参数,通常用于传递线程所需的数据。在这里,它似乎将当前对象的指针传递给线程函数。

    这段代码的效果是创建一个新线程,该线程在后台运行(分离状态),执行 TransThread::thread_close 函数,并将当前对象的指针传递给该函数。线程属性的设置确保了线程在结束后会自动释放资源。

    分离线程(Detached Thread)是一种线程状态,它具有以下主要特点和作用:

    1. 自动资源回收:分离线程在执行结束后,线程库会自动回收其相关资源,包括线程的内存和其他资源,而不需要显式调用 pthread_join 或类似的函数等待线程的结束。这可以减少代码的复杂性,特别是在创建许多临时线程时。

    2. 不阻塞主线程:在主线程中创建分离线程后,主线程不需要等待分离线程的完成。这允许主线程继续执行其他任务,而不必等待分离线程的结果或等待其完成。

    3. 线程资源管理:分离线程对于创建临时线程或线程池中的线程非常有用,因为它们可以在不增加主线程的负担的情况下并发执行任务。这可以提高程序的性能和效率。

    4. 减少资源泄漏风险:当线程是分离状态时,程序员不需要关心线程的资源释放问题,这有助于减少资源泄漏的风险。如果线程是非分离状态(joinable),则需要确保在主线程中正确地调用 pthread_join 函数来等待线程的结束和释放资源。

    需要注意的是,分离线程不适合所有情况。在某些情况下,你可能需要创建非分离线程,以等待线程的结果或协调线程之间的操作。选择线程状态(分离或非分离)取决于你的具体需求和应用程序的设计。在需要时,你可以使用线程库提供的函数来设置线程的状态。在POSIX线程库中,使用 pthread_attr_setdetachstate 函数来设置线程的分离状态。

    互斥锁

    这行代码使用了C++标准库中的std::lock_guard,它是一种用于管理互斥锁(std::mutex)的RAII(资源获取即初始化)类。下面是对这行代码的解释:

    std::lock_guard<std::mutex> lk(mutex_);
    
    • 1
    • std::lock_guard:这是一个模板类,用于在其作用域内锁定互斥锁,并在作用域结束时自动释放锁。它是一种C++中管理锁的推荐方式,因为它确保在离开作用域时始终释放锁,无论作用域是正常退出还是异常退出。

    • std::lock_guard是一个模板类,需要指定互斥锁的类型作为模板参数。在这里,它指定了std::mutex,表示将使用std::mutex类型的互斥锁。

    • lk:这是std::lock_guard对象的名称,它是一个局部对象,其生命周期与当前作用域相对应。当lk离开其所在的作用域时,它会自动释放关联的互斥锁(mutex_)。

    • mutex_:这是一个名为mutex_的互斥锁,std::lock_guard将会锁定这个互斥锁。

    这段代码的作用是在当前作用域中锁定mutex_互斥锁,以确保在锁定期间只有一个线程可以访问受保护的共享资源。一旦lk对象超出其作用域,互斥锁将自动释放,允许其他线程获得锁。这有助于避免多线程中的竞态条件和数据竞争问题。

    分析过程

    点连接后不断的获取列表 和接收串口数据

    在调用每个业务接口时,先把回调等设好,数据放进TransEntity,进到trans里
    find_device 设备还在不在 不在就调用demo中的回调callback (response码和response信息)

    create_pipe   identity就是串口设备名  MAX_PIPE_SIZE最大使用10个通道
    
    insert_trans_thread  数量不限
    
    find_trans_thread  
    
    thread->run(); 
    				thread_run  又创新线程
    				find_device
    				find_pipe
    				pipe->start() 发送开始
    				mEntity.getJsonData()
    				des3_encrypt 选授权时需要加解密
    				pipe->send 发送数据
    				ThreadLock::wait(&thread->mMutex, &thread->mCond, timeout * 1000) 等待响应
    				
    				
    				回调if (!d->create_pipe(identity, TransThread::PRO_RECEIVED_CALLBACK))
    				    mPipeList.insert(mPipeList.end(), std::make_shared(identity, received));
    				     	mReceived = received;
    				    		PROTOCOL_RECEIVED mReceived; //pipe received data callback
    				    			void FGLPipe::receivedData(FGLMessage *message)
    				    				        mReceived(mIdentity, buff, buffLen);  实际的回调
    				    				        
    				    				        
    				    				        void *FGLDevice::thread_received(void *data)调用receivedData
    				    				        
    				    				        		void FGLDevice::receivedData(const unsigned char *data, unsigned int dataLen)调用了thread_received
    				    				        				void FGLProtocol::COMM_RECEIVED_CALLBACK(const char *device,const unsigned char *data, int unsigned dataLen) 调用了        d->receivedData(data, dataLen);
    																				void FGLProtocol::COMM_INIT_CALLBACK(int code, const char *data) 调用了        IComm::getInstance().setReceivedListener(FGLProtocol::COMM_RECEIVED_CALLBACK);  看下面
    																						void FGLProtocol::initialize(int type, PROTOCOL_RESULT callback)调用了COMM_INIT_CALLBACK
    																								void FGLPos::initialize(int type, RESULT_CALLBACK callback)调用了上面
    																										void impl_initialize(int comm, RESULT_CALLBACK callback)调用了上面
    																											接口void  ConnectDevice(const char *deviceName, FGLCallback callback)调用了上面  
    				    	
    				   
    														setReceivedListener 转到	mCommReceivedCallBack
    															unsigned __stdcall Fun1Proc(void* p1)
    																	mCommReceivedCallBack(deviceCom.c_str(), pbRecv, nRecvLen);  实际在这里
    																			m_Thread.Run(Fun1Proc, 0, mCommReceivedCallBack);  unsigned __stdcall Fun1Proc(void* p1)未使用
    				
    				void TransThread::PRO_RECEIVED_CALLBACK(const char *identity, const unsigned char *data,unsigned int dataLen) 正常回调
    					void TransThread::close(int code, const char *data)
    						thread_close 
    							result.callback(result.transNo, entity.error_code, entity.error_info.c_str()); 有回调
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
  • 相关阅读:
    搭建统一的依赖管理
    Linux高性能服务器编程 学习笔记 第十章 信号
    Open3D 进阶(10)使用FilterReg算法对点云配准
    GDAL+Java实现获取对应栅格影像经纬度对应的像素值
    m1芯片-centos安装mysql
    < Python全景系列-3 > Python控制流程盘点及高级用法、神秘技巧大揭秘!
    solidity使用create2预测合约地址|create2用法|智能合约create2
    优化软件测试成本的 7 个步骤
    【Python】《Python编程:从入门到实践 (第2版) 》笔记-Chapter1-起步
    自动驾驶与人工驾驶并存,自动驾驶取代人工驾驶
  • 原文地址:https://blog.csdn.net/chenhao0568/article/details/133162493