Q:我见到过一些即可以作为控制台应用程序运行,也可以作为基于Windows的应用程序运行的程序。即:如果你在命令提示符键入程序的名字,它就会作为一个普通的基于 Windows 的应用程序运行;如果你输入一个命令行选项,如:“-batch”,它就会以批处理模式作为一个控制台应用程序运行,同时所有输出被定向到控制台。请问这个功能是怎么做的?
Joe Tadmann
A: 我没法告诉你其它的应用程序是怎么这样做的,因为在 Window 中,总是有多种方法来给操作系统加壳,但是我可以向你展示实现这种功能的一种途径---通过 Visual Studio.NET 来实现。也许你没有注意到,你可以在命令提示符键入 devenv 来启动 Visual Studio .NET,这时,Windows 启动 其图形用户界面。但是如果你键入 devenv 的同时再加上一个命令行开关,例如,-? 表示帮助,-bulid 表示编译并生成你的工程,它将以控制台模式运行,没有用户交互界面。例如:
devenv -build MyProject.sln
上面这行命令生成解决方案文件 MyProject.sln。
遗憾的是,自从GUI(图形用户界面)出现以后,太多的程序员已经忘了命令行的强大功能,这些强大的命令行功能使用户能以批处理模式从脚本中运行你的应用程序。你可以 确信微软的那帮家伙是不会在 Visual Studio 中打开某个工程来生成他们的产品的!如果你写了一个确实能实现某些功能的程序,比如,把.wav文件转换成.mp3,或者通过计算预测股票, 这些功能适合用批处理接口来实现。如果你的程序没有人守着就不能成批压缩收藏的音乐乐曲或分析前一天的股票信息,那你的程序有什么好的呢?
好,现在言归正传,你怎么能够实现一个组合了用户界面/控制台的应用呢?现在几乎所有的 Windows 程序员都知道,Windows 把 EXE(可执行程序)分成两 大类:控制台应用和 GUI应用。这种体系结构可以追溯到 Windows 的早期,当时它首先是从 MS_DOS 中发展而来的。如今,你只要给链接 器一个开关:/subsystem:Windows 或者 /subsystem:console,你就能生成你想要的那种应用程序。
所以,如果你要建立一组合应用,首要问题是:它是一个控制台应用还是一个GUI应用?最初,你可能认为要建立一个控制台应用,以后它还能够作为一个 GUI 程序运行。从来没有谁说过一个控制台应用就不能够创建窗口或处理消息,控制台之所以是控制台,那是因为当控制台不存在时, Windows 会为控制台应用创建一个控制台。但是这里有一个问题,如果你在 Windows 下通过在其资源管理器(Explorer)中双击或快捷方式运行某个控制台应用程序,Windows 将会创建一个控制台,你可以通过调用 FreeConsole 来销毁 这个控制台,但是这个控制台窗口会暂时一闪而过,告诉整个世界你其实并不知道你自己做了什么。
然后,要使它为一个GUI应用。那么你如何把它写到控制台呢?有许多文章解释了怎样重新路由 printf 或 cout 到控制台,但是, 它们都涉及到创建新控制台窗口的问题,不是使用一个当程序是从命令行被启动时已经存在的窗口。即使有某个使用现有控制台的途径,那你又怎么知道你的应用程序是通过 Windows Explorer 还是通过命令提示符调用的呢?
有一个新的函数( Windows XP 使用的)正好可以利用:这个函数是 AttachConsole。 它允许你将程序“绑定”到其它进程的控制台窗口,如果你用了专用的进程 ID:ATTACH_PARENT_CONSOLE,AttachConsole 将绑定到启动你的程序的控制台。太好了,但是有两个问题,第一,AttachConsole 只能在 Windows XP 系统中使用,所以如果你想要你的程序运行在其它版本的 Windows 中,就没那么走运了;第二,AttachConsole 工作并不稳定,你可以写内容到控制台,但是你的程序退出后,命令提示符就乱七八糟了。
简而言之,基于 Windows 的应用程序要么是控制台应用,要么是GUI应用,二者不可兼得。(除非你想写你自己的启动代码,那已非我力所能及)。但是你知道它 是能够做到的,因为我已经告诉你 Visual Studio 能行,到底怎样做呢?
如果你看一下 Visual Studio 的安装目录,你会发现实际上有两个程序:devenv.exe和 devenv.com。还记得.com 是什么吗? 它可不是 Web,而是可执行程序,在很久很久以前,当你还是小孩子的时候,基于 Windows 的程序有三种内存模式:大内存模式(large)、小内存 模式(small)和巨大内存模式(huge)。其它的模式都被叫做紧凑模式或微小模式,它们产生不同类型的可执行文件,这些可执行文件都以 .com 作为扩展名。(.com文件是一种在加载时不需要固定地址的直接内存映 象,这样使用起来非常的快,但它们必须很小。) 现在内存模式已经没有这么多了,大部分可执行文件都使用PE格式。但是命令解释器仍然能识别 .com 可执行文件,并且你可以将任何.exe 程序重新 更名为 .com 程序,如把 foo.exe 改成 foo.com,它仍然可以通过输入名字执行。所以可以用这个技巧去创建两个程序:foo.com 和 foo.exe。一个是 控制台应用程序,另一个是基于 Windows 应用程序。
Figure 2 在对话框里显示进程列表
为了示范它的工作原理,我修改了我在
2002年7月专栏文章里的程序lp(列举进程)(编者注:中文译文参见
在线杂志第14期文章——“
如何获取某个进程的主窗口以及创建进程的程序名?”),它既可作为 GUI 程序运行,也可作为控制台程序运行。如果你输入 ListProc 而不用参数,它会在对话框中列出进程,如 Figure 2 所示。如果你键入 ListProc -c,它会以控制台模式运行并列出进程,如 Figure 3 所示。ListProc 有两个主程序文件:ListProc.cpp 是通常的 MFC 应用实现,ListProc-cons.cpp 是控制台应用实现。这两个程序都调用相同的模块—— EnumProc,实际的进程列表正是由它产生的。ListProc-cons 处理命令行并显示控制台信息,没有用命令行参数的程序通过调用 ShellExecute 启动程序的 GUI 版本。
// 将 myself.com 改为 myself.exe 并运行
TCHAR lpExeName[_MAX_FNAME];
GetModuleFileName(NULL, lpExeName, _MAX_FNAME);
LPTSTR ext = lpExeName + _tcslen(lpExeName) - 3;
_tcscpy(ext,_T("exe"));
ShellExecute(NULL, _T("open"),
lpExeName, NULL, NULL, SW_SHOWNORMAL);
Figure 3 在控制台的进程列表
Figure 4 是 ListProc-cons 的全部代码,Visual Studio 的解决方案包含两个工程:ListProc和 ListProc-cons。后者有一个定制编译步骤,是重命名输出文件 ListProc-cons.exe 为ListProc.com (参见 Figure 5)。当你安装程序时,要保证把 .com 和 .exe 都放在了相同的目录下,并且确保你创建的任何快捷方式都指向 .exe 文件。那样,从 Windows 调用会直接运行.exe 文件,而从控制台调用则运行 .com 文件(如果 .com 和 .exe 都存在于用户的路径下, Windows 首选 .com 文件运行)。明白了吗?
Figure 5 将 .exe 文件重命名为 .com
c++控制台应用程序
悬赏分:50 -
解决时间:2009-9-19 12:38
设计一个控制台应用程序XXXPersion,其中包含一个描述人的抽象类PersionClass,利用继承的方式派生学生类StudentClass,教师类TeacherClass和歌手类SingerClass等,在PersionClass类中要有必要的数据如:姓名、性别、年龄等,在派生类StudentClass中要有所在学校等与学生有关的数据的描述;在TeacherClass类中要有职称等与教师有关的相关数据的描述,在SingerClass中要有住址、身高等与歌手有关的数据的描述。总的要求:要有必要的获得数据的手段,如获得学生、教师或歌手的姓名、性别、年龄,学生所在的学校、教师的职称、歌手的住址等,尽量将题目设计完整,功能完善,同时设计测试类对所设计的类及包含的功能进行测试。
评分标准:
1. 正确合理设计PersionClass类 10分
2. 正确使用继承设计StudentClass类 10分
3. 正确使用继承设计TeacherClass类 10分
4. 正确使用继承设计SingerClass类 10分
5. 能够正确设计测试类并进行合理测试 10分
6. 程序整体效果及运行 10分
问题补充:
学校的一次模拟,大家帮帮忙,对了,后面的题目的分数!!
#include <stdio.h>
#include <iostream>
using namespace std;
class XXXPerson{
public:
char name[20]; //姓名
char sex[2]; //性别
int age; //年龄
public:
XXXPerson(){
memset(name, 0, 20);
memset(sex, 0,2);
age = 0;
}
XXXPerson(char n[], char s[], int i){
strcpy(name, n);
strcpy(sex, s);
age = i;
}
virtual void displayInfo(){
cout << "Person--> name: "<<name << "/t sex: " << sex << "/t age: " << age <<"." << endl;
}
};
class StudentClass:public XXXPerson{
public:
char school[50]; //所在学校
char otherinfo[200]; //其它信息
public:
StudentClass(char n[], char s[], int age, char sch[], char oi[]){
strcpy(name, n);
strcpy(sex, s);
this->age = age;
strcpy(school, sch);
strcpy(otherinfo, oi);
}
virtual void displayInfo(){
cout << "学生 "<<name << " 的信息:" << endl;
cout<< "/t性别:/t/t" << sex << endl;
cout << "/t年龄:/t/t" << age << endl;
cout << "/t所在学校:/t" << school << endl;
cout << "/t附加信息:/t" << otherinfo << endl;
}
};
class TeacherClass:public XXXPerson{
public:
char tlevel[50]; //职称
char otherinfo[200]; //其它信息
public:
TeacherClass(char n[], char s[], int age, char sch[], char oi[]){
strcpy(name, n);
strcpy(sex, s);
this->age = age;
strcpy(tlevel, sch);
strcpy(otherinfo, oi);
}
virtual void displayInfo(){
cout << "老师 "<<name << " 的信息:" << endl;
cout<< "/t性别:/t/t" << sex << endl;
cout << "/t年龄:/t/t" << age << endl;
cout << "/t职称:/t/t" << tlevel << endl;
cout << "/t附加信息:/t" << otherinfo << endl;
}
};
class SingerClass:public XXXPerson{
public:
int stature; //身高
char address[200]; //住址
char otherinfo[200]; //其它信息
public:
SingerClass(char n[], char s[], int age,int t, char sch[], char oi[]){
stature = t;
strcpy(name, n);
strcpy(sex, s);
this->age = age;
strcpy(address, sch);
strcpy(otherinfo, oi);
}
virtual void displayInfo(){
cout << "歌手 "<<name << " 的信息:" << endl;
cout<< "/t性别:/t/t" << sex << endl;
cout << "/t年龄:/t/t" << age << endl;
cout << "/t身高:/t/t" << stature << endl;
cout << "/t住址:/t/t" << address << endl;
cout << "/t附加信息:/t" << otherinfo << endl;
}
};
int main(void){
StudentClass p1("小明", "男", 19, "XX大学", "学生测试。");
SingerClass p2("刘德华", "男", 40, 180,"保密不公开。", "歌手测试。");
TeacherClass p3("李老师", "女", 39, "高级教师", "老师测试");
XXXPerson *p[3] = {&p1, &p2, &p3};
for(int i=0; i<3; i++)
p[i]->displayInfo();
}