2011年1月10日星期一

VC++ 2005 Express + SDK

You can use Visual C++ Express to build powerful .NET Framework applications immediately after installation. In order to use Visual C++ Express to build Win32 applications, you'll need to take just a few more steps. I'll list the steps necessary for building Win32 applications using Visual C++ Express.

Step 1: Install Visual C++ Express.


If you haven't done so already, install Visual C++ Express.


Step 2: Install the Microsoft Platform SDK.

Install the Platform SDK over the Web from the Download Center. Follow the instructions and install the SDK for the x86 platform.


Step 3: Update the Visual C++ directories in the Projects and Solutions section in the Options dialog box.

From the Tools menu in Visual Studio, select Options. The Options dialog box appears.

From the Options dialog box, expand the Projects and Solutions node and select VC++ Directories. In that section, add the following paths to the appropriate subsection:

    * Executable files: C:\Program Files\Microsoft Platform SDK for Windows Server 2003 R2\Bin
    * Include files: C:\Program Files\Microsoft Platform SDK for Windows Server 2003 R2\Include
    * Library files: C:\Program Files\Microsoft Platform SDK for Windows Server 2003 R2\Lib

Note: Alternatively, you can update the Visual C++ Directories by modifying the VCProjectEngine.dll.express.config file located in the \vc\vcpackages subdirectory of the Visual C++ Express install location. Please make sure that you also delete the file "vccomponents.dat" located in the "%USERPROFILE%\Local Settings\Application Data\Microsoft\VCExpress\8.0" if it exists before restarting Visual C++ Express Edition.


Step 4: Update the corewin_express.vsprops file.

One more step is needed to make the Win32 template work in Visual C++ Express. You need to edit the corewin_express.vsprops file (found in C:\Program Files\Microsoft Visual Studio 8\VC\VCProjectDefaults) and

Change the string that reads:

AdditionalDependencies="kernel32.lib"

to

AdditionalDependencies="kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib"


Step 5: Generate and build a Win32 application to test your paths.

In Visual C++ Express, the Win32 Windows Application type is disabled in the Win32 Application Wizard. To enable that type, you need to edit the file AppSettings.htm file located in the folder %ProgramFiles%\Microsoft Visual Studio 8\VC\VCWizards\AppWiz\Generic\Application\html\1033\".

In a text editor comment out lines 441 - 444 by putting a // in front of them as shown here:

// WIN_APP.disabled = true;
// WIN_APP_LABEL.disabled = true;
// DLL_APP.disabled = true;
// DLL_APP_LABEL.disabled = true;

Save and close the file and open Visual C++ Express.

From the File menu, click New Project. In the New Project dialog box, expand the Visual C++ node in the Product Types tree and then click Win32. Click on the Win32 Console Application template and then give your project a name and click OK. In the Win32 Application Wizard dialog box, make sure that Windows application is selected as the Application type and the ATL is not selected. Click the Finish button to generate the project.

As a final step, test your project by clicking the Start button in the IDE or by pressing F5. Your Win32 application should build and run.

参考译文:

安装之后,您可以立即使用Visual C++ 2005速成版来生成功能强大的.NET Framework 应用程序。若要使用 Visual C++ 速成版生成Win32应用程序,只需采取几个步骤,下面对此进行了详细介绍。 
   
1. 安装 Platform SDK 以便与 Visual C++ 速成版结合使用从 Platform SDK 更新站点 的 Platform SDK Update 站点通过 Web 安装 Microsoft Platform SDK。在该页上,单击“Download”(下载),然后确保从列表中依次选择“Windows SDK”和“Core SDK”。 

2. 从 Visual Studio 中的“工具”菜单上,选择“选项”。出现“选项”对话框。 
从“选项”对话框中,展开“项目和解决方案”节点并选择“VC++ 目录”。在该部分,将以下路径添加到相应的子节: 
可执行文件:C:\Program Files\Microsoft SDK\Bin 
包含文件:C:\Program Files\Microsoft SDK\include 
库文件:C:\Program Files\Microsoft SDK\lib 
    注意 在您的系统上,Platform SDK 的位置可能有所不同。 

3. 更新 corewin_express.vsprops 文件 
(位于C:\Program Files\Microsoft Visual Studio 8\VC\VCProjectDefaults 中) 

并将以下字符串: 

AdditionalDependencies="kernel32.lib" 

更改为: 

              AdditionalDependencies="kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib" 

注意 在您的系统上,Visual C++ 速成版 可能安装到了不同的位置。 

4. 在Visual C++ 速成版,Win32应用程序向导上的Windows 应用程序选项是不可选的,要使用这个选项,你需要修改AppSettings.htm这个文件,它位于"%ProgramFiles%\ Microsoft Visual Studio 8\VC\VCWizards\AppWiz\Generic\Application\html\1033\". 

在文本编辑器用"// "符号把文件里的441 - 444这几行注释掉,像下面这样: 

// WIN_APP.disabled = true; 

// WIN_APP_LABEL.disabled = true; 

// DLL_APP.disabled = true; 

// DLL_APP_LABEL.disabled = true; 

保存和关闭文件,然后打开Visual C++ 速成版.

2011年1月8日星期六

msvcprt.lib(MSVCP90.dll) : error LNK2005:已经在libcpmtd.lib(xmutex.obj) 中定义

来源:未知

这篇文章要来谈谈程式库 (Library) 连结,以及关于 MSVC 与 CRT 之间的种种恩怨情仇。

如果你使用的作业系统是 Linux、Mac 或其他非 Windows 平台,你可以忽略这篇文章;如果你使用的作业系统是 Windows 平台,但没有用 Microsoft Visual Studio C++(以下简称为 MSVC)软体撰写 C++ 程式的话,这篇文章对你的帮助可能很有限;但如果你的作业系统是 Windows,而且你使用的程式整合开发环境是 MSVC 软体撰写 C++ 程式的话,这篇文章应该能够帮助你釐清一些重要的基础观念。

身为程式设计者,在学习程式设计的过程中,你是否曾经遇过某些看起来不知所云的错误讯息,却不知该如何解决?例如当你快快乐乐地写完程式,并且确认所有的程式码都能成功通过编译之后,接着执行「建置方案」(Build Solution) 的步骤,结果却跑出一堆莫名其妙的错误:

LIBCMTD.lib(mlock.obj) : error LNK2005: __lock 已在 MSVCRTD.lib(MSVCR80D.dll) 中定义过了

LIBCMTD.lib(mlock.obj) : error LNK2005: __unlock 已在 MSVCRTD.lib(MSVCR80D.dll) 中定义过了

LIBCMTD.lib(crt0.obj) : error LNK2005: _mainCRTStartup 已在 MSVCRTD.lib(crtexe.obj) 中定义过了

…………

LINK : warning LNK4098: 预设的程式库 ‘MSVCRTD’ 与其他使用的程式库冲突,请使用 /NODEFAULTLIB:library

LINK : warning LNK4098: 预设的程式库 ‘LIBCMTD’ 与其他使用的程式库冲突,请使用 /NODEFAULTLIB:library

D:\Workspace\CrtLibTest\Debug\CrtLibTest.exe : fatal error LNK1169: 找到有一或多个已定义的符号

以一般的情况来说,如果在你的程式专案中有使用某些由他人所撰写的第三方程式库或是开源专案的程式库,比较容易会发生上述的错误状况。从上述这些看似离奇而令人摸不着头绪的错误讯息中,我们大概可以猜测问题点应该在于 LIBCMTD.lib 与 MSVCRTD.lib 这两个程式库身上。但到底什么是 LIBCMTD.lib 和 MSVCRTD.lib?在我们的程式码中有使用这些程式库吗?

 

答案是肯定的。

熟悉 C 语言的程式设计者都知道,如果要使用 printf()、scanf() 或者 fopen() 等等 C 语言的基本 I/O 操作函式时,首先必须用 #include 语法将 stdio.h 这个标头档纳入我们的程式码中。藉由 stdio.h 中对这些 I/O 操作函式所做出的函式宣告 (function declaration),编译器 (Compiler) 才得以确认 printf、scanf 以及 fopen 等等都是合法可用的函式。

而当我们撰写的程式码经过编译器产出 OBJ 形式的档案之后,需要再经由连结器 (Linker) 的处理程序,将程式码中全部有使用到的函式定义 (function definition) 连结建置起来,才能够产生出最后的程式执行档。问题来了,我们知道 printf、scanf 以及 fopen 的函式宣告存在于 stdio.h 当中,但是这些傢伙的函式定义,也就是真正的实做程式码,究竟存放在什么地方呢?

在 C 语言的标准程式库中。

由 C 语言所制订的标准程式库,称之为「执行阶段程式库」,也就是 C Run-Time Library,通常可简称为 CRT。在 C 语言的标准程式库中,包含了一组常用的基础函式,例如 I/O 处理与字串操作程序等等,所以只要我们使用 C 语言撰写程式码,就一定要将编译完成后的程式码 OBJ 档,连结至 C 语言的执行阶段程式库,才能够产生出合法的 C 语言程式执行档。

而 CRT 并非只有单一一种版本存在。事实上,除了可以依「除错」与「释出」用途分成两个版本之外,两者又可分别衍生分出「静态连结」与「动态连结」两种形式:

静态连结:

LIBCMTD.lib(除错版本)

LIBCMT.lib

动态连结:

MSVCRTD.lib(除错版本)

MSVCRT.lib

虽然这四个 CRT 版本的用途与使用方式各不相同,但却有个共通的特点,就是它们都是满足执行绪安全需求,可在多执行绪程式码中安全使用的程式库版本。事实上,在过去 MSVC 6 的版本中,本来还有另外两个 LIBCD.lib(除错版本)与 LIBC.lib 程式库,是专门给单执行绪程式使用的 CRT 版本,但是这两个选项自 MSVC 2005 开始就从设定选项中被删除掉了,所以现在大多数程式设计者使用的都是多执行绪的 CRT 版本。

在程式库连结 (library linking) 的行为中,静态连结和动态连结的分别,在于使用静态连结时,会直接将程式库的函式定义嵌入执行档之中,而使用动态连结时,程式库的函式定义则存在于另外的独立档案,通常是 DLL 格式的档案中,然后与程式执行档一同发佈给使用者。因此在档案的尺寸上,使用动态连结的执行档档案,通常会比使用静态连结的执行档档案来得更小一些。

使用动态连结 CRT 版本的好处,是能够将经常使用到的标准程式库们独立出来,放在 Windows 的系统资料夹中,以减少我们建置出来的执行档档案尺寸。但反过来说,使用动态连结 CRT 版本的缺点也在于这些与执行档相依为命的 DLL 档案上。举例来说,如果程式以 MSVC 2005 建置出 Debug 组态的执行档,则此执行档需要有 msvcr80d.dll 存在才能顺利执行;如果是 Release 组态,则相依于 msvcr80.dll。但是如果你把相同的程式码拿到 MSVC 2008 上建置,产生出来的执行档则相依于 msvcr90d.dll 与 msvcr90.dll 两个不同的 DLL 档案。不同版本的 MSVC,都会有各自不同的相依 DLL 档案。

在 MSVC 的程式专案中,如何指定程式码要使用静态连结或者动态连结的 CRT 版本?其实很容易,只要在专案属性的「C/C++」页面中,选择「程式码产生」(Code Generation) 子页面,其中有个「执行阶段程式库」(Runtime Library) 的项目,也就是专案中用来设定 CRT 连结版本的地方。其中总共有四个选项,正好对应于上述静态连结与动态连结的四个不同程式库版本。

多执行绪侦错 (/MTd):对应 LIBCMTD.lib

多执行绪 (/MT):对应 LIBCMT.lib

多执行绪侦错 DLL (/MDd):对应 MSVCRTD.lib

多执行绪 DLL (/MD):对应 MSVCRT.lib

如果你没有做任何设定就开始建置程式的话,MSVC 的预设选项则会使用动态连结的版本。


C Runtime Library

请注意,以上只是单纯 C 语言的程式库而没有包含 C++ 语言在内。如果你的程式系统中,有包含 C++ 语言的程式码的话,那又是另外一回事了。但是在专案属性的页面中,为什么找不到相关的设定选项呢?因为 MSVC 悄悄地帮程式设计者代劳处理掉了。只要在程式码中使用 #include 语法纳入任何一个 C++ 的标头档,例如 iostream 或 fstream,MSVC 就会在连结器的运作阶段中,自动帮我们连结 C++ 的执行阶段程式库。而 C++ 的执行阶段程式库,同样可分为四个版本:

静态连结:

LIBCPMTD.lib(除错版本)

LIBCPMT.lib

动态连结:

MSVCPRTD.lib(除错版本):执行档相依于 MSVCP90D.dll

MSVCPRT.lib:执行档相依于 MSVCP90.dll

至于程式执行档使用的是静态连结或者动态连结的版本,就仰赖于 C 语言的版本设定选项了。举个例子来说,如果你撰写了一个 Debug 组态的 C++ 程式,并且保留专案原先预设的建置选项(动态连结),那么最终建置出来的程式执行档将会相依于 MSVCR90D.dll 以及 MSVCP90D.dll 两个 DLL 档案。如果将相同的程式以 Release 组态建置完成,则会相依于 MSVCR90.dll 以及 MSVCP90.dll 二者。


Standard C++ Library

刚学习程式设计的入门者,经常会在满心欢喜地完成一件程式作品并且传给其他人使用时,却发现不能在别人的电脑上启动程式,其实就是陷入了使用者电脑缺少 DLL 档案而无法执行程式的窘境。有三种方法可以解决这个令人困扰的问题:

使用者的电脑,必须先安装「Visual C++ 可转发套件」(MSVC 2008 或 MSVC 2005 )。

将所需的 DLL 档案,例如 MSVCR90D.dll 与 MSVCP90D.dll,直接附在程式的下载包当中。

以静态连结方式建置程式执行档。

当你无法确定自己的程式或别人的程式,是否相依于某些特定的 DLL 档案时,有一个非常好用的免费工具程式 Dependency Walker,可以开启 EXE 格式的执行档或者 DLL 格式的动态程式库,然后详细地条列出它们所相依的 DLL 档案。

瞭解了几种不同的 CRT 版本选项之后,回到最前面的错误讯息问题,相信各位现在应该能够很清楚地理解,原来会发生这些奇怪的错误状况,是因为程式同时连结了 LIBCMTD.lib 与 MSVCRTD.lib 所以造成函式定义版本冲突。也就是说,程式连结器已经在其中一个 CRT 的版本中找到所需的函式定义,但此时却又跳出另外一位 CRT,也给了一份相同函式的实作版本,所以连结器无法判断应该忽略谁并且选择谁。

而这个状况的发生原因,就是你的程式与程式所连结的外部程式库,使用了不同的 CRT 版本之故。例如,当你的程式使用了 Lua,自然必须连结至 Lua 的程式库 lua5.1.lib,但如果 lua5.1.lib 是以静态连结版本的 CRT 建置而成,而你的程式却是以预设选项,动态连结 CRT 来建置程式执行档的话,如此一来就会产生上述这些错误讯息了。至此,问题的答案已昭然若揭,解决方法有二种:其一是将 Lua 重新以动态连结 CRT 的方式建置出一个新的程式库,其二则是将自己的程式专案改成以静态连结 CRT 方式建置。

换个角度想,当你身为一位程式库的设计开发者,想要将自己写的东西分享给其他人,但又不想要完全开放自己撰写的程式源码时,至少可以同时提供以下四种版本的程式库,以妥善满足使用者的各种不同需求:

Debug:动态连结除错版本

Release:动态连结版本

Debug_Static:静态连结除错版本

Release_Static:静态连结版本

然而,有时候世界并不会运作得如此理想。在某些特殊的状况下,当我们使用他人所写的第三方程式库时,有时可能只拿得到其中某个特定的版本,例如 Release_Static 版本时,就很有可能会遇到程式库冲突的错误情形。此时就需要视专案的实际需求而定,可以在专案属性中指定「忽略特定程式库」(Ignore Specific Library) 这个选项,让程式码连结器忽略某些程式库,以此化解动静程式库或新旧程式库之间的恩怨冲突。

小测验:你所撰写的程式,必须连结某个以静态多执行绪 (/MT) CRT 建置而成的程式库。如果你的程式在 Debug 组态下以多执行绪侦错 (/MTd) 选项建置,是否会产生冲突?如果你的程式在 Release 组态下以多执行绪 (/MT) 选项建置,是否会产生冲突?是的话,应该如何解决?

 

 

上面的方法还是不行!会出现其他问题的。

以下是我摸索出的最新的解决方法:

首先,所有的lib文件,使用/MTd或/MT编译。Debug调试模式使用/MTd,Release模式使用/MT。

然后,在自己的程序中也使用/MTd或/MT编译。这样就不会出问题了。

三种编译链接库的方式:

(1)连接Windows库。针对Win32 API编写的应用程序,上面的方法可能带来新问题,可以忽略libcmt.lib库,即可。如果还有其他问题,再忽略相应的库。

(2)MFC静态链接。上面的方法就是针对这种链接方式的,所以没问题。

(3)MFC动态链接。没有试过,应该和(1)类似。

 

最后补充:如果还不行,直接加入/force:multiple编译参数吧。这次之所以没有使用它,也是为了严谨起见。



2011年1月7日星期五

输出从1到1000的数


本文来自: http://stackoverflow.com/q/4568645/89806
有这样一个面试题——请把从1到1000的数打印出来,但你不能使用任何的循环语句或是条件语句。更不能写1000个printf或是cout用C/C++语言
我相信,大多数人一开始你可能想到的是递归算法:
void f(int n){
printf("%d\n",n);
(1000-n) ? f(n+1) : exit(0) ;
}
int main(){
f(1);
}
当然,题目中说了不能使用条件语句,所以,上面那种解法的不符合题意的,因为还是变向地使用了条件表达式。不过,我们可以用别的方法来让这个递归终止,比如:
除以零,当程序crash,呵呵。
void f(int n){
printf("%d\n",n);
n/(1000-n);
f(n+1);
}
还有这样退出递归的:

void yesprint(int i);
void noprint(int i);

typedef void(*fnPtr)(int);
fnPtr dispatch[] = { yesprint, noprint };

void yesprint(int i) {
printf("%d\n", i);
dispatch[i / 1000](i + 1);
}

void noprint(int i) { /* do nothing. */ }

int main() {
yesprint(1);
}
还有下面这些各种各样的解法:
#include

/* prints number i */
void print1(int i) {
printf("%d\n",i);
}

/* prints 10 numbers starting from i */
void print10(int i) {
print1(i);
print1(i+1);
print1(i+2);
print1(i+3);
print1(i+4);
print1(i+5);
print1(i+6);
print1(i+7);
print1(i+8);
print1(i+9);
}

/* prints 100 numbers starting from i */
void print100(int i) {
print10(i);
print10(i+10);
print10(i+20);
print10(i+30);
print10(i+40);
print10(i+50);
print10(i+60);
print10(i+70);
print10(i+80);
print10(i+90);
}

/* prints 1000 numbers starting from i */
void print1000(int i) {
print100(i);
print100(i+100);
print100(i+200);
print100(i+300);
print100(i+400);
print100(i+500);
print100(i+600);
print100(i+700);
print100(i+800);
print100(i+900);
}

int main() {
print1000(1);
return 0;
}
不过,print用得多了一些。我们可以用宏嘛。
#include
#define Out(i) printf("%d\n", i++);
#define REP(N) N N N N N N N N N N
#define Out1000(i) REP(REP(REP(Out(i))));
void main()
{
int i = 1;
Out1000(i);
}
不过,我们应该使用C++的一些特性,比如:
使用构造函数
class Printer
{
public:
Printer() { static unsigned i=1; cout << i++ << endl;; }

};

int main()
{
Printer p[1000];
}
或是更为NB的Template:
template
struct NumberGeneration{
static void out(std::ostream& os)
{
NumberGeneration::out(os);
os << N << std::endl;
}
};

template<>
struct NumberGeneration<1>{
static void out(std::ostream& os)
{
os << 1 << std::endl;
}
};

int main(){
NumberGeneration<1000>::out(std::cout);
}
最后来个BT一点的:
void main(int j) {
printf("%d\n", j);
(main + (exit - main)*(j/1000))(j+1);
}

2010年12月13日星期一

标准输出重定向

标准输入输出指的是控制台的输入输出,涉及的函数和对象包括C运行库中的printf,scanf,fprintf……,C++标准库中的cin,cout,cerr,……
这里只讨论标准输出的重定向。重定向包括定向到文件、屏幕(Console窗口)、GUI中。
(1)重定向到文件中
利用freopen函数实现。当将C运行库的stdout重定向到文件中后,C++标准库中的cout未做任何改变即已重定向了。
//将stdout重定向到文件中
FILE *stream;
if( (stream = freopen("file.txt", "w", stdout ) ) == NULL )
    exit(-1);
printf("this is stdout output");

//重新将stdout 重定向到console中
stream = freopen("CON","w",stdout);
pintf("And now back to the console once again");

(2)重定向到屏幕
参考资料:http://dslweb.nwnexus.com/~ast/dload/guicon.htm
对于console程序而言,谈论重定向到屏幕可能没有意义。所以这里谈论的是,对于GUI程序,也就是普通的Windows程序中,如何使用printf输出到console。
主要思路是,AllocConsole分配一个控制台窗口,GetStdHandle获得相应句柄再调用_open_osfhandle关联到C运行库的句柄,_fdopen打开句柄获得文件流指针,与stdout关联。
/*****************************************************
*guicon.cpp
*
*****************************************************/

#include <windows.h>
#include <io.h>
#include <stdio.h>
#include <fcntl.h>

#include "guicon.h"

static const WORD MAX_CONSOLE_LINES = 500;

void RedirectIOToConsole()
{
int hConHandle;
//HANDLE lStdHandle;
FILE *fp;
CONSOLE_SCREEN_BUFFER_INFO coninfo;

// allocate a console for this app
AllocConsole();

// set the screen buffer to be big enough to let us scroll text
GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE),
&coninfo);
coninfo.dwSize.Y = MAX_CONSOLE_LINES;
SetConsoleScreenBufferSize(GetStdHandle(STD_OUTPUT_HANDLE),
coninfo.dwSize);

// redirect unbuffered STDOUT to the console
hConHandle = _open_osfhandle((intptr_t)GetStdHandle(STD_OUTPUT_HANDLE), _O_TEXT);
fp = _fdopen( hConHandle, "w" );
*stdout = *fp;
setvbuf( stdout, NULL, _IONBF, 0 );

// redirect unbuffered STDIN to the console
hConHandle = _open_osfhandle((intptr_t)GetStdHandle(STD_INPUT_HANDLE), _O_TEXT);
fp = _fdopen( hConHandle, "r" );
*stdin = *fp;
setvbuf( stdin, NULL, _IONBF, 0 );

// redirect unbuffered STDERR to the console
hConHandle = _open_osfhandle((intptr_t)GetStdHandle(STD_ERROR_HANDLE), _O_TEXT);
fp = _fdopen( hConHandle, "w" );
*stderr = *fp;
setvbuf( stderr, NULL, _IONBF, 0 );

}

(3)重定向到GUI中
参考资料:http://blog.vckbase.com/michael/archive/2005/06/06/6163.aspx

将stdout重定向某个MFC程序View中显示出来,实现稍有点复杂。总体思路是
1)创建一个匿名管道(pipe)
2)将stdout重定向到这个pipe上
3)创建一个线程,读取pipe中的内容,然后将这些内容输出到你希望输出的地方,譬如一个view里。
启动进程时,先调用StdoutRedirect(),那么这个进程中所有的printf都可以输出到View里。

#include <windows.h>
#include <io.h>
#include <stdio.h>
#include <fcntl.h>

HANDLE hOutputReadTmp = 0;
HANDLE hOutputWrite = 0;
void DisplayError(char* szMsg)
{
 TRACE(szMsg);
 TRACE(" ");
}

DWORD WINAPI ReadStdout(LPVOID lpvThreadParam)
{
 CHAR lpBuffer[256];
 DWORD nBytesRead;

 while(TRUE)
 {
 if (!ReadFile(hOutputReadTmp, lpBuffer,sizeof(lpBuffer),
 &nBytesRead,NULL) || !nBytesRead)
 {
 if (GetLastError() == ERROR_BROKEN_PIPE)
 break; // pipe done - normal exit path.
 else
 DisplayError("ReadFile"); // Something bad happened.
 }

 if (nBytesRead >0 )
 {
 //获取到printf的输出
 lpBuffer[nBytesRead] = 0;

 //replace your code here. Do it yourself.
 DisplayInView(lpBuffer);
 }
 }

 return 0;
}

void StdoutRedirect()
{
 if (!CreatePipe(&hOutputReadTmp,&hOutputWrite,0,0))
 DisplayError("CreatePipe");

 int hCrt;
 FILE *hf;
 //AllocConsole();
 hCrt = _open_osfhandle(
 (long)hOutputWrite,
 _O_TEXT
 );
 hf = _fdopen( hCrt, "w" );
 *stdout = *hf;
 int i = setvbuf( stdout, NULL, _IONBF, 0 );

 // Launch the thread that gets the input and sends it to the child.
 DWORD ThreadID;
 HANDLE hThread = ::CreateThread(NULL,0,ReadStdout,
 0,0,&ThreadID);
}


后记:
想实现类似此帖中的接口,它把重定向到GUI的操作用一个回调函数来实现,可以很好地扩展,封装了匿名管道的操作,而不是像(3)中那样,用户需要在新创建的线程中修改。但尝试失败,可能是线程的操作不熟悉。

2010年8月2日星期一

一个小函数,ShowErrMsg

void ShowErrMsg()
{
TCHAR szBuf[80];
LPVOID lpMsgBuf;
DWORD dw = GetLastError();

FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM,
NULL,
dw,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR) &lpMsgBuf,
0, NULL );

MessageBox(NULL, lpMsgBuf, "系统错误", MB_OK|MB_ICONSTOP);

LocalFree(lpMsgBuf);
}

[转]关于Windows平台下内存的分配和释放

Windows平台下主要的内存管理途径:
new / delete
malloc / free
CoTaskMemAlloc / CoTaskMemFree
IMalloc::alloc / IMalloc/free
GlobalAlloc / GlobalFree
LocalAlloc / LocalFree
HeapAlloc / HeapFree
VirtualAlloc / VirtualFree

VirtualAlloc以页面为单位(4K)进行分配,是操纵虚拟内存的底层函数。

HeapAlloc在指定堆上分配内存。
Windows为每个进程设置了一个缺省堆,应用程序也可以用HeapCreate创建更多的堆。

GlobalAlloc和LocalAlloc原本为16位环境下的API,Win32为了保持兼容性而保留了这两个函数,但在Win32下已不存在全局堆(所有的堆都是进程私有),所以 GlobalAlloc 和 LocalAlloc 在Win32下意义完全相同,它们都在进程缺省堆中进行内存分配。需注意的是,在Win32下,GlobalAlloc的字面意思已失效,它并不能在进程间共享数据。微软强调,GlobalAlloc/LocalAlloc 比 HeapAlloc 要慢,已不再推荐使用,但由于GlobalAlloc/LocalAlloc 具有简单的使用接口,所以即使在微软所提供的源码中,它们仍被大量使用。

malloc是CRT函数,实现方式取决于具体的CRT版本。VC++的malloc系对 HeapAlloc 作简单的包装,而Borland C++则选择自己实现malloc。在应用程序启动时,CRT创建自己的私有堆,驻留在Win32堆的顶部。

C++中的new先调用 operator new,再调用构造函数生成类对象。而 operator new 在缺省情况下调用 malloc 进行内存分配。应用程序可以重载 operator new,选择其他的内存分配方式。

CoTaskMemAlloc用于COM对象,它在进程的缺省堆中分配内存。
IMalloc接口是对 CoTaskMemAlloc/CoTaskMemFree 的再次封装。

调用关系:
msvcrt.malloc => kernel32.HeapAlloc(ntdll.RtlAllocateHeap)
kernel32.LocalAlloc => ntdll.RtlAllocateHeap
kernel32.GlobleAlloc => ntdll.RtlAllocateHeap
kernel32.HeapAlloc == ntdll.RtlAllocateHeap (映射)
kernel32.VirtualAlloc => kernel32.VirtualAllocEx
kernel32.VirtualAllocEx => ntdll.NtAllocateVirtualMemory
ntdll.RtlAllocateHeap => ntdll.NtAllocateVirtualMemory
ntdll.NtAllocateVirtualMemory => ntdll.KiFastSystemCall
ntdll.KiFastSystemCall => sysenter指令 (0F34)

即:
new -> malloc -> HeapAlloc -> VirtualAlloc -> 驱动程序的_PageAlloc

2010年4月21日星期三

简易垃圾回收器

一、垃圾回收器概述

典型的垃圾回收器算法有三种:一是引用计数(Reference Counting),二是标记并清除(Mark-Sweep),三是复制(Copying)。对于这三种算法,可以用餐巾纸的例子来通俗解释。

引用计数( Reference Counting )算法

拿餐巾纸的例子来说,这种算法的原理大致可以描述为:

午餐时,为了把脑子里突然跳出来的设计灵感记下来,我从餐巾纸袋中抽出一张餐巾纸,打算在上面画出系统架构的蓝图。按照“餐巾纸使用规约之引用计数版”的要求,画图之前,我必须先在餐巾纸的一角写上计数值 1,以表示我在使用这张餐巾纸。这时,如果你也想看看我画的蓝图,那你就要把餐巾纸上的计数值加1,将它改为 2,这表明目前有2个人在同时使用这张餐巾纸(当然,我是不会允许你用这张餐巾纸来擦鼻涕的)。你看完后,必须把计数值减1,表明你对该餐巾纸的使用已经结束。同样,当我将餐巾纸上的内容全部誊写到笔记本上之后,我也会自觉地把餐巾纸上的计数值减1。此时,不出意外的话,这张餐巾纸上的计数值应当是0,它会被垃圾收集器——假设那是一个专门负责打扫卫生的机器人——捡起来扔到垃圾箱里,因为垃圾收集器的惟一使命就是找到所有计数值为 0 的餐巾纸并清理它们。

引用计数算法的优点和缺陷同样明显。这一算法在执行垃圾收集任务时速度较快,但算法对程序中每一次内存分配和指针操作提出了额外的要求(增加或减少内存块的引用计数)。更重要的是,引用计数算法无法正确释放循环引用的内存块,这说明,单是使用引用计数算法还不足以解决垃圾收集中的所有问题。当然,作为一种最简单、最直观的解决方案,引用计数算法本身具有其不可替代的优越性。

标记-清除( Mark-Sweep )算法

第一种实用和完善的垃圾收集算法是 J. McCarthy 等人在 1960 年提出并成功地应用于 Lisp 语言的标记-清除算法。仍以餐巾纸为例,标记-清除算法的执行过程是这样的:

午餐过程中,餐厅里的所有人都根据自己的需要取用餐巾纸。当垃圾收集机器人想收集废旧餐巾纸的时候,它会让所有用餐的人先停下来,然后,依次询问餐厅里的每一个人:“你正在用餐巾纸吗?你用的是哪一张餐巾纸?”机器人根据每个人的回答将人们正在使用的餐巾纸画上记号。询问过程结束后,机器人在餐厅里寻找所有散落在餐桌上且没有记号的餐巾纸(这些显然都是用过的废旧餐巾纸),把它们统统扔到垃圾箱里。

正如其名称所暗示的那样,标记-清除算法的执行过程分为“标记”和“清除”两大阶段。这种分步执行的思路奠定了现代垃圾收集算法的思想基础。与引用计数算法不同的是,标记-清除算法不需要运行环境监测每一次内存分配和指针操作,而只要在“标记”阶段中跟踪每一个指针变量的指向——用类似思路实现的垃圾收集器也常被后人统称为跟踪收集器( Tracing Collector )


复制( Copying )算法

为了解决标记-清除算法在垃圾收集效率方面的缺陷, M. L. Minsky 于 1963 年发表了著名的论文“一种使用双存储区的 Lisp 语言垃圾收集器( A LISP Garbage Collector Algorithm Using Serial Secondary Storage )”。 M. L. Minsky 在该论文中描述的算法被人们称为复制算法,它也被 M. L. Minsky 本人成功地引入到了 Lisp 语言的一个实现版本中。

复制算法别出心裁地将堆空间一分为二,并使用简单的复制操作来完成垃圾收集工作,这个思路相当有趣。借用餐巾纸的比喻,我们可以这样理解 M. L. Minsky 的复制算法:

餐厅被垃圾收集机器人分成南区和北区两个大小完全相同的部分。午餐时,所有人都先在南区用餐(因为空间有限,用餐人数自然也将减少一半),用餐时可以随意使用餐巾纸。当垃圾收集机器人认为有必要回收废旧餐巾纸时,它会要求所有用餐者以最快的速度从南区转移到北区,同时随身携带自己正在使用的餐巾纸。等所有人都转移到北区之后,垃圾收集机器人只要简单地把南区中所有散落的餐巾纸扔进垃圾箱就算完成任务了。下一次垃圾收集的工作过程也大致类似,惟一的不同只是人们的转移方向变成了从北区到南区。如此循环往复,每次垃圾收集都只需简单地转移(也就是复制)一次,垃圾收集速度无与伦比——当然,对于用餐者往返奔波于南北两区之间的辛劳,垃圾收集机器人是决不会流露出丝毫怜悯的。

M.L.Minsky的发明绝对算得上一种奇思妙想。分区、复制的思路不仅大幅提高了垃圾收集的效率,而且也将原本繁纷复杂的内存分配算法变得前所未有地简明和扼要(既然每次内存回收都是对整个半区的回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存就可以了),这简直是个奇迹!不过,任何奇迹的出现都有一定的代价,在垃圾收集技术中,复制算法提高效率的代价是人为地将可用内存缩小了一半。实话实说,这个代价未免也太高了一些。

二、基于引用计数的垃圾回收器

为了实现引用计数的垃圾回收器,必须有某种方法来跟踪指向每块动态分配内存的指针的数量。解决方案是:可以建立一个新的支持垃圾回收的指针类型,也就是说该指针类型支持指针的操作,又有个数据成员来跟踪指向分配内存的指针的数量。具体来说,新的指针类型必须完成三件事情:第一,它必须为使用中的动态分配的对象维护一个引用计数的链表。第二,它必须跟踪所有的指针运算符,每次某个指针指向一个对象时,都要使这个对象的引用计数增1,每次某个指针重新指向其他对象时,都要使原本指向的对象的引用计数减1。第三,它必须回收那些引用计数降为0的对象。

根据上述的设计思路,构造指针类GCPtr
template
class GCPtr
{
private:

static list > gclist; //记录分配的每块内存的引用计数
T * addr; // 当前存储指针
bool isArray;
unsigned arraySize;
static bool first; // true when first GCPtr is created,用来注册退出函数,以解决引用计数实现的垃圾回收的循环引用问题
typename list >::iterator findPtrInfo(T * ptr); //查找ptr是否已记录在gclist链表中
public:

//构造函数,复制构造函数,析构函数及指针操作的一些函数
//特别的,有几个static函数,是垃圾回收器工作的核心
static bool collect();
static void shutdown(); //atexit注册的退出函数,用于清理循环引用的对象
};

其中GCInfo设计如下
template
class GCInfo
{
public :
unsigned refcount;
T *memptr;
bool isArray;
unsigned arraySize;
GCInfo(T *mptr,unsigned size = 0)
{
refcount = 1;
memptr = mptr;
if(size != 0)
isArray = true;
else
isArray = false;
arraySize = size;
}
};

在源码中,还提供了另外两个类,Iter 和 OutOfRangeExc。Iter是封装的迭代器类,因为GCPtr虽然支持普通指针的"*"和“->”操作,但并不提供指针运算,比如“++”,“--”。Iter类提供了类型安全的指针运算,使用方法与一般迭代器相同。
OutOfRangeExc是个异常类,但并没有实际的成员。如果有需要用到异常,可通过它进行扩展。

侯捷说“源码之前,了无秘密“,放出源码在此