习惯了单片机上面的C语言,使用printf进行输出调试,上位机虽然可以使用日志进行调试,但是很多时候做通信,需要实时显示16进制HEX编码,很不方便,因此自己写了一个使用richTextBox显示的自定义控件,用于刷新格式化的数据到界面,这个地方千万不用使用textBox,会非常卡的,长时间运行各种不稳定,基本的原理就是自定义一个printf函数,将格式化的字符串写入到FIFO中,使用一个异步线程将char转换为string,再使用异步委托显示到richTextBox中,这样可以高效的显示数据,并且耗用极低的CPU,但是内存占用会增加10-40MB,因为string特别占用内存,效果如下:
显示的调试信息效果
下面看使用方法,需要以下4个文件
1.将自定义控件添加到一个容器中。
DebugPrintfControl ^mDebugPrintfControl; //调试
//调试界面初始化 this->mDebugPrintfControl = gcnew DebugPrintfControl(2*1024*1024); this->mDebugPrintfControl->Dock = System::Windows::Forms::DockStyle::Fill; //填充 this->panel7->Controls->Add(this->mDebugPrintfControl); this->mDebugPrintfControl->SetDisplay(false); //默认关闭显示打印2.使用方法,使用有2种,一种是专用的数据包打印,一种就是通用的printf打印
USER_DEBUG.Printf("程序启动\r\n"); sprintf_s(buff, 8 * 1024, "\r\n->收到数据(%d字节)\r\n", Data->DataLen); USER_DEBUG.PrintfDataPack(buff, Data->DataBuff, Data->DataLen);
3.代码如下
DebugPrintf.c
#include "StdAfx.h" #include "DebugPrintf.h" #include <stdio.h> #define WIN32_LEAN_AND_MEAN #include <windows.h> #include <MyFIFO.h> #include <stdlib.h> #include <stdarg.h> //定义成一个可变参数列表的指针 #include "crtdbg.h" //2017-11-29 修复C++异常处理 //2017-12-23 增加缓冲区为5K,并且增加数据包打印输出 DebugPrintf USER_DEBUG; //调试输出 DebugPrintf::DebugPrintf() { this->pDebugFifo = new MyFIFO(128, 5*1024 + 32); } DebugPrintf::~DebugPrintf() { delete(this->pDebugFifo); } //调试信息输出-注意单次长度不要超过5K DWORD DebugPrintf::Printf(const char * format, ...) { char *buff = new char[1024*5 + 32]; DWORD len; va_list ap; try { va_start(ap, format); len = vsnprintf(buff, 1024*5+32, format, ap); va_end(ap); this->pDebugFifo->FIFO_Write((BYTE *)buff, len); } catch (...) { len = 0; } delete []buff; return len; } //数据包打印输出 DWORD DebugPrintf::PrintfDataPack(char *pInfo, BYTE *pDataPack, WORD DataLen) { char *buff = new char[1024 * 5 + 32]; DWORD len = 0; if (pDataPack == nullptr) return 0; try { if (pInfo != nullptr) { len = strlen(pInfo); if (len > 400) { len = 400; pInfo[400] = 0; //限制长度 } if (len > 0) { len = sprintf_s(buff, 5 * 1024, pInfo); //先添加信息头 } } else len = 0; //循环打印报文 if (DataLen > 1500) DataLen = 1500; //限制长度 for (int i = 0; i < DataLen; i++) { len += sprintf_s(&buff[len], 5 * 1024 - len, "%02X ", pDataPack[i]); } this->pDebugFifo->FIFO_Write((BYTE *)buff, len); //写入到FIFO中 } catch (...) { len = 0; } delete []buff; return len; }
DebugPrintf.h
#pragma once #ifndef _DEBUG_PRINTF_ #define _DEBUG_PRINTF_ #define WIN32_LEAN_AND_MEAN #include <windows.h> #include <MyFIFO.h> class DebugPrintf { private:MyFIFO *pDebugFifo; public: DebugPrintf(); ~DebugPrintf(); DWORD Printf(const char * format, ...); //调试输出 DWORD PrintfDataPack(char *pInfo, BYTE *pDataPack, WORD DataLen);//数据包打印输出 MyFIFO *GetFifo() //获取调试数据FIFO { return this->pDebugFifo; } }; extern DebugPrintf USER_DEBUG; //调试输出 #endif //_DEBUG_PRINTF_自定义控件
DebugPrintfControl.c
#include "StdAfx.h" #include "DebugPrintfControl.h" #include "DebugPrintf.h" #define WIN32_LEAN_AND_MEAN #include "windows.h" #include <MyFIFO.h> #include <userlib.h> #include <SystemLog.h> #define CLASS_NAME WindowsFormsApplication_c01::DebugPrintfControl //类名称定义 //异步线程数据打印处理 void CLASS_NAME::BackgroundWorker1_DoWork(void) { MyFIFO *pFifo = USER_DEBUG.GetFifo(); //获取调试FIFO缓冲区 BYTE *p; DWORD len; DWORD cnt; DWORD i; while (1) { try { cnt = pFifo->FIFO_GetDataNumber(); //获取缓冲区中数据数量 if (cnt > 30) { cnt = 30; } if (cnt > 0) { this->mStringBuilder->Clear(); //清空 for (i = 0; i < cnt; i++) //将数据集中到一起,一次打印 { if (pFifo->FIFO_ReadNotCopy(&p, &len) == true) { try { if (this->isShow == true) //开启了显示才显示数据 { p[len] = 0; this->mStringBuilder->Append(CharToString(p)); } } catch (Exception^ e) { SYS_LOG.Write(__FILE__ + __LINE__ + e->Message); Sleep(10); } pFifo->FIFO_ReduceOne(); } } this->DebugPrintString(this->mStringBuilder->ToString()); //一次打印 } Sleep(300); //降低刷新率,尽量一次刷新,保证系统的稳定性 } catch (Exception^ e) { SYS_LOG.Write(__FILE__ + __LINE__ + e->Message); Sleep(100); } } } //存储数据为txt void CLASS_NAME::SaveDataToText(void) { if (this->richTextBox1->Text->Length == 0) { System::Windows::Forms::MessageBox::Show("数据为空,无法进行存储!", "错误", System::Windows::Forms::MessageBoxButtons::OK, System::Windows::Forms::MessageBoxIcon::Error); return; } //进行数据存储 //弹出保存对话框 this->saveFileDialog1->Filter = "文本文件(*.txt)|*.txt|所有文件(*.*)|*.*"; //支持的扩展名 this->saveFileDialog1->FileName = "调试日志记录文件.txt"; //主设置默认文件extension(可以不设置) this->saveFileDialog1->DefaultExt = "txt"; System::Windows::Forms::DialogResult result = this->saveFileDialog1->ShowDialog(); //显示保存对话框 if (result == System::Windows::Forms::DialogResult::OK) //点击了保存 { array<BYTE> ^data; System::Text::Encoding^ unicode = System::Text::Encoding::UTF8; try { System::IO::FileStream ^fs = System::IO::File::Create(this->saveFileDialog1->FileName->ToString()); String ^str = this->richTextBox1->Text->Replace("\n", "\r\n"); //替换换行符 data = unicode->GetBytes(str); if (data == nullptr || data->Length == 0) { System::Windows::Forms::MessageBox::Show("数据为空,存储失败!", "错误", System::Windows::Forms::MessageBoxButtons::OK, System::Windows::Forms::MessageBoxIcon::Error); return; } fs->Write(data, 0, data->Length); //写入数据 fs->Close(); //关闭文件 if (System::Windows::Forms::MessageBox::Show("保存数据成功,是否打开文件!", "存储成功", System::Windows::Forms::MessageBoxButtons::YesNo, System::Windows::Forms::MessageBoxIcon::Question) == System::Windows::Forms::DialogResult::Yes) { //打开文件 //检查文件是否存在 if (!System::IO::File::Exists(this->saveFileDialog1->FileName->ToString())) { System::Windows::Forms::MessageBox::Show("打开文件失败,文件不存在!", "错误", System::Windows::Forms::MessageBoxButtons::OK, System::Windows::Forms::MessageBoxIcon::Error); return; } //调用默认程序打开文件 System::Diagnostics::Process::Start(this->saveFileDialog1->FileName->ToString()); } } catch (System::IO::IOException^ e) { System::Windows::Forms::MessageBox::Show("文件被占用,请关闭文件或修改导出文件名!", "错误", System::Windows::Forms::MessageBoxButtons::OK, System::Windows::Forms::MessageBoxIcon::Error); return; } } } //显示开关设置 void CLASS_NAME::SetDisplay(bool isShow) { this->isShow = isShow; }
DebugPrintfControl.h
#pragma once #define WIN32_LEAN_AND_MEAN #include "windows.h" #include "DebugPrintf.h" #include "SystemLog.h" //2017-12-23 增加删除超出部分文本功能,增加显示开关,增加自动滚动设置 //2018-01-10 优化TextBox占用率 using namespace System; using namespace System::ComponentModel; using namespace System::Collections; using namespace System::Windows::Forms; using namespace System::Data; using namespace System::Text; using namespace System::Drawing; namespace WindowsFormsApplication_c01 { /// <summary> /// DebugPrintfControl 摘要 /// </summary> public ref class DebugPrintfControl : public System::Windows::Forms::UserControl { String ^NormalInfo; //正常打印信息 String ^ErrorInfo; //故障信息 private: System::Windows::Forms::ToolStripMenuItem^ 显示开ToolStripMenuItem; delegate void Delegate_DebugString(String ^pString);//UI托管 Delegate_DebugString ^mDataDebug; //数据包调试 private: System::Windows::Forms::Timer^ timer1; private: System::Windows::Forms::TextBox^ textBox2; bool isShow; //是否显示 private: System::Windows::Forms::ToolStripMenuItem^ 自动滚动开ToolStripMenuItem; bool isLogAutoScroll; //是否自动滚动 StringBuilder ^mStringBuilder; private: System::Windows::Forms::RichTextBox^ richTextBox1; DWORD ShowStrSize; //显示的字符串最大长度 public: DebugPrintfControl(DWORD ShowStrSize) { this->ShowStrSize = ShowStrSize; if (this->ShowStrSize < 200) this->ShowStrSize = 200; if (this->ShowStrSize > 100 * 1024 * 1024) this->ShowStrSize = 100 * 1024 * 1024; InitializeComponent(); // //TODO: 在此处添加构造函数代码 // this->isShow = true; //默认开启显示 this->isLogAutoScroll = true; //是否自动滚动 this->mStringBuilder = gcnew StringBuilder(1024*1024); mDataDebug = gcnew Delegate_DebugString(this, &WindowsFormsApplication_c01::DebugPrintfControl::DebugToString); //数据包调试 this->backgroundWorker1->RunWorkerAsync(); //执行异步线程 } protected: /// <summary> /// 清理所有正在使用的资源。 /// </summary> ~DebugPrintfControl() { if (components) { delete components; } } //任务 void BackgroundWorker1_DoWork(void); //存储数据为txt void SaveDataToText(void); //打印debug信息,打印string void DebugToString(String ^pStr) { int temp; if (pStr == nullptr) return; try { temp = this->richTextBox1->TextLength + pStr->Length; //计算总长度 if (temp > (this->ShowStrSize - 100)) { temp -= (this->ShowStrSize - 100); //删掉最前面的 this->richTextBox1->Select(0, temp); //选择超出部分 this->richTextBox1->SelectedText = ""; //设置超出部分为空,相当于删掉超出部分的数据 } //使用 AppendText 后会自动滑动到最后面,不受控制 this->richTextBox1->AppendText(pStr); //可以极大的降低CPU占用率 } catch (Exception^ e) { SYS_LOG.Write(__FILE__ + __LINE__ + e->Message); Sleep(100); //延时5秒,防止不停崩溃消耗电脑资源 } //限制长度 /*temp = this->textBox1->TextLength + pStr->Length; //计算总长度 if (temp > (1024*1000 - 100)) { temp -= (1024*1000 - 100); //删掉最前面的 this->textBox1->Select(0, temp); //选择超出部分 this->textBox1->SelectedText = ""; //设置超出部分为空,相当于删掉超出部分的数据 } this->textBox1->Text += pStr; //下面两行代码可以让数据一直显示在最下面 if (this->isLogAutoScroll == true) //自动滚动开启了 { this->textBox1->SelectionStart = this->textBox1->Text->Length; this->textBox1->ScrollToCaret(); }*/ } //调用托管打印数据 void DebugPrintString(String ^pStr) { try { this->BeginInvoke(mDataDebug, pStr); //打印实时日志 } catch (Exception^ e) { SYS_LOG.Write(__FILE__ + __LINE__ + e->Message); Sleep(100); //延时5秒,防止不停崩溃消耗电脑资源 } } public: void SetDisplay(bool isShow); //显示开关设置 protected: private: System::Windows::Forms::ContextMenuStrip^ contextMenuStrip1; private: System::Windows::Forms::ToolStripMenuItem^ 另存为ToolStripMenuItem; private: System::Windows::Forms::ToolStripMenuItem^ 清空显示ToolStripMenuItem; private: System::Windows::Forms::SaveFileDialog^ saveFileDialog1; private: System::ComponentModel::BackgroundWorker^ backgroundWorker1; private: System::ComponentModel::IContainer^ components; private: /// <summary> /// 必需的设计器变量。 /// </summary> #pragma region Windows Form Designer generated code /// <summary> /// 设计器支持所需的方法 - 不要 /// 使用代码编辑器修改此方法的内容。 /// </summary> void InitializeComponent(void) { this->components = (gcnew System::ComponentModel::Container()); this->contextMenuStrip1 = (gcnew System::Windows::Forms::ContextMenuStrip(this->components)); this->另存为ToolStripMenuItem = (gcnew System::Windows::Forms::ToolStripMenuItem()); this->清空显示ToolStripMenuItem = (gcnew System::Windows::Forms::ToolStripMenuItem()); this->显示开ToolStripMenuItem = (gcnew System::Windows::Forms::ToolStripMenuItem()); this->自动滚动开ToolStripMenuItem = (gcnew System::Windows::Forms::ToolStripMenuItem()); this->saveFileDialog1 = (gcnew System::Windows::Forms::SaveFileDialog()); this->backgroundWorker1 = (gcnew System::ComponentModel::BackgroundWorker()); this->timer1 = (gcnew System::Windows::Forms::Timer(this->components)); this->textBox2 = (gcnew System::Windows::Forms::TextBox()); this->richTextBox1 = (gcnew System::Windows::Forms::RichTextBox()); this->contextMenuStrip1->SuspendLayout(); this->SuspendLayout(); // // contextMenuStrip1 // this->contextMenuStrip1->Items->AddRange(gcnew cli::array< System::Windows::Forms::ToolStripItem^ >(4) { this->另存为ToolStripMenuItem, this->清空显示ToolStripMenuItem, this->显示开ToolStripMenuItem, this->自动滚动开ToolStripMenuItem }); this->contextMenuStrip1->Name = L"contextMenuStrip1"; this->contextMenuStrip1->Size = System::Drawing::Size(149, 92); this->contextMenuStrip1->Opening += gcnew System::ComponentModel::CancelEventHandler(this, &DebugPrintfControl::contextMenuStrip1_Opening); // // 另存为ToolStripMenuItem // this->另存为ToolStripMenuItem->Name = L"另存为ToolStripMenuItem"; this->另存为ToolStripMenuItem->Size = System::Drawing::Size(148, 22); this->另存为ToolStripMenuItem->Text = L"另存为(.txt)"; this->另存为ToolStripMenuItem->Click += gcnew System::EventHandler(this, &DebugPrintfControl::另存为ToolStripMenuItem_Click); // // 清空显示ToolStripMenuItem // this->清空显示ToolStripMenuItem->Name = L"清空显示ToolStripMenuItem"; this->清空显示ToolStripMenuItem->Size = System::Drawing::Size(148, 22); this->清空显示ToolStripMenuItem->Text = L"清空显示"; this->清空显示ToolStripMenuItem->Click += gcnew System::EventHandler(this, &DebugPrintfControl::清空显示ToolStripMenuItem_Click); // // 显示开ToolStripMenuItem // this->显示开ToolStripMenuItem->Name = L"显示开ToolStripMenuItem"; this->显示开ToolStripMenuItem->Size = System::Drawing::Size(148, 22); this->显示开ToolStripMenuItem->Text = L"显示:开"; this->显示开ToolStripMenuItem->Click += gcnew System::EventHandler(this, &DebugPrintfControl::显示开ToolStripMenuItem_Click); // // 自动滚动开ToolStripMenuItem // this->自动滚动开ToolStripMenuItem->Name = L"自动滚动开ToolStripMenuItem"; this->自动滚动开ToolStripMenuItem->Size = System::Drawing::Size(148, 22); this->自动滚动开ToolStripMenuItem->Text = L"自动滚动:开"; this->自动滚动开ToolStripMenuItem->Visible = false; this->自动滚动开ToolStripMenuItem->Click += gcnew System::EventHandler(this, &DebugPrintfControl::自动滚动开ToolStripMenuItem_Click); // // backgroundWorker1 // this->backgroundWorker1->WorkerReportsProgress = true; this->backgroundWorker1->WorkerSupportsCancellation = true; this->backgroundWorker1->DoWork += gcnew System::ComponentModel::DoWorkEventHandler(this, &DebugPrintfControl::backgroundWorker1_DoWork); this->backgroundWorker1->ProgressChanged += gcnew System::ComponentModel::ProgressChangedEventHandler(this, &DebugPrintfControl::backgroundWorker1_ProgressChanged); this->backgroundWorker1->RunWorkerCompleted += gcnew System::ComponentModel::RunWorkerCompletedEventHandler(this, &DebugPrintfControl::backgroundWorker1_RunWorkerCompleted); // // timer1 // this->timer1->Enabled = true; this->timer1->Interval = 1000; this->timer1->Tick += gcnew System::EventHandler(this, &DebugPrintfControl::timer1_Tick); // // textBox2 // this->textBox2->Anchor = static_cast<System::Windows::Forms::AnchorStyles>((System::Windows::Forms::AnchorStyles::Top | System::Windows::Forms::AnchorStyles::Right)); this->textBox2->BackColor = System::Drawing::SystemColors::ScrollBar; this->textBox2->BorderStyle = System::Windows::Forms::BorderStyle::None; this->textBox2->Font = (gcnew System::Drawing::Font(L"微软雅黑", 11.25F, System::Drawing::FontStyle::Regular, System::Drawing::GraphicsUnit::Point, static_cast<System::Byte>(134))); this->textBox2->ForeColor = System::Drawing::Color::FromArgb(static_cast<System::Int32>(static_cast<System::Byte>(192)), static_cast<System::Int32>(static_cast<System::Byte>(0)), static_cast<System::Int32>(static_cast<System::Byte>(0))); this->textBox2->Location = System::Drawing::Point(787, 8); this->textBox2->Name = L"textBox2"; this->textBox2->Size = System::Drawing::Size(68, 20); this->textBox2->TabIndex = 1; this->textBox2->Text = L"显示关闭"; this->textBox2->TextAlign = System::Windows::Forms::HorizontalAlignment::Center; this->textBox2->Visible = false; // // richTextBox1 // this->richTextBox1->ContextMenuStrip = this->contextMenuStrip1; this->richTextBox1->DetectUrls = false; this->richTextBox1->Dock = System::Windows::Forms::DockStyle::Fill; this->richTextBox1->HideSelection = false; this->richTextBox1->Location = System::Drawing::Point(0, 0); this->richTextBox1->Name = L"richTextBox1"; this->richTextBox1->ReadOnly = true; this->richTextBox1->ScrollBars = System::Windows::Forms::RichTextBoxScrollBars::ForcedVertical; this->richTextBox1->Size = System::Drawing::Size(882, 426); this->richTextBox1->TabIndex = 2; this->richTextBox1->Text = L""; // // DebugPrintfControl // this->AutoScaleDimensions = System::Drawing::SizeF(6, 12); this->AutoScaleMode = System::Windows::Forms::AutoScaleMode::Font; this->Controls->Add(this->textBox2); this->Controls->Add(this->richTextBox1); this->Name = L"DebugPrintfControl"; this->Size = System::Drawing::Size(882, 426); this->contextMenuStrip1->ResumeLayout(false); this->ResumeLayout(false); this->PerformLayout(); } #pragma endregion private: System::Void 清空显示ToolStripMenuItem_Click(System::Object^ sender, System::EventArgs^ e) { this->richTextBox1->Clear(); } private: System::Void 另存为ToolStripMenuItem_Click(System::Object^ sender, System::EventArgs^ e) { this->SaveDataToText(); } //异步线程 private: System::Void backgroundWorker1_DoWork(System::Object^ sender, System::ComponentModel::DoWorkEventArgs^ e) { this->BackgroundWorker1_DoWork(); } //状态改变,刷新UI private: System::Void backgroundWorker1_ProgressChanged(System::Object^ sender, System::ComponentModel::ProgressChangedEventArgs^ e) { } //结束 private: System::Void backgroundWorker1_RunWorkerCompleted(System::Object^ sender, System::ComponentModel::RunWorkerCompletedEventArgs^ e) { } //显示开关切换 private: System::Void 显示开ToolStripMenuItem_Click(System::Object^ sender, System::EventArgs^ e) { if (this->isShow == true) //开启了显示 { this->isShow = false; this->显示开ToolStripMenuItem->Text = "显示:关"; } else { this->isShow = true; this->显示开ToolStripMenuItem->Text = "显示:开"; } } //弹出时更新菜单显示开关名称 private: System::Void contextMenuStrip1_Opening(System::Object^ sender, System::ComponentModel::CancelEventArgs^ e) { if (this->isShow == true) //开启了显示 { this->显示开ToolStripMenuItem->Text = "显示:开"; } else { this->显示开ToolStripMenuItem->Text = "显示:关"; } } //1秒定时器,如果显示关闭后进行提示 private: System::Void timer1_Tick(System::Object^ sender, System::EventArgs^ e) { if (isShow == false) //显示关闭了,闪烁提示 { if (this->textBox2->Visible == false) { this->textBox2->Visible = true; } else { this->textBox2->Visible = false; } } else //影藏提示 { this->textBox2->Visible = false; } } //自动滚动设置 private: System::Void 自动滚动开ToolStripMenuItem_Click(System::Object^ sender, System::EventArgs^ e) { if (this->isLogAutoScroll == true) //自动滚动开启了 { this->isLogAutoScroll = false; this->自动滚动开ToolStripMenuItem->Text = "自动滚动:关"; } else { this->isLogAutoScroll = true; this->自动滚动开ToolStripMenuItem->Text = "自动滚动:开"; } } }; }