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();
}