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是个异常类,但并没有实际的成员。如果有需要用到异常,可通过它进行扩展。

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

2010年3月21日星期日

关闭vmware喇叭报警音

在vmware里面安装linux系统后,操作时经常使用tab键,然后就经常听到pc
speaker突然叫一声,如果正当戴着耳机欣赏音乐,被这么一叫还要吓一跳。下面是解决办法:
C:\Documents and Settings\All Users\Application Data\VMware\VMware
Workstation中config.ini,在里面写入mks.noBeep =
"TRUE",如果没有config.ini文件,自己建立,修改完后把guest os重启下就OK了

2010年3月18日星期四

[转] (i++)+(++i)+(i++),这是个罪恶的语句

源代码是程序员之间的交流,不是程序员之间的互打哑谜。───题记

这是郑晖老师的话,我引为题记。我想,这里的读者一定会有大学生,你们在学C语言时,一定遇到过许多求“(i++)+(++i)+(i++)”之类的问题。昨天有人问我,令我一时语塞,研究了半天。今天我要告诉你们,这样的语句是罪恶的。你们也许要说,这明明是考试重点呀。别急,下面我们分成三个部分来组织:在第一部分中,解释为什么它有罪;第二部分,为了解救你们,我们找出解题规律;在第三部分中,给出几个更变态的题目,今后谁出这种题给你,你就用我这里的题目去问他。


一. 这种语句的合法性

C语言的创始人D.M.R在《CPL》中明确指出:

自增与自减运算符只能作用于变量,类似于表达式(i+j)++是非法的。

自增运算实际上包括了一个赋值运算,而表达式不能作赋值运算的左值,因为它没有确定的内存地址。只要你明白 (i+j)=5 是非法的,那就不难理解为什么(i+j)++非法。以此为依据,(i++)+(i++)这个语句中的后一项,是非法的。尽管看起来它只是一个i++,但别忘了,在前一个i++的作用下,后面的“i”本身就已经是表达式了,表达式不能再做++。一般地,在一个语句中,对同一个变量调用多次自增或自减运算,都是非法的。D.M.R还提醒,编译器应在这种情况下给出警告。事实上,gcc确实会对此给出一个:

Warning: operation on ‘i’ may be undefined

这已经够清楚了,无需多言。请编写教学大纲者注意,多年以来,你们一直在用非法语句作考试重点。

二. 如何计算

尽管它非法,但同学们没有执法权,只能任其蹂躏。所以,我们探索一下,编译器在摸不到头脑的情况下,是如何处理这个语句的。我们以 (i++)+(++i)+(++i)+(i++)+(i++) 为例,先将其编译成汇编代码,结果是这样的:

addl -4(%rbp), %eax movl -4(%rbp), %eax addl -4(%rbp), %eax incl -4(%rbp) addl -4(%rbp), %eax addl -4(%rbp), %eax addl -4(%rbp), %eax incl -4(%rbp) incl -4(%rbp) incl -4(%rbp)为了照顾看不懂汇编的朋友,我把它直接对应着翻译成C语言。用变量sum表示要求的和,就是下面这个样子:

i=i+1; sum=i; sum=sum+i; i=i+1; sum=sum+i; sum=sum+i; sum=sum+i; i=i+1; i=i+1; i=i+1这段代码能帮助你理解这个过程,我就不说了,留给你自己探索。下面直接给出我分析的结果:

1. 先将所有的 i++ 改成 i ,然后在整个语句的最后,统一将 i 自增相应的次数(语句中有几个 i++,就在最后自增几次)。我们的例子,这时会变成 i+(++i)+(++i)+i+i; i++; i++; i++;
2. 按照加法的结合性,先将左起前两项相加。如果前两项中含有 ++i,则先算 ++i;
3. 前两项的和作为一项,与第三项相加,以此类推。同样,遇到 ++i,就先算 ++i。

以上就是编译器处理这种语句的规律,当然,这并不是C语言定义的,只是编译器在出错情况下的无奈之举。大家可以用这个方法来解题,下面我就回答昨天一位同学问我的问题:

i=5; 求 (i++)+(++i)+(i++) 的值

按上面的方法做:
1. 先把i++换到最后面,变成 i + (++i) + i ; i++; i++;
2. 从左到右累加,先拿出前两项 i + (++i)。先算 ++i,i 的值变成6,两项相加得12;
3、计算 12 +i,得18。

2010年3月14日星期日

MP3 ID3的读取

研究MP3的结构,就不能不研究ID3标签。ID3标签是MP3音乐档案中的歌曲附加讯息,它能够在MP3中附加曲子的演出者、作者以及其它类别资讯,方便众多乐曲的管理。缺少ID3标签并不会影响 MP3的播放,但若没有的话,管理音乐文件也会相当的麻烦。如果你在网上download MP3,里面多半已经写有预设的ID3讯息。ID3,一般是位于一个mp3文件的开头或末尾的若干字节内,附加了关于该mp3的歌手,标题,专辑名称,年代,风格等信息,该信息就被称为ID3信息,ID3信息分为两个版本,v1和v2版。

其中:v1版的ID3在mp3文件的末尾128字节,以TAG三个字符开头,后面跟上歌曲信息。

v2版一般位于mp3的开头,可以存储歌词,该专辑的图片等大容量的信息。

 但ID3并不是MP3标签的ISO国际标准,ID3的各种版本目前只是一个近乎事实上的标准,并没有人强迫播放器或者编码程序必须支持它。

ID3V1大概有两个版本,由于ID3V1.0没有包括曲目序号的定义,所以Michael Mutschler在1997年进行了改进,引入了版本1.1。通过占用备注字段的最后两个字节,用一个00字节作标记,另一个字节改为序号,可以让ID3支持曲目编号了。一个字节的空间让ID3 V1.1支持最高到255的曲目序号,考虑到一张唱片超过256个曲目的可能性极小,这个改进还是相当合理的。但ID3V1只有128个字节可以使用,如果要在MP3中储存更多的信息,比如歌词,专辑图片等,显然是无法达到的,于是产生了ID3V2。ID3V2到现在一共有4个版本,但流行的播放软件一般只支持第3版,既ID3v2.3。由于ID3V1记录在MP3文件的末尾,ID3V2就只好记录在MP3文件的首部了。也正是由于这个原因,对ID3V2的操作比ID3V1要慢。而且ID3V2结构比ID3V1的结构要复杂得多,但比前者全面且可以伸缩和扩展。

但我们只需要读出MP3的TITLE,所以只要解析IDV1就够了。

ID3V1的内容和每个标签占用的字节说明如下:

char Header[3];    /*标签头必须是"TAG"否则认为没有标签*/
char Title[30];    /*标题*/
char Artist[30];   /*作者*/
char Album[30];    /*专集*/
char Year[4];    /*出品年代*/
char Comment[30];   /*备注*/
char Genre;    /*类型*/

可以定义一个如下的结构来存储MP3信息:

typedef struct _MP3INFO //MP3信息的结构
{
 char Identify[3]; //TAG三个字母
 //这里可以用来鉴别是不是文件信息内容
 char Title[31];   //歌曲名,30个字节
 char Artist[31];  //歌手名,30个字节
 char Album[31];   //所属唱片,30个字节
 char Year[5];   //年,4个字节
 char Comment[29]; //注释,28个字节
 unsigned char reserved;  //保留位,1个字节
 unsigned char reserved2; //保留位,1个字节
 unsigned char reserved3; //保留位,1个字节
} MP3INFO;
代码可以简单如下:

 #include "stdlib.h"
#include "stdio.h"
#include "string.h"
#define MAX 128

typedef struct _MP3INFO //MP3信息的结构
{
 char Identify[3]; //TAG三个字母
 //这里可以用来鉴别是不是文件信息内容
 char Title[31];   //歌曲名,30个字节
 char Artist[31];  //歌手名,30个字节
 char Album[31];   //所属唱片,30个字节
 char Year[5];   //年,4个字节
 char Comment[29]; //注释,28个字节
 unsigned char reserved;  //保留位,1个字节
 unsigned char reserved2; //保留位,1个字节
 unsigned char reserved3; //保留位,1个字节
} MP3INFO;

int main(int argc, char* argv[])
{
 FILE * fp;
 unsigned char mp3tag[128] = {0};
    MP3INFO mp3info;
 char oldname[MAX],newname[MAX],cmd[MAX];


 fp = fopen("G:\\mp3\\Debug\\5.mp3","rb");
 if (NULL==fp)
 {
  printf("open read file error!!");
  return 1;
 }
 fseek(fp,-128,SEEK_END);
 fread(&mp3tag,1,128,fp);
    if(!((mp3tag[0] == 'T'|| mp3tag[0] == 't')
  &&(mp3tag[1] == 'A'|| mp3tag[1] == 'a')
  &&(mp3tag[2] == 'G'|| mp3tag[0] == 'g')))
 {
     printf("mp3 file is error!!");
  fclose(fp) ;
  return 1;
 }

 memcpy((void *)mp3info.Identify,mp3tag,3); //获得tag
 memcpy((void *)mp3info.Title,mp3tag+3,30); //获得歌名
 memcpy((void *)mp3info.Artist,mp3tag+33,30); //获得作者
 memcpy((void *)mp3info.Album,mp3tag+63,30); //获得唱片名
 memcpy((void *)mp3info.Year,mp3tag+93,4); //获得年
 memcpy((void *)mp3info.Comment,mp3tag+97,28); //获得注释
 memcpy((void *)&mp3info.reserved,mp3tag+125,1); //获得保留
 memcpy((void *)&mp3info.reserved2,mp3tag+126,1);
 memcpy((void *)&mp3info.reserved3,mp3tag+127,1);
 fclose(fp);
 if (strlen(mp3info.Title) == 0)
 {
  printf("title is null\n");
  return 1;
 }
    strcpy(oldname,"5.mp3");
 sprintf(newname,"%s.mp3",mp3info.Title);
 sprintf(cmd,"rename G:\\mp3\\Debug\\%s %s",oldname,newname);
 printf("%s\n", cmd);
 system(cmd);


 return 0;
}




更新:
我尝试读出ID3v1的四个字段,发现因为mp3info没有初始化,显示出来的字段有些有乱码,所以在mp3info的定义语句后,通过memset()来初始化mp3info结构。结果在vs2008环境中,出现了一个很奇怪的错误:“error C2143: 语法错误 : 缺少“;”(在“类型”的前面)”。一番搜索无解后,发现调换下面那句char oldname[MAX],newname[MAX],cmd[MAX]; 与 memset()的顺序,就没有报错了。不知所以然。

2010年3月10日星期三

【转】学之者生,用之者死――ACE历史与简评

陈硕 (giantchen_AT_gmail)

Blog.csdn.net/Solstice

2010 March 10

ACE 是现代面向对象网络编程的鼻祖,确立了许多重要模式,如 Reactor、Acceptor 等,重要到我们甚至觉得网络编程就应该是那样的。但为什么 ACE 叫好不叫座?大名鼎鼎却使用者寥寥?本文谈谈我的个人观点。

ACE 是一套重量级的 C++ 网络库,早期版本由 Douglas Schmidt 独自开发,后来有 40 余名学生与工作人员也贡献了大量代码。作者 Douglas Schmidt 凭借它发表了 30 余篇学术论文。ACE 的一大特点是融合了 Douglas Schmidt 提出的很多面向对象网络编程的设计模式,并且具有不可思议的跨平台能力

1 ACE 历史

先说说 ACE 之父 Douglas Schmidt 的个人经历

我相信 ACE 是 Douglas 在读博期间的主要工作,ACE 这个名字最早出现在 1993 年 12 月的一篇会议论文上,Douglas 的这篇文章获得了“最佳学生论文”奖。在此之前,Douglas 已经用 ASX 等其他名字发表了内容相近的文章。

我能下载到的最早的 ACE 版本是 4.0.32,有大约 86,000 行 C++ 代码,代码的时间戳是 1998 年 10 月 22 日。早期 ACE 由 Douglas Schmidt 个人独立开发,从 ChangeLog 得知,1993 年 11 月 ACE 的版本号是 2.12。到了 1995 年 9 月,才有第一次出现其他开发者。在 1993~1996 年间的 684 次改动中,Douglas 一个人贡献了 529 次,另外几个主要开发者以及他们的修改次数分别是 Prashant Jain (58)、Tim Harrison (42)、David Levine (28)、Irfan Pyarali (20)、Jesper S. M|ller (5)。

从整个 ChangeLog 看,从 1993 年到 2010 年 3 月有 19,000 余次改动。有超过 200 人修改过代码,其中 23 个人的 check-in 次数大于 100,排名前 12 的代码修改者为:

   3635  Johnny Willemsen (活跃年份:2001~今)

   2586  Douglas C. Schmidt(原作者,活跃年份:1993~今)

   1861  Steve Huston (活跃年份:1997~今)

   1197  David L. Levine  (活跃年份:1996~2000)

    962  Nanbor Wang  (活跃年份:1998~2003)

    907  Ossama Othman (活跃年份:1999~2005)

    865  Chad Elliott (活跃年份:2000~今)

    823  Bala Natarajan (活跃年份:1999~2004)

    708  Carlos O'Ryan (活跃年份:1997~2001)

    544  J.T. Conklin (活跃年份:2004~2008)

    479  Irfan Pyarali (活跃年份:1996~2003)

    368  Darrell Brunsch (活跃年份:1997~2001)

看到这些“活跃年份”,你的第一反应是什么?我想到的是,这些人会不会多半是 Douglas 指导的研究生?我猜他们在读研期间参与改进 ACE,把工作内容写成论文发表,然后毕业走人。或许这能解释 ACE 代码风格的多样性。

在浏览代码历史的过程中,我还发现一个很有意思的现象,在 2008 年 3 月 4 日,某人不小心把整个 ACE 的源代码树删除了:

https://svn.dre.vanderbilt.edu/viewvc/Middleware?view=revision&revision=80824

随后又很快恢复:

https://svn.dre.vanderbilt.edu/viewvc/Middleware?view=revision&revision=80826

干这件事情的老兄在 2005~2009 这几年里一共 check in 了 120 余次。你对这件事情怎么看?你们的开发团队里有这样的人吗?

2 事实与思考

1. 除了 Douglas Schmidt 和 Stephen Huston 写的三本书籍之外,没有其他专著讲 ACE。

究竟是 ACE 太好用了,以至于无需其他书来讲解,还是太难用了,讲也讲不明白?抑或根本就没人在乎?

C++ 网络编程 第1卷》《C++ 网络编程 第2卷》《ACE 程序员指南》这三本书先后于 2001、2002、2003 年出版,之后再无更新。在同一时期,同样在网络编程领域,尽管 W. Richard Stevens 在 1999 年去世,他的 UNP 和 APUE 仍然由别人续写了新版。讲 C 语言 Sockets API 的书尚且不断更新,上层封装的 C++ 居然无动于衷?真的是封装到位了,屏蔽了这些变化?

UNP 的可操作性很强,读前面几章,就能上手编写简单的网络程序,看完大半本书,网络编程基本就算入门了,能编写一般应用的网络程序。相反,读完 ACE 那几本书,对于简单的网络编程任务还是感觉无从下手,这是因为书写得不好,还是 ACE 本身不好用?

2. ACE 很难用,非常容易用错

我不止听到一个人对我说,他们在项目里尝试过 ACE,不是中途放弃,因为出了问题无法解决;就是勉强交差,并且从下一个项目起坚决不用。我听到的另一个说法是,ACE 教程的例子必须原封不动地抄下来,改一点点就会出漏子。不巧的是,ACE 的例子举来举去就是个 Logging 服务器,让人想照猫画虎也无从下手。在最近的《代码之美》一书中,Douglas Schmidt 再次拿它为例,说明他真的很喜欢这个例子。

用 ACE 编程如履薄冰,生怕在阴沟里翻船,不知道它背后玩了什么把戏。相反,用 10 来个 Sockets 系统调用就能搞定网络编程,我感觉比使用 ACE 难度要小。为什么“高级”工具反而没有低级工具顺手呢?

不好用的直接后果是少有人用,放眼望去,目前涉及网络的 C++ 开源项目里边,鲜有用 ACE 作为通信平台的(我知道的只有 Mangos)。相反,libevent 这个轻量级的 IO multiplexing 库有 memcached 这样的著名用户。

3. ACE 代码质量不高,更像是一个研究项目,而不是工业界的产品

读 ACE 现在的代码,一股学生气扑面而来,感觉像在读实习生写的代码。抛开编码风格不谈,这里举三个“硬伤”:

  • sleep < 2ms

在某些早期的 Linux 内核上,如果 select/poll 的等待时间小于 2ms,内核会采用 busy-waiting。这是极大的 CPU 资源浪费,而 ACE 似乎没有考虑避免这一点。

  • Linux TCP self-connection

Linux 的 TCP 实现有一个特殊“行为”,在某些特殊情况下会发起自连接。而 Linux 网络协议栈的维护者认为这是一个 feature,不是 bug,拒绝修复。通常网络应用程序不希望出现这种情况,我见过的好的网络库会有意识地检查并断开这种连接,然而 ACE 漠然视之。

  • timeval on 64-bit

ACE_Time_Value 类直接以 struct timeval 为成员变量,保存从 Epoch 开始的微秒数。这在 32-bit 下没问题,对象大小是 8 字节。到了 LP64 模式的 64-bit 平台,比如 Linux,对象大小变为 16 字节,这么做就不够好了。我们可以直接用 int64_t 来保存这个以微秒为单位的时间,64-bit 整数能存下上下 30 万年,足够用了。减小对象大小并不是为了节约几个字节的内存,而是方便函数参数传递。在 x86-64 上,这种 8 字节的结构体可以用 64-bit 寄存器直接传参,也就是说 pass by value 会比 pass by reference 更快。对于一般的应用程序而言,要不要这么做值得商榷。对于底层的 C++ 网络库,不加区分地使用 pass by reference 会让人怀疑作者知其然不知其所以然。

对于以上几点情况,我怀疑 ACE 根本没用在 Linux 大规模生产环境下使用过,我只能期望它在别的平台表现好一些了。ACE 的作者们似乎更注重验证新想法,然后发论文,而不是把它放到工业环境中反复锤炼,打造为靠得住的产品。(类似 Minix 与 Linux 的关系。)

4. 移植性很好,支持我知道的和不知道的很多平台

ACE 的意义在于让我们明白了C++代码可以做到可移植,并展示了这么做会付出多么巨大的代价。不细说了,读过 ACE 代码的人都明白。

从代码质量上看,ACE 做到了能在这些平台上运行,但似乎没有在哪个平台占据主导地位。有没有哪个平台的网络编程首选 ACE?

出现这一状况的原因是,跨平台和高性能是矛盾的。跨平台意味着要抽象出多个平台的共性,以最 general 的方式编写上层代码。而高性能则要求充分发挥平台的特性,剑走偏锋,用尽平台能提供的一切加速手段,哪怕与其他平台不兼容。网络编程对此尤为敏感。

我不知道 ACE 的性能如何,因为在各项性能评测榜上基本看不到它的名字(c10k 里就没有 ACE 的身影)。另外,Buffer class 的好坏直接反应了网络库对性能的追求,ACE 提供了比 std::deque<uint8_t> 更好的输入输出 Buffer 吗?(我不是说 deque 有多好,它基本是 fail-safe 的选择而已。)

5. ACE 过于复杂,甚至比它试图封装的对象更复杂。

(这里的代码行数均为 wc 命令的粗略估计。)

ACE 5.7 自身(不含 TAO 和 CIAO)有 30 万行 C++ 代码(Douglas 自己给出的数据是 25 万行,可能指的是略早的版本),这是一个什么概念呢?我们来看 TCP/IP 协议栈本身的实现有多少行:(均不含 IPv6)

  • TCPv2 列出的 BSD4.4-Lite 完整 TCP/IP 协议栈代码有 15,000 行,其中 4,500 行 TCP 协议,800 行 UDP 协议,2,500 行 IP 协议
  • Linux 1.2.13 完整的 TCP/IP 协议栈有 2 万多行 (net/inet)
  • Linux 2.6.32.9 的 TCP/IP 协议栈有 6 万多行 (net/ipv4)
  • FreeBSD 8.0 的 TCP/IP 协议栈有 5 万多行 (sys/netinet, 不含 sctp)

换句话说,ACE 用 30 万行 C++ 代码“封装”了不到 10 万行 C 代码(且不论 C++ 代码的信息密度比 C 大),这是不是头重脚轻呢?我理解的“封装”是把复杂的东西变简单,但 ACE 好像走向了另一个方向,把不那么复杂的东西变复杂了。

这个对比数字可能不太准确,因为 ACE 还封装了很多其他东西,请看。http://www.dre.vanderbilt.edu/Doxygen/5.7.7/html/ace/inherits.html 和 http://www.dre.vanderbilt.edu/Doxygen/5.7.7/html/ace/hierarchy.html

以下两张类的继承关系图片请在新窗口打开:

http://www.dre.vanderbilt.edu/Doxygen/5.7.7/html/ace/a06178.png

http://www.dre.vanderbilt.edu/Doxygen/5.7.7/html/ace/a06347.png

Douglas 说 ACE 包含了 40 人年的工作量,对此我毫不怀疑。但是,网络编程真的需要这么复杂吗?TCP/IP 协议栈的实现也没这么多工作量嘛。或许只有 CORBA 这样的应用才会用到这么复杂的东西?那么为什么 ICE 在重新实现 CORBA 的时候没有基于 ACE 呢?是不是因为 ACE 架子拉得大,底子并不牢?

3 ACE 的意义

ACE 对于面向对象、设计模式和网络编程具有重大历史和现实意义。

ACE 诞生之时,正是 90 年代初期面向对象技术的高速发展期,ACE 一定程度上是作为面向对象技术的成功案例来宣传的。

在 1994 年前后,Unix 分为两个阵营,AT&T 的 SVR4 与 BSD 的 BSD4.x,这两家的 IO multiplexing 不完全兼容。比如 SVR4 提供 poll 调用,而 BSD 提供 select 调用。ACE 当时的宣传点之一是用面向对象技术屏蔽了两个平台的差异,提供了统一的 Reactor 接口。

【接下来,poll 在 1996 年 9 月 7 号加入 NetBSD,并随 NetBSD 1.3 于 1998 年 1 月 4 号发布。随后 FreeBSD 3.0 也支持 poll,1998 年 10 月发布。Linux 很早就支持 select,从 2.1.23 内核起支持 poll,发布日期为 1997 年 1 月 26 号。也就是说,到了 1998 年,平台差异被暂时抹平了。随后 epoll、/dev/poll、kqueue 以性能为名,再次扩大了平台差异。当然,Windows 至今不支持 poll。】

ACE 的设计似乎过于强调面向对象的灵活性,一些不该使用虚函数的地方也提供了定制点,比如 ACE_Timer_Queue 就应是个具体类,而不是允许用户 override schedule/cancel/expire 之类的具体操作。面向对象中,“继承”的目的是为了被复用,而不是去复用基类的代码。

查其文献,Reactor 在 1993 年登上《C++ Report》杂志的时候,文章标题还比较朴素,挂着“面向对象”的旗号:

转眼到了 1994 年,也就是《设计模式》成书的那一年,Douglas 开始写文章言必称 pattern:

似乎 "pattern" 这个字样成了发文章的通行证,这股风气直到 2000 左右才刹住。之后这些论文集结出版,以《Pattern-Oriented Software Architecture》为名出了好几本书,ACE 的内容主要集中在第二卷。(请留意,原来的提法是 Object-Oriented,现在变成了 Pattern-Oriented,似乎软件开发就应该像糖果厂生产绿豆糕,用模子一个个印出来完事。)

ACE 就像一个 pattern 大观园,保守估计有 10 来种 patterns 藏身其中,形成了一套模式语言(《Applying a Pattern Language to Develop Application-level Gateways》),这还不包括 GoF 定义的一般意义下的 OO pattern。

通过 ACE 来学习网络编程,那是本末倒置,因为它教不了你任何 UNP 以外的知识。(Windows 网络编程?)

然而,如果要用面向对象的方式来搞网络编程,那么 ACE 的思想(而不是代码)是值得效仿的,毕竟它饱含了 Douglas Schmidt 等学者的心血与智慧。学得好的例子有 Apache Mina、JBoss Netty、Python Twisted、Perl POE 等等。

这就是我说“学之者生,用之者死”的含义。

4 ACE 文献导读

Douglas Schmidt 写了很多 ACE 的文章,其中不乏内容相近的作品。读他的文章,首选发表在技术杂志上的文章(比如 C++ Report),而不是发表在学术期刊或会议上的论文。前者的写作目的是教会读者技术,后者则往往是展示作者的新思路新想法,技术文章比学术论文要好读得多。

由于当时面向对象技术尚在发展,Douglas 文章里的图形很有特色,不是现在规范的 UML 图(那会儿 UML 还没定型呢),而是像变形虫一样的类图(经pinxue指出,这种图是 Grady Booch 发明的),放在一堆文献里也很容易认出来。

如果要用 ACE 的代码来验证文章的思路,我建议阅读和文章同时期的 4.0 版本代码,代码风格比较统一,代码量也不大,便于理解。

下面介绍几篇有代表性的论文。

以上两篇文章实际上内容基本相同,都是对 ACE 的概要介绍,看第二篇即可,第一次没看懂也没关系。

剩下要看的是一篇 Socket OO 封装、四篇 Reactor、三篇 Acceptor-Connector、一篇 Proactor。这些文章前面大多都给了链接,其余的这里补充一下:

不想看这 10 篇论文的话,读中译本《C++ 网络编程 第1卷》《C++ 网络编程 第2卷》《ACE 程序员指南》也行,翻译质量都不错。

5 设想中的 C++ 网络库

谈了这么多 ACE 的优缺点,那么我心目中理想的网络库是什么样子的呢?

  • 线程安全,支持多核多线程
  • 不考虑可移植性,不跨平台,只支持 Linux,不支持 Windows。
  • 在不增加复杂度的前提下可以支持 FreeBSD/Darwin,方便将来用 Mac 作为开发用机,但不为它做性能优化。也就是说 IO multiplexing 使用 poll 和 epoll。
  • 主要支持 x86-64,兼顾 IA32
  • 不支持 UDP,只支持 TCP
  • 不支持 IPv6,只支持 IPv4
  • 不考虑广域网应用,只考虑局域网
  • 只支持一种使用模式:non-blocking IO + one event loop per thread,不考虑阻塞 IO
  • API 简单易用,只暴露具体类和标准库里的类,不使用 non-trivial templates,也不使用虚函数
  • 只满足常用需求的 90%,不面面俱到,必要的时候以 app 来适应 lib
  • 只做 library,不做成 framework
  • 争取全部代码在 5000 行以内(不含测试)
  • 以上条件都满足时,可以考虑搭配 Google Protocol Buffers RPC

我觉得网络库要解决现实的问题,满足现实的需要,而不是把 features/patterns 堆在那里等别人来用。应该先有应用,再提炼出库。而不是先造库,然后寻求应用。上面这样一个网络库就能满足我目前的全部需要。

2010年2月28日星期日

C++ Keywords: typedef

(1)定义已知类型的别名

In C++, the typedef keyword allows you to create an alias for a data type. The formula to follow is:

typedef [attributes] DataType AliasName;

The typedef keyword is required. The attribute is not.

例子:
typedef short SmallNumber;
typedef unsigned int Positive;
typedef double* PDouble;
typedef string FiveStrings[5];


1. In typedef short SmallNumber, SmallNumber can now be used as a data type to declare a variable for a small integer.
2. In the second example, typedef unsigned int Positive, The Positive word can then be used to declare a variable of an unsigned int type.
3. After typedef double* PDouble, the word PDouble can be used to declare a pointer to double.
4. By defining typedef string FiveStrings[5], FiveStrings can be used to declare an array of 5 strings with each string being of type string.

SmallNumber temperature = -248;
Positive height = 1048;
PDouble distance = new double(390.82);
FiveStrings countries = { "Ghana", "Angola", "To“,"Tunisia", "Cote d'Ivoire" };

值得注意的写法
typedef char* PSTR, CHAR;
CHAR为char型,所以更好的写法是
typedef char *PSTR, CHAR; 或者 typedef char CHAR, *PSTR;

(2) 定义函数类型别名

The typedef keyword can also be used to define an alias for a function. In this case, you must specify the return type of the function and its type(s) of argument(s), if any. Another rule is that the definition must specify that it is a function pointer。

例子:
typedef double (*Addition)(double value1, double value2);
                          
double Add(double x, double y)
{
       double result = x + y;
        return result;
}

Addition plus;
plus = Add;
plus(3855.06, 74.88);

例子2:(msdn上)

The following example provides the type DRAWF for a function returning no value and taking two int arguments:

 typedef void DRAWF( int, int );

After the above typedef statement, the declaration

 DRAWF box; 

would be equivalent to the declaration

 void box( int, int );

(3)
总结typedef的语法为:

More generally, after the word “typedef”, the syntax looks exactly like what you would do to declare a variable of the existing type with the variable name of the new type name. Therefore, for more complicated types, the new type name might be in the middle of the syntax for the existing type. For example:

    typedef char (*pa)[3]; // "pa" is now a type for a pointer to an array of 3 chars
    typedef int (*pf)(float); // "pf" is now a type for a pointer to a function which takes 1 float argument and returns an int

C语言函数入栈顺序与可变参数函数

函数调用时如果出现堆栈异常,十有八九是由于函数调用约定不匹配引起的。比如动态链接库a有以下导出函数:

long MakeFun(long lFun);

动态库生成的时候采用的函数调用约定是__stdcall,所以编译生成的a.dll中函数MakeFun的调用约定是_stdcall,也就是函数调用时参数从右向左入栈,函数返回时自己还原堆栈。现在某个程序模块b要引用a中的MakeFun,b和a一样使用 C++方式编译,只是b模块的函数调用方式是__cdecl,由于b包含了a提供的头文件中MakeFun函数声明,所以MakeFun在b模块中被其它调用MakeFun的函数认为是__cdecl调用方式,b模块中的这些函数在调用完MakeFun当然要帮着恢复堆栈啦,可是MakeFun已经在结束时自己恢复了堆栈,b模块中的函数这样多此一举就引起了栈指针错误,从而引发堆栈异常。宏观上的现象就是函数调用没有问题(因为参数传递顺序是一样的),MakeFun也完成了自己的功能,只是函数返回后引发错误。解决的方法也很简单,只要保证两个模块的在编译时设置相同的函数调用约定就行了。

在了解了函数调用约定和函数的名修饰规则之后,再来看在C++程序中使用C语言编译的库时经常出现的LNK2001错误就很简单了。还以上面例子的两个模块为例,这一次两个模块在编译的时候都采用__stdcall调用约定,但是a.dll使用C语言的语法编译的(C语言方式),所以a.dll的载入库a.lib中MakeFun函数的名字修饰就是“_MakeFun@4”。b包含了a提供的头文件中MakeFun函数声明,但是由于b采用的是C++语言编译,所以MakeFun在b模块中被按照C++的名字修饰规则命名为“?MakeFun@@YGJJ@Z”,编译过程相安无事,链接程序时c++的链接器就到a.lib中去找“?MakeFun@@YGJJ@Z”,但是a.lib中只有“_MakeFun@4”,没有“?MakeFun@@YGJJ@Z”,于是链接器就报告:

error LNK2001: unresolved external symbol ?MakeFun@@YGJJ@Z

解决的方法和简单,就是要让b模块知道这个函数是C语言编译的,extern"C"可以做到这一点。一个采用C语言编译的库应该考虑到使用这个库的程序可能是C++程序(使用C++编译器),所以在设计头文件时应该注意这一点。通常应该这样声明头文件:

#ifdef _cplusplus
extern "C" {
#endif

long MakeFun(long lFun);

#ifdef _cplusplus
}
#endif

这样C++的编译器就知道MakeFun的修饰名是“_MakeFun@4”,就不会有链接错误了。

许多人不明白,为什么我使用的编译器都是VC的编译器还会产生“error LNK2001”错误?其实,VC的编译器会根据源文件的扩展名选择编译方式,如果文件的扩展名是“.C”,编译器会采用C的语法编译,如果扩展名是 “.cpp”,编译器会使用C++的语法编译程序,所以,最好的方法就是使用extern "C"。

==============================================
大家会注意到,像gcc之类的编译器也是采用__cdecl方式,这样对于printf(“%d %d“,i++,i++)之类的输出结果的疑问就迎刃而解了。

另外引出的一个问题是C语言的变参数函数,对于这部分估计很少大学会涉及到,但这的确是C语言很常见的一个特性,比如printf等。上面提到对于可变参数的支持,需要由函数调用者负责清除栈中的函数参数,但为什么非要这样才可以支持变参数呢?函数调用所涉及到的参数传递是通过栈来实现的,子函数从栈中读取传递给它的参数,如果是从左向右压栈的话那么子函数的最后一个参数在栈顶,然后依次是倒数第二个参数......;如果是从右向左压栈的话那么子函数的第一个参数在栈顶,然后依次是第二个参数......,压栈的顺序问题决定了子函数读取其参数的位置。对于变参数的函数本身来讲,并不知道有几个参数,需要某些信息才能知道,对于printf来讲,就是从前面的格式化字符串fmt来分析出来,一共有几个参数。所以被调函数返回清理栈的方式,对于可变参数是无法实现的,因为被调函数不知道要弹出参数的数量。而对于函数调用着,自己传递给被调函数多少参数(通过把参数压栈)当然是一清二楚的,这样被调函数返回后的堆栈清理也就可以做到准确无误了(不会多也不会少地把参数清理干净)。
===============================================

Solution of fatal error RC1004: unexpected end of file found

After you modified your reource.h, you may got this error:

fatal error RC1004: unexpected end of file found.

It's a bug of the resource compiler.

The solution is adding a blank line or two to the end of the file  and build again.

2010年1月15日星期五

visual studio 2008 试用版评估期已结束的解决方法

启动visual studio 2008后显示对话框:visual studio的试用版评估期已结束。

 解决方法如下:

VS2008中英文正式版序列号
1.Visual Studio 2008 Professional Edition:
XMQ2Y-4T3V6-XJ48Y-D3K2V-6C4WT
2.Visual Studio 2008 Team Test Load Agent:
WPX3J-BXC3W-BPYWP-PJ8CM-F7M8T
3.Visual Studio 2008 Team System:
PYHYP-WXB3B-B2CCM-V9DX9-VDY8T
4.Visual Studio 2008 Team Foundation Server:
WPDW8-M962C-VJX9M-HQB4Q-JVTDM
-----------------------------------------------------------------
把90天试用版改为正式版,二种方法:
1. 把Setup\setup.sdb文件中的
    [Product Key]
    T2CRQGDKBVW7KJR8C6CKXMW3D
  改成
    [Product Key]
    PYHYPWXB3BB2CCMV9DX9VDY8T
2.安装完成后,在“控制面板”中启动“添加删除程序”,选中Vs2008,点击“更改、删除”,
输入序列号:PYHYP-WXB3B-B2CCM-V9DX9-VDY8T