Labwindows 虚拟仪器高级应用-文本、文件类程序设计技术之:十六进制文本文件转换
1.2 十六进制文本文件转换
1.2.1 十六进制文本文件转换设计方法
在LabWindows/CVI 程序设计中,一些参数需要进行初始化,如字符串常量、动态链接库、静态链接库等。如字符串常量内容非常大,通常都会以文本文件的形式存储到磁盘上,动态链接库、静态链接库等也是如此。有没有办法,将这些文件都一同打包到可执行文件中,使其只包含一个可执行文件呢?将常量文本、各种库文件都打包到可执行文件中,便于文件的管理与操作。办法是有的,可以将这些文件导出为十六进制文本文件格式,并以C 语言的.h 头文件形式出现,在.c 源程序中通过#include 方式引用并在实现代码中调用,这些文件就可以通过隐式方式随可执行文件一起装载到内存中,调用简单而且方便。
在本例程中,由于涉及大量数据的转换,而这些转换又是在一个for 循环语句中完成的,因此,程序执行期间不能及时响应外部的操作,如拖拽标题栏、点击面板按钮等,为了提高系统响应的及时性以及算法执行效率,采用多线程技术,将转换算法放入一个新线程中执行,即实现界面逻辑与算法逻辑的分离,效果会更好一些。
一个采用了多线程技术的应用程序可以更好地利用系统资源。其主要优势在于充分利用了CPU 的空闲时间片,可以用尽可能少的时间来对用户的要求做出响应,使得进程的整体运行效率得到较大提高,同时增强了应用程序的灵活性。更为重要的是,由于同一进程的所有线程是共享同一内存的,所以不需要特殊的数据传送机制,不需要建立共享存储区或共享文件,从而使得不同任务之间的协调操作与运行、数据的交互、资源的分配等问题更加易于解决。
对于本例程来说,界面逻辑虽然在算法执行过程中可以实现移动、按钮点击等操作,但如果在转换没有完成时进行第二次转换操作(如点击转换按钮),可能会出现逻辑错误,因此,最好在程序设计时考虑按钮的有效性或按钮自锁功能。
1.2.2 十六进制文本文件转换程序设计
(1)面板设计
编写一个十六进制文本文件转换程序,可将各种文件如.txt、.dll、.lib、.obj 、.exe 等转换为十六进制文本格式,如“0x0A3, 0xB4, … ”等,并以.h 或.c 等格式存储,方便在程序运行时载入。点击转换按钮,弹出打开文件对话框,选择要转换的文件,之后会弹出保存对话框,提示保存为何种类型的文件,最后程序自动进行转换,在转换进度条中显示实际的转换进度,进度条到达满刻度时,转换完成。在编辑状态下双击Numeric Slide 控件,弹出Edit Numeric Slide 对话框,设置Data type 为int,Control mode 为Indicator ,点击Range Values… 按钮,弹出Edit Range Values 对话框,设置最小值Minimum 为0,如图1-5 所示,点击Show/Hide Parts… 按钮,弹出Show/Hide Parts 对话框,设置相关属性为No Markers 、No Ticks 等,如图1-6 所示。面板设计如图1-7 所示,面板中主
图1-5 Edit Range Values 对话框要
图1-6 Show/Hide Parts 对话框
图1-7 十六进制文本文件转换面板
表1-5 控件属性设置表
常量名 |
控件类型 |
控件的主要属性 |
PANEL |
Panel |
标题:十六进制文本文件转换回调函数:panelCB |
PROGRESS |
Numeric Slide |
标题:转换进度 |
TRANSLATE |
Command Button |
标题:转换回调函数:Translate |
(2)程序源代码
//头文件声明
#include <ansi_c.h>
#include <formatio.h>
#include <utility.h>
#include <cvirte.h>
#include <userint.h>
#include "十六进制文本文件转换.h"
//全局静态变量
static int BytesRead;
static CmtThreadFunctionID ThreadID;
static int panelHandle;
static char *buffer;
char SaveDir[MAX_PATHNAME_LEN];
//线程回调函数声明
int CVICALLBACK HextoText (void *functionData);
//主函数
int main (int argc, char *argv[])
{
if (InitCVIRTE (0, argv, 0) == 0)
return -1;
/* out of memory */
if ((panelHandle = LoadPanel (0, " 十六进制文本文件转换.uir", PANEL)) < 0)
return -1;
DisplayPanel (panelHandle);
RunUserInterface ();
//释放资源
free (buffer);
DiscardPanel (panelHandle);
return 0;
}
//面板回调函数
int CVICALLBACK panelCB (int panel, int event, void *callbackData,
int eventData1, int eventData2)
{
switch (event)
{
case EVENT_CLOSE:
QuitUserInterface (0);
break;
}
return 0;
}
//转换按钮
int CVICALLBACK Translate (int panel, int control, int event,
void *callbackData, int eventData1, int eventData2)
{
int SaveSel;
int Present;
ssize_t FileSize;
int sel;
char path[MAX_PATHNAME_LEN];
int FileHandle;
switch (event)
{
case EVENT_COMMIT:
// 打开文件,获得文件信息
sel = FileSelectPopup ("", "*.txt", "*.txt;*.dll;*.lib;*.obj;*.exe", " 打开", VAL_LOAD_BUTTON,
0, 0, 1, 1, path);
Present = GetFileInfo (path, &FileSize);
// 保存为十六进制文本文件
SaveSel = FileSelectPopup ("", "*.h", "*.h;*.c;*.txt", " 保存", VAL_SAVE_BUTTON, 0, 0, 1, 1,
SaveDir);
// 判断文件是否存在
if ((sel > 0) && (Present == 1) && (SaveSel > 0))
{
buffer = malloc (FileSize * sizeof(char) + 1);buffer[0] =
'\0';
// 打开文件,读取数据并关闭文件
FileHandle = OpenFile (path, VAL_READ_ONLY, VAL_OPEN_AS_IS, VAL_BINARY);
BytesRead = ReadFile (FileHandle, buffer, FileSize);
CloseFile (FileHandle);
// 创建线程
CmtScheduleThreadPoolFunction
(DEFAULT_THREAD_POOL_HANDLE,
HextoText,
NULL, &ThreadID);
// 等待线程执行完毕
CmtWaitForThreadPoolFunctionCompletion
(DEFAULT_THREAD_POOL_HANDLE,
ThreadID, OPT_TP_PROCESS_EVENTS_WHILE_WAITING);
// 释放线程资源
CmtReleaseThreadPoolFunctionID (DEFAULT_THREAD_POOL_HANDLE, ThreadID);
}
break;
}
return 0;
}
//线程回调函数—将十六进制数据转换为文本形式
int CVICALLBACK HextoText (void *functionData)
{
int i;
int file;
int Bytes;
unsigned char temp;
unsigned char str[10];
unsigned char *bin;
//设置Numeric Slide 控件最大值,用作进度条
SetCtrlAttribute (panelHandle, PANEL_PROGRESS, ATTR_MAX_VALUE, BytesRead);
//转换为十六进制
file = OpenFile (SaveDir, VAL_WRITE_ONLY, VAL_TRUNCATE, VAL_ASCII);
bin = malloc (BytesRead * sizeof(char) * 6 + 1);
bin[0] =
'\0';
for (i = 0; i < BytesRead; i ++)
{
str[0] = '0';
str[1] = 'x';
//高四位
temp = (buffer[i] >> 4) & 0x0F;
if (temp >= 10)
{
str[2] = temp - 10 + 'A';
}
else
{
str[2] = temp + '0';
}
//低四位
temp = (buffer[i]) & 0x0F;
if (temp >= 10)
{
str[3] = temp - 10 + 'A';
}
else
{
str[3] = temp + '0';
}
str[4] = ',';
str[5] = '\0';
strcat (bin, str);
//设置Numeric Slide 控件当前值
SetCtrlVal (panelHandle, PANEL_PROGRESS, i);
}
//将数据保存到文件
Bytes = WriteFile (file, bin, strlen(bin));
//释放资源
free (bin);
CloseFile (file);return 0;
}
(3)程序注释
① CmtScheduleThreadPoolFunction 函数通知线程池创建一个新线程。如果线程池存在空闲线程,则被分配给该线程。如果没有空闲线程并且线程池中的线程没有达到线程池上限,线程池将创建一个新线程。当线程池达到线程上限且没有空闲线程,线程池将等待直到有空闲线程或有可用线程再分配给该线程。如果需要用到定时器回调函数,可以采用异步定时器(在自身线程中执行),不需要用定时器控件再开辟一个新线程。函数原型为:
int CmtScheduleThreadPoolFunction (int poolHandle, ThreadFunctionPtr threadFunction, void *threadFunctionData, int *threadFunctionID);
poolHandle :线程池句柄。可以采用DEFAULT_THREAD_POOL_HANDLE 常量,表示一个默认的线程池句柄。如果采用RT 应用,不能使用默认线程池句柄,由于默认线程池句柄即使在RTMain 函数退出后仍在运行,使系统运行不稳定,需要创建一个新线程池并且在退出RTMain 函数之前释放线程池句柄。
threadFunction :线程函数。函数原型为:
int CVICALLBACK ThreadFunction (void *functionData);
*functionData :线程函数参数传递数据。在任何线程中,可以通过CmtGetThreadPoolFunction-Attribute 函数的ATTR_TP_FUNCTION_RETURN_VALUE 属性获得该线程所要传递的数据。
*threadFunctionData :线程函数回调数据。不能传递局部变量地址或其他可能在函数执行过程中无效的地址。
*threadFunctionID :标识线程函数的唯一ID 号。可以通过ID 号来获得线程函数的各种属性,包括线程函数的返回值,并可以将ID 号传递给CmtWaitForThreadPoolFunctionCompletion 函数,等待线程函数执行完毕进行其他操作。如果不需要此参数,输入NULL 。如果该参数值不为NULL, 必须用CmtReleaseThreadPoolFunctionID 函数释放ID 号。值得注意的是,如果该工程使用的版本低于LabWindows/CVI 2009 ,并在LabWindows/CVI 2009 以上的版本中重新编译时,必须在工程中包含头文件cvi2009compat.h 。
② CmtWaitForThreadPoolFunctionCompletion 函数等待指定线程函数执行完毕。可以使用CmtScheduleThreadPoolFunctionAdv 函数等待指定线程函数执行完毕。如果在线程中调用CmtWaitForThreadPoolFunctionCompletion 函数并且使用了ActiveX 函数,由于在线程初始化时使用了ActiveX 内部线程模式,可能造成两个线程之间的死锁,可以调用CA_InitActiveXThreadStyle-ForCurrentThread 函数为ActiveX 设置单独线程。函数原型为:
int CmtWaitForThreadPoolFunctionCompletion (int poolHandle, int threadFunctionID, unsigned int options);
options:选项参数。OPT_TP_PROCESS_EVENTS_WHILE_WAITING 指当等待线程执行期间,线程可以响应事件;当参数为0 时指等待线程执行期间,线程不能响应事件。任何线程中所创建的面板或对话框窗口在没有被释放以前,一定会响应一些事件。如果此时线程不能响应事件,可能导致系统“挂起”。为了避免这种情况的产生,应当使用OPT_TP_PROCESS_EVENTS_WHILE_ WAITING 参数来等待响应发送消息事件、显示面板事件及对话框事件。
③ CmtReleaseThreadPoolFunctionID 函数释放被CmtScheduleThreadPoolFunction 函数或CmtScheduleThreadPoolFunctionAdv 函数创建的线程ID 号,如果 CmtScheduleThreadPoolFunction 函数或CmtScheduleThreadPoolFunctionAdv 函数的threadFunctionID 参数值设置为NULL,系统将自动在线程调用执行完毕之后释放线程ID,可以不必调用此函数。函数不会中断一个将要执行或正在执行的线程函数。调用CmtDiscardThreadPool 函数将自动释放属于指定线程池的任何线程ID 号。函数原型为:
int CmtReleaseThreadPoolFunctionID (int poolHandle, int threadFunctionID);
④ 不使用多线程技术的程序处理方法如果不使用多线程技术,只需要将Translate 回调函数中的CmtScheduleThreadPoolFunction 、CmtWaitForThreadPoolFunctionCompletion 和CmtRelease- ThreadPoolFunctionID 函数删除,直接调用HextoText 函数,但此函数需要一个空指针类型数据,可以借用Translate 中的callbackData 参数。采用这种技术能显著降低CPU 的使用率,在系统资源有限的情况下是不错的解决方案,其实现代码如下:
int CVICALLBACK Translate (int panel, int control, int event, void *callbackData, int eventData1, int eventData2)
{ int SaveSel; int Present; ssize_t FileSize; int sel;
char path[MAX_PATHNAME_LEN];
int FileHandle;
switch (event)
{
case EVENT_COMMIT:
// 打开文件,获得文件信息
sel = FileSelectPopup ("", "*.txt", "*.txt;*.dll;*.lib;*.obj;*.exe", " 打开", VAL_LOAD_BUTTON, 0, 0, 1, 1, path);
Present = GetFileInfo (path, &FileSize);
// 保存为十六进制文本文件
SaveSel = FileSelectPopup ("", "*.h", "*.h;*.c;*.txt", " 保存", VAL_SAVE_BUTTON, 0, 0, 1, 1, SaveDir);
// 判断文件是否存在
if ((sel > 0) && (Present == 1) && (SaveSel > 0))
{
buffer = malloc (FileSize * sizeof(char) + 1);
buffer[0] = '\0';
// 打开文件,读取数据并关闭文件
FileHandle = OpenFile (path, VAL_READ_ONLY, VAL_OPEN_AS_IS, VAL_BINARY);
BytesRead = ReadFile (FileHandle, buffer, FileSize);
CloseFile (FileHandle);
// 不使用多线程技术
HextoText (callbackData);
}
break;
}
return 0;
}
在HextoText 函数中添加SetSleepPolicy 函数,设置系统的Sleep 策略,使系统不进入Sleep 状态,以加快算法执行速度,提高算法效率,添加ProcessSystemEvents 函数,使系统能及时响应鼠标事件。算法只需要进行少量代码修改,就能完成与多线程方式类似的操作。HextoText 函数实现代码修改如下:
int CVICALLBACK HextoText (void *functionData)
{
int i;
int file;
int Bytes;
unsigned char temp;
unsigned char str[10];
unsigned char *bin;
//设置Numeric Slide 控件最大值,用作进度条
SetCtrlAttribute (panelHandle, PANEL_PROGRESS, ATTR_MAX_VALUE, BytesRead);
//转换为十六进制
file = OpenFile (SaveDir, VAL_WRITE_ONLY, VAL_TRUNCATE, VAL_ASCII);
bin = malloc (BytesRead * sizeof(char) * 6 + 1); bin[0] = '\0'; for (i = 0; i < BytesRead; i ++) {
str[0] = '0'; str[1] = 'x'; //高四位temp = (buffer[i] >> 4) & 0x0F; if (temp >= 10){ str[2] = temp - 10 + 'A';
}
else
{
str[2] = temp + '0'; } //低四位temp = (buffer[i]) & 0x0F;if (temp >= 10){
str[3] = temp - 10 + 'A';
}
else
{
str[3] = temp + '0'; } str[4] = ','; str[5] = '\0'; strcat (bin, str); //设置Numeric Slide 控件当前值SetCtrlVal (panelHandle, PANEL_PROGRESS, i);//处理面板事件SetSleepPolicy (VAL_SLEEP_NONE); ProcessSystemEvents ();
} //将数据保存到文件Bytes = WriteFile (file, bin, strlen(bin)); //释放资源 free (bin); CloseFile (file); return 0;
}
(4)运行效果图
点击工具栏中的Debug Project 按钮,程序开始运行,其效果如图1-8 所示。
图1-8 运行效果图