Browse Source

智能识别模式下,打印卡住的问题优化好了

张洋 2 weeks ago
parent
commit
74f418d4bd

+ 2 - 1
DuiLib/Control/UICombo.cpp

@@ -294,7 +294,8 @@ namespace DuiLib {
 			POINT ptCur = { 0 }; ::GetCursorPos(&ptCur);
 			DuiLibDiag::Log(_T("COMBO-INIT begin owner=%s ownerHwnd=%p cursor=%d,%d ownerVisible=%d ownerEnabled=%d wnd=%p"),
 				pOwner != NULL ? pOwner->GetName().GetData() : _T("null"),
-				pOwner != NULL ? pOwner->GetManager()->GetPaintWindow() : NULL,
+				// 【R57】链式解引用必须逐级判空:GetManager() 可能为 NULL(历史崩点同款)。
+				(pOwner != NULL && pOwner->GetManager() != NULL) ? pOwner->GetManager()->GetPaintWindow() : NULL,
 				ptCur.x, ptCur.y,
 				(int)(pOwner != NULL && pOwner->IsVisible() ? 1 : 0),
 				(int)(pOwner != NULL && pOwner->IsEnabled() ? 1 : 0),

+ 13 - 4
DuiLib/Core/UIManager.cpp

@@ -4678,12 +4678,21 @@ namespace DuiLib
 							// m_hDcOffscreen 保持未调制原稿供下一帧重算。蒙版建失败(熔断)时
 							// m_pBackgroundBits 为 NULL,维持旧源,绝不 ULW 一个空 DC。
 							HDC hDcLayeredSrc = (m_bLayeredRoundMask && m_pBackgroundBits != NULL) ? m_hDcBackground : m_hDcOffscreen;
+						// 【R57 · 空指针崩溃修复】下面的探针**实参在调用前求值**,与 Enabled()
+						// 开关无关(Log 是跨 .cpp 的普通函数,编译期消不掉实参求值)。
+						// 而 m_pBackgroundBits 只在「round mask」或「配了 m_diLayered」两条分支里
+						// 才被创建;走本段末尾的 else(分层但**无圆角蒙版**的窗口,Combo 下拉窗
+						// 就是这一类)时它**恒为 NULL** ⇒ 裸解引用必崩。
+						// 2026-09-26 真机 dump 实证:点击任意 ComboBox 即崩,EIP=DuiLib+0x431AA
+						// (`push [eax+ecx*4+4]`, eax=NULL, 访问 0x7EE4),源码行就是下面这几行。
+						// 修法:逐个判空,保持日志可用且零崩溃风险。
 						DuiLibDiag::Log(_T("LAYERED-DUMP off=%p bkg=%p dcOff=%p dcBkg=%p w=%d h=%d px00=%08X pxTR=%08X pMid=%08X pxML=%08X"),
 						(void*)m_pOffscreenBits, (void*)m_pBackgroundBits, (void*)m_hDcOffscreen, (void*)m_hDcBackground, (int)dwWidth, (int)dwHeight,
-						m_pBackgroundBits[0],
-						m_pBackgroundBits[(int)((dwHeight - 1) * dwWidth) + (int)(dwWidth - 1)],
-						m_pBackgroundBits[(int)((dwHeight / 2) * dwWidth) + (int)(dwWidth / 2)],
-						m_pBackgroundBits[(int)((dwHeight / 2) * dwWidth) + 1]);
+						(m_pBackgroundBits != NULL) ? m_pBackgroundBits[0] : 0,
+						(m_pBackgroundBits != NULL) ? m_pBackgroundBits[(int)((dwHeight - 1) * dwWidth) + (int)(dwWidth - 1)] : 0,
+						(m_pBackgroundBits != NULL) ? m_pBackgroundBits[(int)((dwHeight / 2) * dwWidth) + (int)(dwWidth / 2)] : 0,
+						(m_pBackgroundBits != NULL) ? m_pBackgroundBits[(int)((dwHeight / 2) * dwWidth) + 1] : 0);
+						if (m_pBackgroundBits != NULL)
 						{
 						    int nSamp = 0, nShapeOnly = 0;
 						    for (LONG qy = 0; qy < 16; ++qy)

BIN
bin/Win32/Debug/zhipuzi_pos_windows/DuiLib_d.dll


BIN
bin/Win32/Release/zhipuzi_pos_windows/DuiLib.dll


BIN
bin/Win32/Release/zhipuzi_pos_windows/dump/2026-09-26-16-51-00.dmp


BIN
bin/Win32/Release/zhipuzi_pos_windows/dump/2026-09-26-16-51-01.dmp


BIN
bin/Win32/Release/zhipuzi_pos_windows/zhipuzi_pos_windows.exe


BIN
dll/debug/DuiLib_d.dll


BIN
dll/release/DuiLib.dll


BIN
pdb/release/DuiLib.pdb


+ 9 - 0
zhipuzi_pos_windows/page/CSettingPageUI.cpp

@@ -2061,6 +2061,9 @@ void CSettingPageUI::HandleItemSelectMsg(TNotifyUI& msg)
 			CEditUI* ip_edit = static_cast<CEditUI*>(this->FindSubControl(_T("setting_printer_wangkou_ip")));
 			ip_edit->SetText(CLewaimaiString::UTF8ToUnicode(setting_printer_wangkou_ip).c_str());
 		}
+
+		//打印机类型变更,重建收银打印端口通道
+		CPosPrinterWorker::GetInstance()->RefreshShouyinChannels();
 	}
 	else if (name == _T("setting_printer_usb"))
 	{
@@ -2068,6 +2071,9 @@ void CSettingPageUI::HandleItemSelectMsg(TNotifyUI& msg)
 		std::wstring usb_device = com->GetText();
 
 		CSetting::GetInstance()->SetParam("setting_printer_usb", CLewaimaiString::UnicodeToUTF8(usb_device));
+
+		//usb端口变更,重建收银打印端口通道
+		CPosPrinterWorker::GetInstance()->RefreshShouyinChannels();
 	}
 	else if (name == _T("setting_printer_com"))
 	{
@@ -2075,6 +2081,9 @@ void CSettingPageUI::HandleItemSelectMsg(TNotifyUI& msg)
 		std::wstring com_device = com->GetText();
 
 		CSetting::GetInstance()->SetParam("setting_printer_com", CLewaimaiString::UnicodeToUTF8(com_device));
+
+		//串口变更,重建收银打印端口通道
+		CPosPrinterWorker::GetInstance()->RefreshShouyinChannels();
 	}
 	else if (name == _T("setting_dianzicheng_xinghao"))
 	{

BIN
zhipuzi_pos_windows/resource/skin.zip


+ 286 - 105
zhipuzi_pos_windows/worker/CPosPrinterWorker.cpp

@@ -29,6 +29,9 @@ void CPosPrinterWorker::Start()
 {
 	m_is_work = true;
 
+	//建立收银打印端口通道,句柄常驻打开,不再每张票重复开关端口
+	StartShouyinChannels();
+
 	//处理收银打印
 	std::thread(&CPosPrinterWorker::HandleShouyinPrinter, this).detach();
 
@@ -42,6 +45,274 @@ void CPosPrinterWorker::Start()
 void CPosPrinterWorker::Stop()
 {
 	m_is_work = false;
+
+	StopShouyinChannels();
+}
+
+void CPosPrinterWorker::RefreshShouyinChannels()
+{
+	//设置变更后重建通道,若打印线程正在等待最慢端口,最多等一个超时周期
+	std::lock_guard<std::mutex> lock(m_shouyin_channels_mutex);
+	StartShouyinChannels();
+}
+
+void CPosPrinterWorker::StartShouyinChannels()
+{
+	StopShouyinChannels();
+
+	std::string printer_leixing = CSetting::GetInstance()->GetParam("setting_printer_leixing");
+
+	//网口不走句柄通道
+	if (printer_leixing == "wangkou")
+	{
+		return;
+	}
+
+	//收集本次要打开的端口路径及其类型
+	std::vector<std::pair<std::wstring, PrinterPortKind>> ports;
+
+	if (printer_leixing == "auto")
+	{
+		//自动模式:同时检测usb、并口、串口
+		m_usb_devices.clear();
+		int nDevice = GetDevicePath((LPGUID)&USB_GUID, m_usb_devices);
+		LOG_INFO("可用的USB打印机数量:" << nDevice);
+
+		std::string setting_biaoqian_printer_usb = CSetting::GetInstance()->GetParam("setting_biaoqian_printer_usb");
+		std::wstring ws_setting_biaoqian_printer_usb = CLewaimaiString::UTF8ToUnicode(setting_biaoqian_printer_usb);
+		bool exclude_biaoqian = (CSetting::GetInstance()->GetParam("setting_is_new_waimai_biaoqian_printer") == "1");
+
+		for (int i = 0; i < nDevice; i++)
+		{
+			if (exclude_biaoqian && ws_setting_biaoqian_printer_usb == m_usb_devices[i])
+			{
+				//设置中心明确设置的标签打印机,过滤掉
+				continue;
+			}
+
+			ports.push_back(std::make_pair(m_usb_devices[i], PrinterPortKind::Usb));
+		}
+
+		m_usb_devices.clear();
+
+		ports.push_back(std::make_pair(L"lpt1", PrinterPortKind::Bingkou));
+
+		CComHelper helper;
+		std::vector<std::wstring> comVector = helper.getComPort();
+		for (std::vector<std::wstring>::iterator it = comVector.begin(); it != comVector.end(); it++)
+		{
+			ports.push_back(std::make_pair(*it, PrinterPortKind::Com));
+		}
+	}
+	else if (printer_leixing == "usb")
+	{
+		std::string printer_usb = CSetting::GetInstance()->GetParam("setting_printer_usb");
+		std::wstring ws_printer_usb = CLewaimaiString::UTF8ToUnicode(printer_usb);
+		if (!ws_printer_usb.empty())
+		{
+			ports.push_back(std::make_pair(ws_printer_usb, PrinterPortKind::Usb));
+		}
+	}
+	else if (printer_leixing == "bingkou")
+	{
+		ports.push_back(std::make_pair(L"lpt1", PrinterPortKind::Bingkou));
+	}
+	else if (printer_leixing == "chuankou")
+	{
+		std::string printer_com = CSetting::GetInstance()->GetParam("setting_printer_com");
+		std::wstring ws_printer_com = CLewaimaiString::UTF8ToUnicode(printer_com);
+		if (!ws_printer_com.empty())
+		{
+			ports.push_back(std::make_pair(ws_printer_com, PrinterPortKind::Com));
+		}
+	}
+
+	//每个端口一个通道,打开失败的端口也保留通道,等待热插拔重试
+	for (std::vector<std::pair<std::wstring, PrinterPortKind>>::iterator it = ports.begin(); it != ports.end(); it++)
+	{
+		std::shared_ptr<CPortSendChannel> ch = std::make_shared<CPortSendChannel>();
+		ch->portPath = it->first;
+		ch->portKind = it->second;
+
+		OpenChannelHandle(ch);
+
+		ch->worker = std::thread(&CPosPrinterWorker::ChannelWorker, this, ch);
+		m_shouyin_channels.push_back(ch);
+	}
+}
+
+void CPosPrinterWorker::StopShouyinChannels()
+{
+	for (std::vector<std::shared_ptr<CPortSendChannel>>::iterator it = m_shouyin_channels.begin(); it != m_shouyin_channels.end(); it++)
+	{
+		std::shared_ptr<CPortSendChannel> ch = *it;
+		{
+			std::lock_guard<std::mutex> lock(ch->mtx);
+			ch->stop = true;
+		}
+		ch->cv.notify_one();
+		if (ch->worker.joinable())
+		{
+			ch->worker.join();
+		}
+	}
+
+	m_shouyin_channels.clear();
+}
+
+void CPosPrinterWorker::ChannelWorker(std::shared_ptr<CPortSendChannel> ch)
+{
+	while (true)
+	{
+		std::string data;
+		{
+			std::unique_lock<std::mutex> lock(ch->mtx);
+			ch->cv.wait(lock, [&ch] { return ch->stop || !ch->queue.empty(); });
+			if (ch->stop)
+			{
+				break;
+			}
+			data = ch->queue.front();
+			ch->queue.pop();
+		}
+
+		//句柄未打开(设备拔掉或被占用),尝试重新打开
+		if (ch->hPort == NULL)
+		{
+			if (!OpenChannelHandle(ch))
+			{
+				LOG_INFO("端口暂不可用,丢弃本次打印数据,Path = " << ch->portPath.c_str());
+				continue;
+			}
+		}
+
+		//慢端口超时只卡本通道的线程,不影响其他端口
+		DWORD dwWrite = 0;
+		bool ret = WriteWithTimeout(ch->hPort, data.c_str(), (DWORD)data.length(), 5000, dwWrite);
+
+		if (!ret)
+		{
+			//写失败可能是usb热插拔,关闭旧句柄后按路径重开一次再重试
+			LOG_INFO("端口写入失败,尝试重开句柄,hPort:" << ch->hPort << ", Path = " << ch->portPath.c_str());
+
+			CloseHandle(ch->hPort);
+			ch->hPort = NULL;
+
+			if (OpenChannelHandle(ch))
+			{
+				dwWrite = 0;
+				ret = WriteWithTimeout(ch->hPort, data.c_str(), (DWORD)data.length(), 5000, dwWrite);
+			}
+		}
+
+		if (!ret || dwWrite != (DWORD)data.length())
+		{
+			LOG_INFO("端口写入失败,hPort:" << ch->hPort << ", dwWrite:" << dwWrite << ", Path = " << ch->portPath.c_str());
+			continue;
+		}
+
+		LOG_INFO("Write Success, hPort:" << ch->hPort << ", dwWrite:" << dwWrite);
+	}
+
+	if (ch->hPort != NULL)
+	{
+		CloseHandle(ch->hPort);
+		ch->hPort = NULL;
+	}
+}
+
+bool CPosPrinterWorker::OpenChannelHandle(std::shared_ptr<CPortSendChannel> ch)
+{
+	HANDLE hPort = NULL;
+
+	if (ch->portKind == PrinterPortKind::Com)
+	{
+		hPort = OpenComPort(ch->portPath);
+	}
+	else
+	{
+		hPort = CreateFile(ch->portPath.c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL);
+	}
+
+	if (hPort == INVALID_HANDLE_VALUE || hPort == NULL)
+	{
+		DWORD error = GetLastError();
+
+		if (error == 2)
+		{
+			//没有指定的文件
+			LOG_INFO("没有找到对应的端口,Path = " << ch->portPath.c_str());
+		}
+		else if (error == 5)
+		{
+			LOG_INFO("端口被占用,Path = " << ch->portPath.c_str());
+		}
+		else
+		{
+			LOG_INFO("打开端口失败,Path = " << ch->portPath.c_str() << ", error = " << error);
+		}
+
+		return false;
+	}
+
+	ch->hPort = hPort;
+	LOG_INFO("打开端口成功,hPort:" << hPort << ", Path = " << ch->portPath.c_str());
+
+	return true;
+}
+
+HANDLE CPosPrinterWorker::OpenComPort(const std::wstring& com_path)
+{
+	HANDLE hPort = CreateFile(com_path.c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL);
+
+	if (hPort == INVALID_HANDLE_VALUE)
+	{
+		DWORD error = GetLastError();
+
+		if (error == 2)
+		{
+			//没有指定的文件
+			LOG_INFO("没有找到对应的串口 " << com_path.c_str());
+		}
+		else if (error == 5)
+		{
+			LOG_INFO("串口被占用 " << com_path.c_str());
+		}
+
+		return NULL;
+	}
+
+	LOG_INFO(com_path.c_str() << " success, handle:" << hPort);
+
+	//佳博的串口打印机,默认是这个波特率
+	DCB dcb;
+	dcb.DCBlength = sizeof(dcb);
+	GetCommState(hPort, &dcb);
+	dcb.BaudRate = 19200;
+
+	if (!SetCommState(hPort, &dcb))
+	{
+		LOG_INFO("set baudRate failed!");
+		CloseHandle(hPort);
+		return NULL;
+	}
+
+	//设定通讯端口超时参数
+	COMMTIMEOUTS tmouts;
+	tmouts.ReadIntervalTimeout = 100;
+	tmouts.ReadTotalTimeoutMultiplier = 100;
+	tmouts.ReadTotalTimeoutConstant = 100;
+	tmouts.WriteTotalTimeoutConstant = 100;
+	tmouts.WriteTotalTimeoutMultiplier = 100;
+	SetCommTimeouts(hPort, &tmouts);
+
+	//设置端口缓冲
+	SetupComm(hPort, 1024, 1024);
+
+	//清除通讯端口缓存
+	PurgeComm(hPort, PURGE_TXCLEAR | PURGE_RXCLEAR | PURGE_TXABORT | PURGE_RXABORT);
+
+	return hPort;
 }
 
 void CPosPrinterWorker::AddShouyinPrinter(std::string chufang_data)
@@ -176,43 +447,7 @@ void CPosPrinterWorker::SendDataToShouyinPirnter(std::string data)
 
 	std::string printer_leixing = CSetting::GetInstance()->GetParam("setting_printer_leixing");
 
-	if (printer_leixing == "auto")
-	{
-		InitUsb();
-
-		InitBingkou();
-
-		InitCom();
-
-		if (m_hShouyinPorts.size() == 0)
-		{
-			return;
-		}
-	}
-	else if (printer_leixing == "usb")
-	{
-		std::string printer_usb = CSetting::GetInstance()->GetParam("setting_printer_usb");
-		std::wstring ws_printer_usb = CLewaimaiString::UTF8ToUnicode(printer_usb);
-
-		bool ret = InitOneUsb(ws_printer_usb, 1);
-		if (ret == false)
-		{
-			return;
-		}
-	}
-	else if (printer_leixing == "bingkou")
-	{
-		bool ret = InitBingkou();
-		if (ret == false)
-		{
-			return;
-		}
-	}
-	else if (printer_leixing == "chuankou")
-	{
-		InitCom();
-	}
-	else if (printer_leixing == "wangkou")
+	if (printer_leixing == "wangkou")
 	{
 		//改为网口模式
 		nType = 2;
@@ -238,20 +473,20 @@ void CPosPrinterWorker::SendDataToShouyinPirnter(std::string data)
 	//开始发送数据
 	if (nType == 1)
 	{
-		//USB、串口、并口,走这里
-		for (std::vector<HANDLE>::iterator it = m_hShouyinPorts.begin(); it != m_hShouyinPorts.end(); it++)
-		{
-			HANDLE hPort = *it;
+		//USB、串口、并口走通道非阻塞分发,慢端口在自己的线程里超时,不影响其他端口和下一轮打印
+		std::lock_guard<std::mutex> lock(m_shouyin_channels_mutex);
 
-			//同步写数据
-			DWORD dwWrite = 0;
-			bool ret = WriteWithTimeout(hPort, data.c_str(), (DWORD)data.length(), 5000, dwWrite);
-			if (!ret || dwWrite != (DWORD)data.length())
+		for (std::vector<std::shared_ptr<CPortSendChannel>>::iterator it = m_shouyin_channels.begin(); it != m_shouyin_channels.end(); it++)
+		{
+			std::shared_ptr<CPortSendChannel> ch = *it;
 			{
-				LOG_INFO("WriteWithTimeout Failed or partial write!, hPort:" << hPort << ", dwWrite:" << dwWrite);
-				continue;
+				std::lock_guard<std::mutex> ch_lock(ch->mtx);
+				ch->queue.push(data);
 			}
+			ch->cv.notify_one();
 		}
+
+		return;
 	}
 	else
 	{
@@ -266,15 +501,6 @@ void CPosPrinterWorker::SendDataToShouyinPirnter(std::string data)
 		}
 	}
 
-	//关闭设备
-	for (std::vector<HANDLE>::iterator it = m_hShouyinPorts.begin(); it != m_hShouyinPorts.end(); it++)
-	{
-		CloseHandle(*it);
-	}
-
-	//删除之前旧的句柄
-	m_hShouyinPorts.clear();
-
 	if (nType == 2)
 	{
 		m_socket.close();
@@ -560,59 +786,14 @@ void CPosPrinterWorker::InitCom()
 	{
 		std::wstring com2Str = *it;
 
-		HANDLE hPort = CreateFile(com2Str.c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL);
+		HANDLE hPort = OpenComPort(com2Str);
 
-		if (hPort == INVALID_HANDLE_VALUE)
+		if (hPort == NULL)
 		{
-			DWORD error = GetLastError();
-
-			if (error == 2)
-			{
-				//没有指定的文件
-				LOG_INFO("没有找到对应的串口 " << com2Str.c_str());
-			}
-			else if (error == 5)
-			{
-				LOG_INFO("串口被占用 " << com2Str.c_str());
-			}
+			continue;
 		}
-		else
-		{
-			LOG_INFO(com2Str.c_str() << " success, handle:" << hPort);
-
-			//扎到了可用的串口,设置串口波特率
-			DCB dcb;
-			dcb.DCBlength = sizeof(dcb);
-			GetCommState(hPort, &dcb);
-
-			//佳博的串口打印机,默认是这个波特率
-			dcb.BaudRate = 19200;
-
-			if (!SetCommState(hPort, &dcb))
-			{
-				LOG_INFO("set baudRate failed!");
-
-				CloseHandle(hPort);
-				continue;
-			}
-
-			//设定通讯端口超时参数
-			COMMTIMEOUTS tmouts;
-			tmouts.ReadIntervalTimeout = 100;
-			tmouts.ReadTotalTimeoutMultiplier = 100;
-			tmouts.ReadTotalTimeoutConstant = 100;
-			tmouts.WriteTotalTimeoutConstant = 100;
-			tmouts.WriteTotalTimeoutMultiplier = 100;
-			SetCommTimeouts(hPort, &tmouts);
-
-			//设置端口缓冲
-			SetupComm(hPort, 1024, 1024);
 
-			//清除通讯端口缓存
-			PurgeComm(hPort, PURGE_TXCLEAR | PURGE_RXCLEAR | PURGE_TXABORT | PURGE_RXABORT);
-
-			m_hShouyinPorts.push_back(hPort);
-		}
+		m_hShouyinPorts.push_back(hPort);
 	}
 }
 

+ 41 - 0
zhipuzi_pos_windows/worker/CPosPrinterWorker.h

@@ -2,6 +2,31 @@
 
 #include "../print/CPosPrinterData.h"
 
+#include <condition_variable>
+#include <memory>
+#include <thread>
+
+//端口的物理类型,用于热插拔后重开句柄时区分配置方式
+enum class PrinterPortKind
+{
+	Usb,
+	Bingkou,
+	Com
+};
+
+//每个物理端口一个发送通道,线程独立、互不阻塞
+struct CPortSendChannel
+{
+	HANDLE hPort = NULL;
+	std::wstring portPath;		//重开句柄用的端口路径(usb设备路径 / lpt1 / COMx)
+	PrinterPortKind portKind = PrinterPortKind::Usb;
+	std::queue<std::string> queue;
+	std::mutex mtx;
+	std::condition_variable cv;
+	bool stop = false;
+	std::thread worker;
+};
+
 //这个类采用队列的模式,执行把数据发送给打印机,注意这个类只负责发送,不负责数据内容的组装
 class CPosPrinterWorker
 {
@@ -24,6 +49,9 @@ public:
 	void AddBiaoqianPrinter(std::string data);
 	void AddChufangPrinter(ChufangPrinterContent chufang_data);
 
+	//打印机类型或端口设置变更后调用,重建收银打印端口通道
+	void RefreshShouyinChannels();
+
 	//从队列中读数据
 	void HandleShouyinPrinter();
 	void HandleBiaoqianPrinter();
@@ -56,6 +84,15 @@ private:
 	bool WriteWithTimeout(HANDLE hPort, const void* buffer, DWORD bufferSize, DWORD timeoutMs, DWORD& dwWrite);
 	bool ReadWithTimeout(HANDLE hPort, void* buffer, DWORD bufferSize, DWORD timeoutMs, DWORD& dwRead);
 
+	//通道的建立与销毁,在Start和设置变更时调用,不再每张票都开关端口
+	void StartShouyinChannels();
+	void StopShouyinChannels();
+	void ChannelWorker(std::shared_ptr<CPortSendChannel> ch);
+
+	//串口/并口/USB各自的句柄打开与配置,复用于通道建立与热插拔重开
+	HANDLE OpenComPort(const std::wstring& com_path);
+	bool OpenChannelHandle(std::shared_ptr<CPortSendChannel> ch);
+
 private:
 	std::queue<std::string> m_shouyin_printer_queue;
 	std::queue<std::string> m_biaoqian_printer_queue;
@@ -75,4 +112,8 @@ private:
 
 	//usb打印机的描述集合,用于收银小票打印机的智能识别模式
 	std::vector<std::wstring> m_usb_devices;
+
+	//收银打印的端口通道,句柄常驻打开,每个端口一个发送线程
+	std::vector<std::shared_ptr<CPortSendChannel>> m_shouyin_channels;
+	std::mutex m_shouyin_channels_mutex;
 };