`

在自己的图形界面里调用命令行程序

阅读更多
<script>function StorePage(){d=document;t=d.selection?(d.selection.type!='None'?d.selection.createRange().text:''):(d.getSelection?d.getSelection():'');void(keyit=window.open('http://www.365key.com/storeit.aspx?t='+escape(d.title)+'&u='+escape(d.location.href)+'&c='+escape(t),'keyit','scrollbars=no,width=475,height=575,left=75,top=20,status=no,resizable=yes'));keyit.focus();}</script> 课程设计,做一个小型编译器。
本人做的是一个图形界面的编译器,美其名曰IDE吧。
做到最后一步的时候,发现如何解释中间代码成了一个大问题!我有如下几种方案:
1,在IDE中调用解释程序,对中间代码进行解释执行。
2,通过自己的代码来模拟一个命令行。
3,让解释程序在后台运行,将输入输出重定向到自己的某个控件(如textBox等)

客观来讲:
第1种方案的好处是简单,大部分工作是命令行帮你做了,但是缺点是:IDE里突然弹出一个命令行程序,怎么看都别扭,不是主流的IDE所为;而且,若将这种成果交给老师的话,估计老师会怀疑这个命令行程序是你本人做的。
第2种方案的好处是一切都在控制之中,不过要模拟一个命令行,你可以到微软当技术骨干了!
第3种方案的好处是自然,一切都在IDE中进行;其实不然,需要后台程序支持,最起码要一个命令行程序支持。而且对于系统编程不是很熟悉的朋友,这个在技术上对程序员有很高的要求。

至于我嘛,由于时间有限,只能选在第1种方案啦,其实我很喜欢第3种方案的,但是它设计到很多系统编程的知识,而我虽然学过,但现在对什么管道操作也不是很熟练了,哎,只能留待以后来实现了。

下面是我从网上转载的一篇文章,是对第3种方案的证明。希望对这方面感兴趣的朋友有所帮助。如果有朋友写了这方面的程序,我们不妨交流交流,共同进步。



编写自己的"IDE"

  -- 如何在图形界面中实时捕获控制台程序的标准输出.

  IDE是集成开发环境(Integrated Development Environment)的简称。印象里有很多出色的IDE,比如JBuilder和Kylix,比如Visual Studio。不知大家是否留意过,大多数IDE本身只提供代码编辑、工程管理等人机交互功能,我们在IDE中编译代码、调试程序时,IDE需要调用命令 行的编译器、调试器完成相应的操作。例如,使用Visual Studio编译C++程序时,我们会在IDE下方的Output窗口中看到编译和连接的全过程,虽然我们看不到弹出的DOS窗口,但实际上是IDE先后 启动了Microsoft C++编译器cl.exe和连接器link.exe这两个命令行程序,而cl.exe和link.exe的输出又实时反映到了IDE的Output窗口 中。还有,我们可以在Visual Studio中配置自己需要的工具程序(比如特殊的编译器),然后让Visual Studio在适当的时候运行这些工具,并将工具程序的输出实时显示到Output窗口中。下图是我在Visual Studio 6.0的Output窗口中运行J2SDK的javac.exe编译java源程序并显示程序中语法错误的情形:

图片点击可在新窗口打开查看

  也就是说,大多数IDE工具都可以在集成环境中调用特定的命令行程序(WIN32里更确切的说法是控制台程序),然后实时捕获它们的输出(这多半是输出到标准的stdout和stderr流里的东西),并将捕获到的信息显示在图形界面的窗口中。

  这显然是一种具备潜在价值的功能。利用这一技术,我们至少可以

  1. 编写出自己的IDE,如果我们有足够的耐心的话;

  2. 在我们自己的应用程序里嵌入全文检索功能(调用Borland C++里的grep.exe工具),或者压缩和解压缩功能(调用控制台方式的压缩解压程序,比如arj.exe、pkzip.exe等);

3. 连接其他人编写的,或者我们自己很久以前编写的控制台程序——我经常因为难以调用一个功能强大但又没有源码的控制台程序而苦恼万分。

  这样好的功能是如何实现的呢?

  首先,如果我们想做的是用一个控制台程序调用另一个控制台程序,那就再简单不过了。我们只消把父进程的stdout重定向到某个匿名管道的 WRITE端,然后启动子进程,这时,子进程的stdout因为继承的关系也连在了管道的WRITE端,子进程的所有标准输出都写入了管道,父进程则在管 道的另一端随时“侦听”——这一技术叫做输入输出的重定向。

  可现在的问题是,GUI方式的Windows程序根本没有控制台,没有stdin、stdout之类的东西,子进程又是别人写好的东西无法更改,这重定向该从何谈起呢?

  还有另外一招:我们可以直接在调用子进程时用命令行中的管道指令“>”将子进程的标准输出重定向到一个文件,子进程运行完毕后再去读取文 件内容。这种方法当然可行,但它的问题是,我们很难实时监控子进程的输出,如果子进程不是随时刷新stdout的话,那我们只能等一整块数据实际写入文件 之后才能看到运行结果;况且,访问磁盘文件的开销也远比内存中的管道操作来得大。

  我这里给出的方案其实很简单:既然控制台程序可以调用另一个控制台程序并完成输入输出的重定向,那我们完全可以编写一个中介程序,这个中介程序 调用我们需要调用的工具程序并随时获取该程序的输出信息,然后直接将信息用约定的进程间通讯方式(比如匿名管道)传回GUI程序,就象下图中这样:

图片点击可在新窗口打开查看

  图中,工具程序和中介程序都是以隐藏的方式运行的。工具程序原本输出到stdout的信息被重定向到中介程序开辟的管道中,中介程序再利用 GUI程序创建的管道将信息即时传递到GUI程序的一个后台线程里,后台线程负责刷新GUI程序的用户界面(使用后台线程的原因是,只有这样才可以保证信 息在GUI界面中随时输出时不影响用户正在进行的其他操作,就象我们在Visual Studio中执行耗时较长的编译功能那样)。

我写的中介程序名字叫wSpawn,这个名字来自Visual Studio里完成类似功能的中介程序VcSpawn(你可以在Visual Studio的安装目录中找到它)。我的wSpawn非常简单,它利用系统调用_popen()同时完成创建子进程和输入输出重定向两件工作。GUI程序 则使用一种特殊的命令行方式调用wSpawn:

  wspawn –h <n> <command> [arg1] [arg2] ...

  其中,-h后跟的是GUI程序提供的管道句柄,由GUI程序自动将其转换为十进制数字,wSpawn运行时将信息写入该句柄中,随后的内容是GUI程序真正要执行的命令行,例如调用C++编译器cl.exe的方式大致如下:

wspawn –h 1903 cl /Id:\myInclude Test.cpp

  wspawn.cpp的程序清单如下:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <string>
#include <windows.h>
using namespace std;
void exit_friendly(void)
{
  puts("请不要单独运行wSpawn.");
  exit(0);
}
int main( int argc, char *argv[] )
{
  HANDLE hWrite = NULL;
  DWORD  dwWrited;
  int   i = 0, ret = 0, len = 0;
  char  psBuffer[256];
  FILE*  child_output;
  string command_line = "";
  // 检查命令行,如存在管道句柄,则将其转换为HANDLE类型
  if (argc < 2)
    exit_friendly();
  if (!stricmp(argv[1], "-h"))
  {
    if (argc < 4)
      exit_friendly();
    hWrite = (HANDLE)atoi(argv[2]);
    i = 3;
  }
  else
    i = 1;
  // 提取要执行的命令
  for (; i < argc; i++)
  {
    command_line += argv[i];
    command_line += " ";
  }
  // 使用_popen创建子进程并重定向其标准输出到文件指针中
  if( (child_output = _popen( command_line.c_str(), "rt" )) == NULL )
    exit( 1 );
  while( !feof( child_output ) )
  {
    if( fgets( psBuffer, 255, child_output ) != NULL )
    {
      if (hWrite)
      {
        // 将子进程的标准输出写入管道,提供给自己的父进程
        // 格式是先写数据块长度(0表示结束),再写数据块内容
        len = strlen(psBuffer);
        WriteFile(hWrite, &len, sizeof(int), &dwWrited, NULL);
        WriteFile(hWrite, psBuffer, len, &dwWrited, NULL);
      }
      else
        // 如命令行未提供管道句柄,则直接打印输出
        printf(psBuffer);
    }
  }
   // 写“0”表示所有数据都已写完
  len = 0;
  if (hWrite)
    WriteFile(hWrite, &len, sizeof(int), &dwWrited, NULL);
   return _pclose( child_output );
}

  下面,我们就利用wSpawn程序,写一个简单的“IDE”工具。我们选择Visual Studio 6.0作为开发环境(本文给出的代码也在Visual Studio.NET 7.0中做过测试)。首先,创建Visual C++工程myIDE,工程类型为MFC AppWizard(EXE)中的Dialog based类型,即创建了一个主窗口为对话框的GUI程序。工程myIDE的主对话框类是CMyIDEDlg。现在我们要在资源编辑器中为主对话框添加一 个足够大的多行编辑框(Edit Box),它的控制ID是IDC_EDIT1,必须为IDC_EDIT1设置以下属性:

 Multiline, Horizontal scroll, Auto HScroll, Vertical scroll, Auto VScroll, Want return

  然后用ClassWizard为IDC_EDIT1添加一个对应的成员变量(注意变量的类型要选CEdit型而非字符串CString型)

CEdit m_edit1;

  使用ClassWizard为“确定”按钮添加消息响应方法OnOK(),编辑该方法:

void CMyIDEDlg::OnOK()
{
  AfxBeginThread(myThread, this);
  InvalidateRect(NULL);
  UpdateWindow();
}

  也就是说,我们在“确定”按钮按下时,启动了后台线程myThread(),那么,myThread()到底做了些什么呢?我们先在CMyIDEDlg类的头文件myIDEDlg.h中加上一个成员函数声明:

protected:
  static UINT myThread(LPVOID pParam);

  然后,在CMyIDEDlg类的实现文件myIDEDlg.cpp里添加myThread()的实现代码:

UINT CMyIDEDlg::myThread(LPVOID pParam)
{
  PROCESS_INFORMATION pi;
  STARTUPINFO siStartInfo;
  SECURITY_ATTRIBUTES saAttr;
  CString Output, tmp;
  char command_line[200];
  DWORD dwRead;
  char* buf; int len;
  HANDLE hRead, hWrite;
  CMyIDEDlg* pDlg = (CMyIDEDlg*)pParam;
  // 创建与wSpawn.exe通讯的可继承的匿名管道
  saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
  saAttr.bInheritHandle = TRUE;
  saAttr.lpSecurityDescriptor = NULL;
  if (!CreatePipe(&hRead, &hWrite, &saAttr, 0))
  {
    AfxMessageBox("创建管道失败");
    return 0;
  }
  // 准备wSpawn的命令行,在命令行给出写管道句柄和要wSpawn执行的命令
  memset(&pi, 0, sizeof(pi));
  sprintf(command_line, "wspawn -h %d cl /?", (unsigned int)hWrite);
  // 子进程以隐藏方式运行
  ZeroMemory( &siStartInfo, sizeof(STARTUPINFO) );
  siStartInfo.cb = sizeof(STARTUPINFO);
  siStartInfo.wShowWindow = SW_HIDE;
  siStartInfo.dwFlags = STARTF_USESHOWWINDOW;
  // 创建wSpawn子进程
  if (!CreateProcess( NULL, command_line, NULL, NULL, TRUE,
            0, NULL, NULL, &siStartInfo, &pi))
  {
    AfxMessageBox("调用wSpawn时失败");
    return 0;
  }
  // 读管道,并显示wSpawn从管道中返回的输出信息
  if(!ReadFile( hRead, &len, sizeof(int), &dwRead, NULL) || dwRead == 0)
    return 0;
  while(len)
  {
    buf = new char[len + 1];
    memset(buf, 0, len + 1);
    if(!ReadFile( hRead, buf, len, &dwRead, NULL) || dwRead == 0)
      return 0;
    // 将返回信息中的"\n"替换为Edit Box可识别的"\r\n"
    tmp = buf;
    tmp.Replace("\n", "\r\n");
    Output += tmp;
    // 将结果显示在Edit Box中,并刷新对话框
    pDlg->m_edit1.SetWindowText(Output);
    pDlg->InvalidateRect(NULL);
    pDlg->UpdateWindow();
    delete[] buf;
    if(!ReadFile( hRead, &len, sizeof(int), &dwRead, NULL) || dwRead == 0)
      return 0;
  }
  // 等待wSpawn结束
  WaitForSingleObject(pi.hProcess, 30000);
  // 关闭管道句柄
  CloseHandle(hRead);
  CloseHandle(hWrite);
  return 0;
}

  很简单,不是吗?后台线程创建一个匿名管道,然后以隐藏方式启动wSpawn.exe并将管道句柄通过命令行传给wSpawn.exe,接下来 只要从管道里读取信息就可以了。现在我们可以试着编译运行myIDE.exe了,记住要把myIDE.exe和wSpawn.exe放在同一目录下。还 有,我在myThread()函数中 写死了传给wSpawn.exe的待执行的命令行是“cl /?”,这模拟了一次典型的编译过程,如果你不打算改变这一行代码的话,那一定要注意在你的计算机上,C++编译器cl.exe必须位于环境变量PATH 指明的路径里,否则wSpawn.exe可就找不到cl.exe了。下面是myIDE程序的运行结果:

图片点击可在新窗口打开查看

  补充一点,上面给出的wSpawn利用_popen()完成子进程创建和输入输出重定向,这一方法虽然简单,但只能重定向子进程的stdout 或stdin,如果还需要重定向子进程的stderr的话(Java编译器javac就利用stderr输出结果信息),那我们就不能这么投机取巧了。根 据以上讨论,你一定可以使用传统的_pipe()、_dup()等系统调用,写出功能更完整的新版wSpawn来,我这里就不再罗嗦了。

分享到:
评论

相关推荐

    C++编写的压缩解压缩程序(调用WinRAR的命令行程序)

    程序的核心机制是调用了WinRAR的命令行工具Rar.exe,这是一个与WinRAR图形用户界面配套的命令行版本,可以执行压缩、解压缩以及管理RAR档案的其他任务。 WinRAR是一款流行的文件压缩和解压缩软件,支持多种文件格式...

    命令行下的绘图程序

    3. 命令行接口:如何在没有图形界面的环境下与用户交互。 4. 输入/输出处理:如何读取用户输入并输出图形。 5. 数学应用:坐标转换和几何形状的计算。 6. 文件操作:读取和理解`说明.txt`中的信息。 7. 模块化编程:...

    带词语联想功能的命令行程序实现

    虽然题目中提到MFC,但通常命令行程序不需要MFC,因为它们不涉及图形用户界面。如果MFC确实被用到,可能是为了实现某些特定的系统交互或者是为了统一项目中的其他Windows应用程序。 在代码文件“test1.c”中,我们...

    qt调用cmd命令和dll例子

    在IT行业中,Qt是一个强大的C++图形用户界面应用程序框架,广泛应用于桌面和移动平台的软件开发。本示例主要探讨如何在Qt环境中调用CMD(命令提示符)命令以及如何加载和使用DLL(动态链接库)文件。这些知识点在...

    在图形界面程序里使用控制台窗口

    在开发图形用户界面(GUI)程序时,有时候我们需要在程序中集成控制台窗口来实现命令行输入输出或者调试功能。这种技术在Windows编程中尤其常见,通过创建和管理控制台窗口,开发者可以同时利用GUI的交互性和控制台...

    DialogShowCmd&#40;对话框调用命令行&#41;

    "DialogShowCmd(对话框调用命令行)" 是一个示例程序,它的主要功能是在一个对话框界面中调用操作系统命令行(CMD)执行特定的命令,并展示命令的输出结果。 **描述分析:** 描述中提到这是一个参考网络资料编写的...

    rar命令行程序(zip)

    综上所述,这个压缩包提供的RAR命令行程序可以用于创建和管理ZIP格式的压缩文件,适用于需要自动化或在无图形界面环境下的文件处理任务。配合提供的帮助文档和其他辅助文件,用户可以学习如何有效地使用这个工具来...

    ISIGHT商业软件如何集成命令大全 调用商业软件命令行

    2. Java接口:对于Java开发者,ISIGHT提供了Java API,允许用户在Java代码中调用命令行。通过`Runtime.getRuntime().exec()`方法,你可以执行任何操作系统可识别的命令。 3. COM接口:对于Windows环境,ISIGHT支持...

    Matlab调用C程序

    通过 Matlab 调用 C 程序,可以将 C 语言的强大功能与 Matlab 的图形化界面和数学计算能力结合起来,实现混合编程。下面将详细介绍 Matlab 调用 C 程序的知识点。 一、 Matlab 中的 C 程序调用 Matlab 提供了一个...

    关于C语言图形界面编程

    C语言图形界面编程是将传统的命令行交互提升到可视化界面的一种技术,主要应用于开发桌面应用程序。在C语言中,我们通常使用Borland C++提供的库函数来实现图形界面,这些函数集中在`conio.h`头文件中。下面将详细...

    编写程序mycp.c,实现从命令行读入文件的复制功能,用原始文件系统调用。

    根据给定的文件信息,我们可以总结出以下关于如何在Linux环境下使用原始文件系统调用来实现文件复制的关键知识点: ### 1. 程序结构与功能概述 该程序`mycp.c`的主要功能是从命令行读取两个参数:源文件路径和目标...

    swift-小工具集辅助你用Swift语言写命令行程序

    然而,Swift 并非仅限于图形用户界面(GUI)应用,它同样适用于编写命令行工具。"swift-小工具集" 提供了一系列实用的工具,帮助开发者更高效地利用 Swift 编写命令行程序。下面我们将详细探讨这个小工具集以及如何...

    基于ncurses开发配置网络图形化界面

    【基于ncurses开发配置网络图形化界面】是一个项目,它利用...无论是对于系统管理员还是对Linux有兴趣的开发者,理解和掌握ncurses开发都是一项重要的技能,因为它能够在无图形界面的环境中提供丰富的用户交互体验。

    linux系统下C语言实现带有图形界面的学生成绩管理系统源代码(包含数据库文件)

    在Linux系统中,使用C语言实现带有图形界面的学生成绩管理系统是一项常见的编程挑战,它涉及到多方面的技术知识点。下面将详细阐述这个项目所涵盖的关键技术及其应用。 1. **C语言编程**:C语言是系统级编程的基础...

    CLI人机界面命令行交互程序

    **CLI(Command Line Interface)人机界面命令行交互程序** CLI,即命令行接口,是计算机操作系统中一种用户与系统进行交互的方式。它允许用户通过输入特定的指令来执行操作,如创建、删除文件,浏览目录结构,运行...

    编写程序mycat.c,实现文件内容的显示,用原始文件系统调用实现

    根据给定的文件信息,我们可以深入探讨几个关键的知识点:Linux环境下的文件系统调用、C语言中的文件操作函数以及如何在程序中正确处理命令行参数。 ### 1. Linux环境下的文件系统调用 在Linux系统中,文件操作...

    C++QT项目——一个简易的用户化界面(Qt命令行模式)

    在这个命令行模式的项目中,尽管我们可能不会使用复杂的图形界面,但理解QApplication类至关重要。它是Qt应用程序的核心,负责事件循环和资源管理。Qt Widgets模块提供了一系列用于构建用户界面的类,如QPushButton...

    Easy c语言图形界面 插件

    在C语言中,图形界面的开发往往比命令行界面更为复杂,因为C语言本身并不内置GUI支持。EasyX的出现解决了这个问题,它提供了一系列的函数,如`DrawLine()`用于画线,`DrawRect()`用于画矩形,`FillCircle()`用于填充...

    CPPC++_OCR离线图片文字识别命令行windows程序以JSON字符串形式输出结果方便别的程序调用提供各种语言A.zip

    这种方式对于熟悉命令的用户来说,操作更快捷、灵活,但是对普通用户来说可能不如图形界面直观易用。 该程序“以JSON字符串形式输出结果”,JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,易于人...

    WIN32 SDK界面:仿Window命令行界面

    在本文中,我们将深入探讨如何使用WIN32 SDK来创建一个仿Windows命令行界面的应用程序。这个项目的主要目标是设计一个具有类似于标准Windows命令提示符或telnet客户端的用户界面,其中包括文本输入和显示功能,支持...

Global site tag (gtag.js) - Google Analytics