王老师:
   
您好,今天无意中发现您的网站,匆匆浏览了一下,佩服于您的专业和文笔,尤其是您为菜
鸟们不厌其烦的答疑,更是罕见。
   
我是通过google搜索CAyncSocket才看到您的网站的。有些问题困扰我很久了,希望您能百
忙中指点迷津。
   
服务器和多个客户机通过CAsyncSocket(TCP)不停的互传数据,数据包的长度不定,从几
个字节到几十K,发送数据的时间也不定,我发现发送时,有时数个数据包是合起来一起发
送的,在接受方只产生一个OnRecieve事件,这就让我很难接收数据了,我必须检查并分解
接收到的数据(数据包格式为标志+长度+内容),更难的是我不知道缓冲区到底有多少数
据待读!
    我现在的解决方案是在OnRecieve事件中:
        char  Buffer[BUFFER_SIZE];
        int   nBufSize = BUFFER_SIZE;
        nReceived = pSocket->Receive(pBuf, nBufSize); 
        while(nReceived > 0)
         {
            .....
            nReceived = pSocket->Receive(pBuf, nBufSize); 
         }
   
以上省去了分解数据报的代码,(由于每次取到的数据不可能正好是几个包,会出现最后一
个包不完整,需要和下次取到的数据的前面部分合成一个包)。
   
但仔细一分析,发现有很大的漏洞:根据WinSock的原理,在缓冲区接收就绪时会通知框架
(我的理解是工作窗口),窗口的消息响应是一步一步的,这样可能造成:第一次数据到达
时,开始循环接收,第二次数据又到了,但第一次的接收循环还没有结束,结果把第二次的
数据接收了,如果的三次数据马上又到了......窗口岂不是无暇响应其它消息了?

   
这个问题对CSocket应该也存在(除非将Buffer定义得足够大,但是对CArchive还没研究)
。
看来,是不是只有为每个CAsyncSocket对象生成一个接收线程,取代OnRecieve事件?
   
我看到过有文章介绍:CAsyncSocket和CSocket对象是不能通过LPVOID传入线程的(不安全
),那么可否通过Detach()得到SOCKET传入线程,在线程内又通过FromHandle()得到CA
syncSocket对象(或指针)?其实在客户端,由于只有一个CAsyncSocket对象,大不了设为
全局变量,但是在服务器端,由于有不定个数的CAsyncSocket对象,我是保存在CPtrList中
的,不传似乎不可能。

    我还考虑过其它方案:用SOCKET API,这样有利于编制多线程,但是不知道如何让对方
    响应OnClose()?

    问题太多,说得太乱,也浪费了老师的时间,很抱歉!


            
2003-09-01 13:31 回复
你碰到的问题应该都是Socket特别是异步Socket编程中的常见问题。

Socket接收数据包时没法按照发送时的单元,完整地一次接收一个。这是所有Socket编程都
会遇到的正常现象。这必须编程处理。你的检查并分解数据的编程思路也是正确的。

接收数据包过多和数据量过大时,当然会影响当前线程的工作。把接收数据的操作封装在一
个独立线程(或进程)中的做法通常是比较有效的。

我不知道你所说的“通过LPVOID传入线程不安全”是指什么,也许你误解了那篇文章的意思
。在多线程的环境下,最重要的是要避免多个线程同时对同一个内存对象进行操作。那篇文
章的意思大概是说,如果你把一个CSocket对象的指针传入了多个线程中,而多线程又毫无
限制地同时访问该对象,这就会带来安全问题了。因此,如果你的程序不会出现多个线程同
时访问同一个CSocket对象的情况,那就不会有任何问题;如果需要同时访问,你只要在代
码中使用同步机制加以控制就可以了(安全与否和传入对象指针之间并没有本质的关联)。

使用Socket编写TCP/IP通信程序是最基本的技术,你可以控制所有环节,但编程也非常麻烦
,必须自己解决许多问题。如果使用更高级的分布式开发技术(比如RPC、CORBA、RMI或者Web
 Service)来实现你的通信程序,你碰到的数据包解析、负载平衡、多线程处理等问题就都
不用你操心了。

不知道以上信息对你是否有所帮助。
            
2003-09-01 16:26 来信
非常感谢王老师的及时帮忙!

您的回答让我放心不少。但还有几个问题想继续请教:
1。把CAsyncSocket对象指针传入线程函数后,在线程外不再使用Receive,只用Send,
是否存在安全隐患?
2。由于每个侦听套接字只能接收5个连接,那当我需要超过5个客户端时,是重新调用
Listen()就可以了呢,还是得重新创建一个侦听套接字?
3。每次缓冲区就绪时,向框架发送WM_SOCKET_NOTIFY消息,然后框架响应相应得事件,
如果用while循环,会不会将下一次得缓冲区也取出来了(不是指下一个数据包),造
成框架响应下一个WM_SOCKET_NOTIFY消息时,缓冲区已经空了?
其实,如果使用while(1)循环接收数据,可以不考虑这个问题,但由于我得程序本身
已经是多线程的,我现在考虑,是否通过向线程发消息来控制线程,不需要时让线程
挂起,需要时激活。请王老师帮忙分析一下可行性。

您推荐的技术,对RPC比较感兴趣,本来想采用DCOM的,但由于没有经验,项目较紧,
未敢贸然采用。

再次骚扰,深感惶恐!
            
2003-09-01 17:45 回复
CAsyncSocket对象中,Receive和Send在被多线程同时调用时是否安全,这取决于CAsyncSoc
ket类的实现,我没有仔细检查过CAsyncSocket类的源代码,没有办法给你一个明确的答复
。最好的办法是为Receive和Send都加上同步处理。

Socket编程中listen函数里的参数5并不是指你只能接收5个客户端的连接请求,5只是指Soc
ket库使用的等待队列的大小。请参阅MSDN中的相关说明。

我不太理解你所说的挂起再激活是指的什么。每次处理OnReceive事件时把读出所有可读的
数据就可以了,为什么还要用死循环或者自己考虑挂起和激活呢?

在各种高级分布式技术中,DCOM不太成熟,在大系统中很不稳定。RPC技术出现最早,也最
稳定,Windows本身的大多数通信功能就是用RPC实现的。CORBA在很多企业级应用上表现很
好。Web Service最新,比较容易学习和配置。你们可以根据需要选择。当然,每种技术都
有个学习和熟悉的时间。

            
2003-09-02 00:43 来信
王老师:
您好!

我对多线程的计划是:在SOCKET一连接成功,马上生成一个接收线程,然后直到
SOCKET关闭时销毁线程。听您的意思,是否应该在OnReceive事件里新建线程,接
收完毕马上销毁线程,然后下一次接收又重新生成、销毁?

            
2003-09-02 13:24 回复
不不不,我说的不是这个意思。你的理解是对的,“SOCKET一连接成功,马上生成一个接收
线程,然后直到SOCKET关闭时销毁线程”,一般的通信程序都是这么做的。

我们此前的交流可能有些误会。其实,很少有人在多线程的环境下使用异步的Socket。异步
Socket的初衷之一就是让当前线程在处理通信事件之余可以执行其他任务。如果你的程序已
经是多线程环境,为什么不让子线程专心地等待客户端传来的数据呢?也就是说,在多线程
环境下,最简单的阻塞式Socket就已经能工作得非常好了。有关在线程或子进程中使用sock
et的方法,你完全可以在网上找些服务程序的源码来参考。
            
2003-09-04 04:12 来信
谢谢王老师!

我的多线程和CAsyncSocket是无关的,不存在对应关系。当众多线程引起状态的改变,马上
需要通知相连的数个客户机(监控),我通过向窗口发送自定义消息,由窗口向客户机发送
数据包,我想,依靠窗口消息队列的特点,可以避免多个线程同时操纵某个SOCKET的现象。
这是我学习COM+线程套间时领悟的,不知对否?

**********************************************************
通过昨天的试验发现,如果通过一个循环不断的发数据,接收方会出现漏掉部分数据的现象
:

发送方:
char Buffer[1024]; 
short nLen = 1024; 

Buffer[0] = 'A';        //标志
Buffer[1023] = 'E';    //标志,试验时检查数据的完整性

memcpy(Buffer+1, nLen, sizeof(short));

for(short i = 0; i < 100; i++)
{
        memcpy(Buffer+1+sizeof(short),&i,sizeof(short));
        pSocket->Send(Buffer, nLen);
}

-----------------------------
接收方:(又长又臭的代码!)
CMyDlg m_pDlg;
CConnectSocket::OnReceive(int nErrorCode)
{
     if (nErrorCode == 0)
          m_pDlg->OnReceive(this);
     else
          TRACE("\nSocket receiving ERR");
}

void CMyDlg::OnReceive(CConnectSocket* pSocket)
{
 char  Buffer[BUFFER_SIZE];        //读取用的缓存
 int   nBufSize  = BUFFER_SIZE;    //缓存长度
 int   nReceived = 0;            //缓存内尚未处理的字节数
 int   nLenHead  = 1+SHORT_SIZE;    //数据包头部长度

 short nLen = 0;                //数据包还需要的长度
 char* pBuf = Buffer;            //当前指针

 nReceived = pSocket->Receive(pBuf, nBufSize);
 TRACE("\nNew OnReceive,nReceived = %d",  nReceived);

 while(nReceived >= nLenHead)
 {
      memcpy(&nLen, pBuf+1, SHORT_SIZE);  //长度
      short nlong = nLen;
      if(nLen >= nLenHead && nLen < 10240)
//规定每个数据包最大长度10K,最小为文件头长度,双方约定
      {
           char * pTempBuf = new char[nLen];//存放数据包的内存
           if(pTempBuf == NULL)            //分配内存失败,退出
                return;   

           char * p = pTempBuf;  
           while(nLen > nReceived)        
//完整数据包需要的长度超过Buffer内剩余的字节数,需要再读取
           {
                if(nReceived > 0)
                {
                    memcpy(p, pBuf, nReceived);
                    //将Buffer内剩余(第一次)或全部(以后重读)的字节拷入数据包
                    nLen -= nReceived;
                    p += nReceived;
                }
               
                nReceived = pSocket->Receive(Buffer, nBufSize);
                pBuf = Buffer;   
                TRACE("\nContinue OnReceive, nReceived = %d",  nReceived);
                if(nReceived == SOCKET_ERROR)    //错误或缓冲区已空
                {
                    delete []pTempBuf;
                    return;
                }
           }
 
           memcpy(p, pBuf, nLen);  //将数据包需要的字节拷入数据包
           nReceived -= nLen;
           pBuf += nLen;
  
       //开始处理发送方的请求

#ifdef _DEBUG
   short idx;
   memcpy(&idx, pTempBuf+nLenHead, SHORT_SIZE);
   TRACE("\n INDEX = %d", idx);
   TRACE("\n SYSMBOL1 = %d    SYSMBOL2 = %d ", *pTempBuf, *(pTempBuf+nlong-1));
#endif  
           if(*pTempBuf == 'A')
           {
                ......    //省略处理过程A
           }
           if(*pTempBuf == 'B')
           {
                ......    //省略处理过程B
           }
           else
           {
                delete []pTempBuf;
                return;
           }

           delete []pTempBuf;  
      }
      else            //数据包长度不对
           return;
         if(nReceived > 0 && nReceived < nLenHead) 
//如果此时还有内容,但已小于文件头
      {
           memcpy(Buffer, pBuf, nReceived);        //将内容移到头部
           while(nReceived < nLenHead)            //需要读取到大于文件头为止
           {            
                nLen = pSocket->Receive(Buffer+nReceived, nBufSize-nReceived);  
//由于头部此时已有nReceived个字节数据, 
                if(nLen == SOCKET_ERROR)    //错误
                     return;
                if(nLen > 0)                
                    nReceived += nLen;        //内容长度增加
           }
            pBuf = Buffer;            //将指针指向头部
      }
      else if(nReceived == 0)       //恰好结束,再读一次即可
      {
           nReceived = pSocket->Receive(Buffer, nBufSize);    
           if(nReceived <= 0)      //错误或缓冲区已空,结束本次接收
                return;
          pBuf = Buffer;            //将指针指向头部
 
      }  
    }
}

经过反复试验,发现老是有数据丢失,数据包越大,越容易丢失。后来,在Send后加::Slee
p(N),当N大于一定值时,才避免了数据丢失现象(这应该算人为造成阻塞吧,呵呵)。但
这只是权宜之计,没有从根本上解决问题。

******************************************************
我的理解是:数据丢失是由发送方造成的!不知道是否正确。
如果真是这样,多线程也解决不了问题了!
所以最好的办法是能检测发送缓冲区是否还有数据,但CAsyncSocket和SOCKET
API似乎都没有这个功能?

******************************************************
关于为什么不用阻塞的CSocket,原因是它没有OnClose,服务器程序退出,客户机都不知道
。但今天又发现OnCLose不是万能的,如果服务器断电,客户机还是检测不到。看来该是用C
Socket的时候了!
那么,请问,该如何检测连接是否正常呢?我原来用Timer,如果一段时间没有收到数据,
则发送一个验证数据包,接受方接到后马上回一个,如果发送了验证包,还是没有回音,证
明连接出问题。这可以吗?是否有什么直接的办法?

问了那么多关于Socket的问题,呵呵,自己都不好意思了。可我身边真的没有高手,只好一
再麻烦您了。

换个问题,应该还是蛮典型的。
我为了软件具有让用户二次开发的功能,为用户提供一个DLL接口,希望用户提供一个字符
串,可是字符串的大小很难确定,如果再主程序里预先定义大小不很合适,所以想通过指针
的指针来解决,如DLLGetString(char**
ppStr),这样就带来一个释放的问题。(主程序是VC的)
1。如果DLL也用VC,那么在DLL分配,在主程序释放,好像没有问题。
2。如果DLL改用Delphi和C++
Builder,那么在主程序释放时会产生错误,难道它们分配内存后还做了特殊的标志?不释放
倒是不出错,但内存肯定泄漏了。
3。能否让用户同时提供一个DLLReleaseString(char**
ppStr),主程序接收后,再调用它释放?就是说VC用过以后,Delphi和C++
Builder还认识它吗?DLL应该是每个进程有自己的一份内存,那么,对于多线程调用它,安
全吗?

这个问题,就算调试了没有问题,却无法保证内存不泄漏。因为DEBUG无法检测到这种内存
泄漏。知道有个Rational Purifier,装了半天没有成功,当然是D版的,呵呵。

再次为浪费您宝贵的时间而内疚.
            
2003-09-04 12:17 回复
原来你做的程序架构是这个样子,我此前一直没弄明白。利用消息机制,的确可以避免多个
线程同时操作某个socket对象。可为什么不做成效率更高的方式:每个线程都拥有一个独立
的socket对象,然后同时向不同客户发送呢?

你说你的代码可能有漏数据的现象。我没有仔细调试,不过我觉得每次简单调用pSocket->S
end(Buffer,nLen);发送1024字节的写法不太可靠(如果你用的是CAsyncSocket的话)。Send
并不能保证一定把1024个数据都发全了,还必须根据Send的返回值判断是否要继续发送。
参见CSocket::Send()函数源码中的写法:
	
	while (nLeft > 0)
	{
		nWritten = SendChunk(pBuf, nLeft, nFlags);
		if (nWritten == SOCKET_ERROR)
			return nWritten;

		nLeft -= nWritten;
		pBuf += nWritten;
	}

CAsyncSocket的OnClose的确只能在正常情况下接收socket的关闭消息。对服务器断电或网
线断掉这样的事情,通常就只有主动检测了。我们以前开发服务程序时,总会在该程序内创
建一个状态报告线程,负责向所有查询该服务状态的客户传递信息。

从API中返传字符串的问题,函数内分配的内存,只要传递正确,函数外总能认识(Delphi
和C++Builder也能认识),但最好不要函数内分配,函数外释放。这里最重要的问题是,调
用者很容易忘记释放该内存而造成内存泄漏。因此,比较稳妥的方法是由用户先调用函数(
或另一个专门的函数)获得要返回的字符串大小,然后用户主动分配内存,再把缓冲区指针
传进来。比如,WIN32函数:

int WideCharToMultiByte(
  UINT CodePage,            // code page
  DWORD dwFlags,            // performance and mapping flags
  LPCWSTR lpWideCharStr,    // wide-character string
  int cchWideChar,          // number of chars in string
  LPSTR lpMultiByteStr,     // buffer for new string
  int cbMultiByte,          // size of buffer
  LPCSTR lpDefaultChar,     // default for unmappable chars
  LPBOOL lpUsedDefaultChar  // set when default char used
);

其中的参数cbMultiByte如果为0,函数就返回要为lpMultiByteStr分配的内存大小,cbMult
iByte不为0,函数才真正返回字符串。通常用户都要调用两次WideCharToMultiByte函数才
能完成操作的。——这是一种最安全的做法。
            
2003-09-09 03:24 来信
出差几天,今天才看到您的回信。

我没有说清楚,其实我的每个线程并不是对应于一个SOCKET的(也就是说已有的线程和SOCK
ET是无关的),而是每个线程可能要向所有的连接发送数据,由于我用的是TCP,所以,我
感觉“每个线程都拥有一个独立的socket对象,然后同时向不同客户发送”似乎不妥,那样
,服务器端的连接SOCKET必须=线程数×客户机数目,客户端必须有SOCKET数目=服务器线
程数目。

由于您的提到MFC源码,所以我决定参考MFC源码以及《VC技术内幕》一书的CBlockingSocke
t类自己重写一个类,替换CAsyncSocket。

但是,如何检测连接是否正常(包括对方的非正常关机,网络的掉线等),我还是一点头绪
都没有,还希望您指点一二。

至于让DLL返回不知长度的字符串问题,我认真分析了一下,我的想法本身不是很好,应该
在同一模块分配和释放,这样比较安全,我现在采用的是向主程序窗口发送自定义消息,让
字符串地址通过消息参数wParam传过来。唯一的缺点是多线程到这里又要排队等候了,看来
没有两全的办法了:( , 不过,幸好我的多线程不是为了运行速度而创建的。
由于我的DLL需要到数据库查询,然后按照一定的规则生成字符串,而且数据库是动态的,
所以通过两次调用不但费时间,而且两次查询还不一定得到一样的结果(虽然概率很小),
所以您推荐的方法,我没有采用。

谢谢王老师。

祝王老师中秋节快乐!
            
2003-09-09 09:21 回复
好像你要做的事情还蛮复杂的。我不清楚你的软件需求,不知道是需求本身就这么复杂还是
设计上没有采用最简单的解决办法。如果你说的是服务端有m个与通信无关的事务线程,每
个线程都需要与n个客户端通信。那么我还见过另一种结构更简单的做法:

在m个事务线程之外,服务程序另起n个带消息循环的通信线程(t1...tn),每个线程有一个
特定的socket(直接用CSocket就可以了,没有必要用CAsyncSocket),并专门负责与一个
特定的客户端通信。对于某个事务线程,如果它要与第k号客户通信时,就直接通过消息把
通信需求传给通信线程tk。t1...tn中的每个线程内部,则顺序处理消息,完成与特定客户
的通信任务。

这样,服务程序与不同客户的通信是多线程并发的,与同一个客户的通信则是排队顺序完成
的。设计上简单,效率上也不错,而且用不着使用异步socket,也不用创建n*m个socket。
——我不知道这个方案是否符合你的需求。

以我的经验,一般情况下没必要替换MFC的CSocket类或CAsyncSocket类。实际上,绝大多数
程序即使不使用MFC,不使用异步socket,只用最原始的(像老的UNIX程序那样的)socket
编程就能解决问题了。——最简单的技术才是最好的技术嘛。

检测非正常关机、网络掉线等非正常情况,一般都通过主动轮询(在适当的时候探测连接是
否能建立)的方法来解决。还有一种被动的方法,即被探测机器上起一个小服务,定期向探
测机发送“存活”的消息,探测机多长时间收不到“存活”消息就认为被探测机已出现故障
。这两种方法的适用范围不大一样,还是要根据具体需求来定。

            
2003-09-01 19:29 来信
王老师:
    您好!
    今天刚阅读了你写的《凌波微步》一书的第7、第8章,因为我买这本书主要是因为对内
    存管理这方面的议题比较感兴趣。我现在有些问题想和你探讨一下:
    书中你对内存不足的处理采用的是抛出异常的方法,但我觉得在一个比较大型的软件项
    目中将会有大量的动态内存分配代码,如果每个地方都用异常的话,代码将会变得非常
    ugly而且难以维护(个人观点:-))。此外,如果在一个函数中有n个地方分配了内存,
    但在第n+1次分配的时候发生了内存不足,那么就得对前n次分配的内存进行释放,这也
    使得代码不易维护,而且也容易忘记释放, 不知对于这种情况有什么比较好的处理方法。
	在MFC中可以调用AfxSetNewHandler()设置内存分配失败的处理函数,我觉得是否可以
	在我们设置的处理函数中进行集中处理内存不足的情况,也就是说在其它分配内存的代
	码根本不做检查(C++ Gotchas一书中第61条有提到)。因为我觉得如果内存不足是由于
	开了太多程序而不是因为内存泄露的原因造成的话,最好的办法就是重启计算机了:-)
	还有我在VC6中实验过,new分配失败的话缺省行为是抛出异常而不是返回null,因此下
	面的代码判断指针部分应该不会执行而且还会有内存泄露:
	int pi = new int;
	// 假设上面分配成功
	int pf = new float;
	// 假设上面分配失败,MFC抛出异常
	if (pf == null)		// 该语句不会执行,而且pi所指的内存无法释放,造成内存泄露
	{
	}
    以上是我的一些个人观点,看了你的书觉得收获颇丰,谢谢你写了这本好一本书!
        

            
2003-09-02 14:27 回复
谢谢你的鼓励。

使用异常可能使代码难以维护,但这不是绝对的。如果处理得好(主要的原则是:选一个合
适的层次,通常是最高层,集中处理每一类严重异常),使用异常机制仍然是迄今为止最好
的一种解决方案。

多个资源的分配与释放的确比较麻烦。不过,一般来说,如果我们开发的是一般的应用程序
,在发生内存不足时,直接退出程序就可以了,根本用不着考虑资源的释放。释放前n个资
源的问题其实只对那些需要不间断运行的关键程序才比较重要。在这种关键程序中,为了程
序能7×24运行,无论怎样麻烦其实都是值得的。

AfxSetNewHandler好像不是MSDN推荐使用的函数(最新的MSDN文档中没有为其提供说明)。
要完成类似的功能,可以用set_new_handler函数。不过,我觉得在统一的层次捕捉内存异
常(如果不重载new操作符,VC抛出的内存不足异常都是bad_alloc异常)和这种做法并没有
本质的不同。我个人更偏好使用异常的方式。

关于new操作符返回异常的问题,你的实验是正确的。《凌波微步》中关于判断new返回值的
提法确实存在问题。关于这件事,比较严格的说法是,早期的C++语言的确在new分配失败时
返回NULL指针,这类似于malloc函数。而1998年的C++语言标准中则说,具体的实现可以在
分配失败时抛出bad_alloc异常(也可以选择别的处理方式)。结果,VC和大多数C++的实现
都选择了抛出异常的处理方式。但也不排除少数C++环境没有这样做,这取决于具体的运行
时库中new_handler函数的实现方法。——感谢你的提醒,我两年前写《凌波微步》时的确
没有考虑得那么全面。

C++标准中关于new操作符的定义摘录:

The allocation function shall either return null or  a  pointer  to  a
  block  of  storage  in  which  space  for  the  object shall have been
  reserved.

The allocation function can indicate failure by throwing  a  bad_alloc
  exception (_except_, _lib.bad.alloc_).  In this case no initialization
  is done.

A new_handler shall perform one of the following:
  --make more storage available for allocation and then return;
  --throw an exception  of  type  bad_alloc  or  a  class  derived  from
    bad_alloc;
  --call either abort() or exit();
            
2003-09-02 18:22 来信
王老师:
    您好
    谢谢你的解答。
    我查了MFC的源码,它确定是在最高层统一处理内存不足的异常情况,既然MFC已经帮我们处
理了内存不足的情况,是不是意味着我们就不用处理了,只要处理好内存泄露等其它错误情
况就好了。但我觉得这还是会造成内存泄露,因为MFC处理内存不足的缺省做法就是弹出一
个对话框报告“内存不足”,然后返回到应用程序,象下面的代码就会造成内存泄露:
     void A:test(void)
     {
          int pi = new int;
          int parr = new int[100000000];
          delete pi;
          delete [] parr;
     }
我用软件模拟内存不足的情况,则parr的分配会失败,弹出一个对话框报告“内存不足”,
然后返回到应用程序,那么pi的内存就泄露掉了。如果用auto_ptr来封装pi指针的话,那么
该问题可以解决,但我总觉得这么使用指针好像很不方便,我总不能把类成员的每个指针变
量都定义成auto_ptr吧。
   
我看过一些比较著名开源软件的源代码,如wincvs,它对不对内存不足的情况做处理,是否
说要求不是很严格的软件可以不对异常情况造成的内存泄露做处理,你觉得怎么做才比较好
呢?
    谢谢!
            
2003-09-03 12:47 回复
我觉得还是要分不同的情况。一般的程序的确可以不处理这种情况(包括Office都可以归入
这种“一般的程序”)。但关键的程序(比如Exchange这样的邮件服务)一定要自己处理好
内存问题。MFC或其他的类库提供的处理方式通常不会和你要求的处理方式一致,再说开发
关键程序时也不一定会用到MFC。

            
2003-09-01 21:40 来信
请教一个问题:

我在命令行中输入 netstat -a
得到以下结果:

   其中nqs是我们自己程序中的一个daemon进程,它还创建其它进程,并且通过socket与其
   它进程进行通信的,

不知道为什么会出现以下这么多的CLOSE_WAIT状态?如果重新启动进程就好了。

没有关闭线程句柄不会出现这个问题吧?

我在msdn中找到以下一段话,但是我不明白,您能够根据您的经验,判断出程序是哪里出
现问题了嘛?

“When you open an authenticated File Transfer Protocol (FTP) connection to 
a UNIX FTP server using the Add Network Place tool in My Network Places, three 
TCP sessions to the FTP server's TCP port 21 are created. When you close the 
window for the connection, only the first and third TCP sessions are properly 
closed. The second TCP session moves to a close_wait state and stays there 
indefinitely. "

结果:
Active Connections

  Proto  Local Address          Foreign Address        State
  TCP    oss11019:nqs           oss11005:516           CLOSE_WAIT
  TCP    oss11019:nqs           oss11005:517           CLOSE_WAIT
  TCP    oss11019:nqs           oss11005:518           CLOSE_WAIT
  TCP    oss11019:nqs           oss11005:519           CLOSE_WAIT
  TCP    oss11019:nqs           oss11005:efs           CLOSE_WAIT
  TCP    oss11019:nqs           oss11005:521           CLOSE_WAIT
  TCP    oss11019:nqs           oss11005:522           CLOSE_WAIT
  TCP    oss11019:nqs           oss11005:523           CLOSE_WAIT
  TCP    oss11019:nqs           oss11005:524           CLOSE_WAIT
  TCP    oss11019:nqs           oss11005:525           CLOSE_WAIT
  TCP    oss11019:nqs           oss11005:tempo         CLOSE_WAIT
  TCP    oss11019:nqs           oss11005:527           CLOSE_WAIT
  TCP    oss11019:nqs           oss11005:528           CLOSE_WAIT
  TCP    oss11019:nqs           oss11005:529           CLOSE_WAIT
  TCP    oss11019:nqs           oss11005:courier       CLOSE_WAIT
  TCP    oss11019:nqs           oss11005:conference    CLOSE_WAIT
  TCP    oss11019:nqs           oss11005:netnews       CLOSE_WAIT
  TCP    oss11019:nqs           oss11005:533           CLOSE_WAIT
  TCP    oss11019:nqs           oss11005:534           CLOSE_WAIT
  TCP    oss11019:nqs           oss11005:535           CLOSE_WAIT
  TCP    oss11019:nqs           oss11005:536           CLOSE_WAIT
  TCP    oss11019:nqs           oss11005:537           CLOSE_WAIT
  TCP    oss11019:nqs           oss11005:538           CLOSE_WAIT
  TCP    oss11019:nqs           oss11005:539           CLOSE_WAIT
  TCP    oss11019:nqs           oss11005:uucp          CLOSE_WAIT
  TCP    oss11019:nqs           oss11005:541           CLOSE_WAIT
  TCP    oss11019:nqs           oss11005:542           CLOSE_WAIT
  TCP    oss11019:nqs           oss11005:545           CLOSE_WAIT
  TCP    oss11019:nqs           oss11005:546           CLOSE_WAIT
  TCP    oss11019:nqs           oss11005:547           CLOSE_WAIT
  TCP    oss11019:nqs           oss11005:548           CLOSE_WAIT
  TCP    oss11019:nqs           oss11005:549           CLOSE_WAIT
  TCP    oss11019:nqs           oss11005:550           CLOSE_WAIT
  TCP    oss11019:nqs           oss11005:551           CLOSE_WAIT
  TCP    oss11019:nqs           oss11005:552           CLOSE_WAIT
  TCP    oss11019:nqs           oss11005:553           CLOSE_WAIT
  TCP    oss11019:nqs           oss11005:554           CLOSE_WAIT
  TCP    oss11019:nqs           oss11005:555           CLOSE_WAIT
  TCP    oss11019:nqs           oss11005:remotefs      CLOSE_WAIT
  TCP    oss11019:nqs           oss11005:557           CLOSE_WAIT
  TCP    oss11019:nqs           oss11005:558           CLOSE_WAIT
  TCP    oss11019:nqs           oss11005:559           CLOSE_WAIT
  TCP    oss11019:nqs           oss11005:560           CLOSE_WAIT
  TCP    oss11019:nqs           oss11005:561           CLOSE_WAIT
  TCP    oss11019:nqs           oss11005:562           CLOSE_WAIT

            
2003-09-02 13:35 回复
是不是这个问题?
http://support.microsoft.com/default.aspx?scid=kb;EN-US;q229658

这个人好像也遇到了和你一样的问题(网上有挺多与这个问题有关的讨论)

Workaround patch for MS CLOSE_WAIT bug
--------------------------------------------------------------------------------

The problem 
***********
What I want to fix is this
> netstat -an
  TCP    65.96.132.163:22       65.114.186.130:4762    CLOSE_WAIT
  TCP    65.96.132.163:22       65.114.186.130:1777    CLOSE_WAIT
  TCP    65.96.132.163:22       193.55.113.140:35861   CLOSE_WAIT
  TCP    65.96.132.163:25       204.127.198.37:40365   CLOSE_WAIT
  TCP    65.96.132.163:25       216.148.227.85:41320   CLOSE_WAIT
  TCP    65.96.132.163:110      65.114.186.130:4874    CLOSE_WAIT


Sockets stay in CLOSE_WAIT forever on Win 95/98/Me due to the bug
http://support.microsoft.com/default.aspx?scid=kb;EN-US;q229658
Please read that page to understand what follows.

Typical server application code looks like this:

sock = socket()
bind(sock)
listen(sock)
while (1) {
 select()
 newsock = accept(sock)
 pid = fork()
 if (pid == 0) { 
   close(sock)
   child works 
 }
 if (pid > 0) close(newsock)
}

Because newsock is closed in the parent before
being closed in the child, it stays in CLOSE_WAIT
until the parent exits. Not good in server applications.

Fixing the application
**********************
To keep sock open in the parent, the ported application 
structure can be changed to:

int oldsocks[2^32];  /* I'll be smarter */
sock = socket()     (1)
bind(sock)
listen(sock)
while (1) {
 select()
 newsock = accept(sock)
 pid = fork()
 if (pid == 0) { 
    close(sock)     (2)
    child works 
 }
 if (pid > 0) {
    oldsocks[pid] = newsock
                 <= (3)
 }
}
sigchild_handler()
{ 
  pid = waitpid()
  close(oldsocks[pid]) (4)
}

The itch is that oldsocks[pidA] remains open in the parent,
thus cygwin will duplicate it in a child pidB when there
is another accept() and fork(). 
The socket of pidA will then remain in CLOSE_WAIT if pidA 
terminates before pidB... Experience shows that CLOSE_WAIT can
occur even if pidB closes its copy (but does not terminate)
before pidA terminates.

Help from cygwin
****************
The itch can be cured by adding to cygwin a 
"close_on_fork" call and using it on line (3) above.
This "close_on_fork" applies only to sockets and simply
sets a flag. When the flag is set the socket is not 
duplicated in the parent and it is closed in the child 
(during fixups).

The easiest way to implement this is with fcntl(fd,F_SETCF,flag)
where "F_SETCF" is a newly defined command and "flag" is unused.
Calling it with non-socket fd's returns an error.

See Changelog and diffs below.

Related items
*************
Adding shutdown() before line (4) has also proved necessary
in some cases. Not sure why.
Weird behavior (details on request) can also be avoided by
"closing on fork" the main sock after line (1) and deleting
line (2).
In this situation the "linger on close" hack is unnecessary
and potentially harmful. 
I added a test in fhandler_socket::close().
 
I have ported two servers, exim (an MTA)and qpopper. 
With those fixes they seem to run fine on all Windows 
platforms I have tried (98, Me, NT).
inetd could be similarly patched (IMHO) but I don't 
find the Cygwin sources.

Similar CLOSE_WAIT issues have been reported with Apache
on Win2000.
http://sources.redhat.com/ml/cygwin/2001-10/msg01171.html
I have no idea if this patch can help there.

Problems with duplicate LISTEN have also been reported
by me and others
http://sources.redhat.com/ml/cygwin/2002-01/msg01579.html
http://sources.redhat.com/ml/cygwin/2002-04/msg00515.html
No progress there, it's mind boggling. My problem occurs 
only when the server re-execs itself while a child is 
running. "close on fork" doesn't seem to help.

Pierre
            
2003-09-02 12:36 来信
王大哥(不知道这样称呼你合不合适,我感觉你是个35左右的人,就叫你大哥呐 :-) ):
   你好!
  
我是北京理工的学生,有个关于LZW算法的问题想请教你,希望能得到你的帮助。对于图像
压缩我是刚开始接触,LZW编码器的输入是字符流而输出的是码字流,我的问题就是:
  
(1)在解码的时候,如果字典表里面只有#1--#6号,结果下一个码字是个#7号,这个序号所
对应的字符串怎么得出来呀,字典表里面还没有#33号呀?比如:
     a.字符串  ABBABABAC    
     b.字典表中原来有#1--A,#2--B,#3--C   编码后
字典表为#1--A,#2--B,#3--C,#4--AB,#5--BB,#6--BA,#7--ABA,#8--ABAC       
输出码字流#1#2#2#4#7#3
     c.解码时当读到#7时,字典表里面还没有#7的字符串,怎么产生#7所对应的字符串?
   (2)如果这有一幅GIF的图片,他的字典表包含在文件里面而传输到其他地方吗?
   (3)在GIF图片里面采用的LZW算法是不是和真正LZW算法有所不同?不同之处在哪里呢?
   希望你能给我一些指点,感激不尽!
            
2003-09-02 14:52 回复
LZW的码表是动态生成的,解码时不需要码表。编码和解码时码表的生成方式完全相同。

对于ABBABABAC,输出码字流#1#2#2#4#7#3

解码时,字典仍然初始化为 #1-A, #2-B, #3-C

#1 输出A
#2 输出B  更新字典为 #1-A, #2-B, #3-C, #4-AB
#2 输出B  更新字典为 #1-A, #2-B, #3-C, #4-AB, #5-BB
#4 输出AB 更新字典为 #1-A, #2-B, #3-C, #4-AB, #5-BB, #6-BA
#7
码表中没有,这表示字典编码时存在自嵌套。根据编码规则,编码时前一个输出是#4,这说
明#7的前一部分必定是AB,编码时,刚在字典里生成#7就输出了#7,这说明,#7的最后一个
字符一定和其第一个字符相同(因为下一个编码是从最后一个字符开始编的),这样,我们
就可以断定#7是ABA。
……

这是LZW中最绕人的问题了。不过只要仔细想,应该不难弄懂。

所以,GIF图片里不用包含任何字典信息(初始化时只要自动建好0-255这256个元素就行了
)。GIF采用的算法就是LZW算法,没有大的差异。

            
2003-09-06 10:47 来信
王老师:
  首先感谢你上封回复中所给我的指点!谢谢!现在还有几个问题:
  (1)在lzw压缩中,当字典表的长度超过了4096怎么办呢?
 (2)gif文件中的clear code 是怎么一回事?它的值等于2的『编码长度』次方,这个
值是随着光栅数据一起被lzw压缩的呢,还是在gif文件的压缩后的数据块中加入这些特
定的值用来划分解码?
  (3)在gif文件中光栅数据就是用前面Global Color Table中定的颜色的索引号,然后
把这些索引号压缩了,是这样吗?
            
2003-09-08 09:48 回复
(1)4096只是人为规定的数字,即最大码长12位时,字典中最多可以
有4096个编码。字典表长度超过4096时,不同的LZW变体有不同的处理方式,
有的变体重新初始化字典表,有的变体则对字典表进行轮转。

(2)gif文件中的clear code就是当字典中的编码个数达到极限时的转义码。
gif采用的是变长lzw编码,初始码长为4时,clear code就取16,结束码
end code取17,新的编码从18开始;初始码长为8时,clear code就取256,
结束码end code取257,新的编码从258开始。随着字典的增长,输出的编
码码长也可能增长。当字典空间用完时,编码器就输出clear code,然后
将字典恢复到初始状态。这样,解码器就知道何时该重建字典表了。

(3)gif文件要压缩的数据就是颜色表中的索引值,你的理解没错。
            
2003-09-02 13:52 来信
王先生:
  您好!
  我最近在学习汇编的时候,遇到了两个函数,分别是SysAllocStringByteLen和
SysFreeString。
我发现它们都是在oleaut32.inc中定义的原型。我只能根据它们在源程序中的使用情况
猜测其用法。
不知道是否有关于oleaut32.inc等包含文件的参考资料?请指教。谢谢。
            
2003-09-02 14:31 回复
这两个函数都是WIN32 Platform SDK中的函数。查微软的MSDN就可以找到它们的说明了。
            
2003-09-03 14:55 来信
王先生:
  您好!
  感谢您及时复信指导。我的 MSDN 可能是由于版本旧,宏汇编中的函数不能都
查到。请问是否有地方下载到最新的MSDN?
  我最近在学习汇编时遇到一个问题。我想在一个编辑软件的EDIT控件中获得光标位置
的行、列值。
不知使用什么函数?望指教!

            
2003-09-03 15:56 回复
我已经忘了我的MSDN是从哪里下载的了。很多网站都有,教育网内更多。直接连网到
msdn.microsoft.com也可以在线查阅。

先用WIN32函数GetCaretPos得到当前光标的坐标(像素坐标),然后可以向EDIT控件发送EM
_CHARFROMPOS消息,以获知该坐标在文本中第几个字符,第几行。

            
2003-09-09 15:20 来信
王先生:
  您好!
  接到您的邮件后,我用您所说的方法对程序进行了改进。但遇到了一个困难。
对EDIT控件发送EM_CHARFROMPOS消息后,返回值的低字好象是字符在文件中的
顺序,而不是在本行中的位置。例如:第一行有5个字符,则第二行的第一个
字符的值是7(可能是包括了第一行的回车)。
  请问是否有API能直接得出字符所在的列,如果没有如何解决?
            
2003-09-09 16:14 回复
没错,返回的字符序号是EDIT控件中所有字符总的序号。好像没有直接返回列号
的办法,不过既然有了总序号,我们又可以顺序扫描EDIT框中的文本,只要数数
换行回车的个数就可以算出列号了。

            
2003-09-12 11:04 来信
王先生:
  您好!
  在您的帮助下,我完成了对MASM32/V8中附带的例子程序的简单修改。增加了行列的
显示。在列显示的实现中,我使用了EM_LINEINDEX消息,获取当前行的首字符的序号,用已经
获得的当前字符的总序号减它再加1,这样就得到了当前列值。
  在您的帮助下,经过这段时间的学习,我增加了信心。
  经过您的多次指点,使我受益多多,我知道自己遇到了一位热心的高手。
  
         感谢您的多次指导!并希望能和您保持联系(如果您有时间的话)。

            
2003-09-12 11:20 回复
别客气。非常高兴能和你就技术问题进行交流。在软件开发方面,任何人都需要
不断学习,和同行的交流就是一种最好的学习方式。
            
2003-09-07 20:49 来信
王先生:
你好.
我是一个高校教师,这学期给学生上多媒体技术,在网上查资料时看到了你的<笨笨
数据压缩教程>,一口气读完,感觉真的很不错,我念硕士时是搞系统集成与智能建筑的,所以对
这方面不太了解,书也看得不多,感觉这方面的资料好象不多.所以我准备把你的文章分发给我
的学生,希望先得到你的同意.
另外,也希望你能抽空完成该教程,看到一个好多西缺一截,真的感觉很可惜.
希望没有浪费你的时间.
祝
好
            
2003-09-08 09:10 回复
没有问题,你可以按照你的需要使用我网站上的所有资源,使用时注明出处就可以了。
            
2003-09-09 00:44 来信
王老师:

您好!

我目前在作 基于 Golomb和 Rice 编码的数据压缩课题;可是有点摸不找门路,
 这两个编码的概念还不是特别清楚。 您能推荐一下有关的技术文档和网站、
 bbs嘛?谢谢!

另外,我想请教一下, Context 这英文单词在图像数据压缩里面表示什么特
别的意思呢? 我有这样一段解释的话;可是由于我是新手,还是理解的不太
透彻;请您能不能在百忙之中给与您的解释和指点? 谢谢!

Now if we consider some recent literature on video coding we 
find the word CONTEXT used in two different ways.  In developing 
the JPEG 2000 wavelet image coder, they used context to 
refer to the status of data in the spatial region of the data. 
In developing H-26L the word context came up in a different way, 
referring to the position in the bit stream of data and implementing 
it via a Context Adaptive Binary Arithmetic Coder (CABAC). 

            
2003-09-09 09:34 回复
Golomb和Rice并不复杂,下面的地址有比较详细的介绍和源代码

http://www.firstpr.com.au/audiocomp/lossless/#rice
http://members.optushome.com.au/emikulic/code/rice/

在压缩领域中Context一般译作“上下文”,即当前考察对象周围或附近的那些对象。根据
考察对象的不同,Context的所指也有所不同。在通用压缩算法如LZ77、LZW中,Context总
是指待压缩字符以前的那些字符流。在图像压缩中,Context则往往指像素空间(或波形空
间)中待考察对象附近区域(如8×8的区域中)的像素分布。但无论怎样,Context的基本
含义“上下文”都不会有太大的变化。你给的那段英文说的也是类似的事情。
            
2003-09-09 15:52 来信
王咏刚,你好!
   明天过节,不会还要加班吧?下面是我画的瓢,可是在运行的时候弹框,说读了
   不该读的内存。其实我就是想设置鼠标键盘两个钩子并把回调放在一个dll中。
另外"_MyMouseProc@12") 是什么意思啊?那鼠标的回调是不是应该叫
"_MyKeyboardProc@12")呢?
 
 
 HOOKPROC hMouseProc; 
 HHOOK* pThisHook;
 hinstDLL = LoadLibrary((LPCTSTR) "hook_mouse_dll.dll"); 
 hMouseProc = (HOOKPROC)GetProcAddress(hinstDLL, "_MyMouseProc@12"); 
 pThisHook = (HHOOK*)GetProcAddress(hinstDLL, "hThisHook"); 
 
 hHook = (*pThisHook) = SetWindowsHookEx(WH_MOUSE, hMouseProc, hinstDLL, 0); 
 if (!hHook)
 {
  AfxMessageBox("Can not install the hook");
  return -1;
 }
    HOOKPROC hKeyboardProc; 
 HHOOK* pThisKeyHook;
 hKeyboardProc = (HOOKPROC)GetProcAddress(hinstDLL, "_MyKeyboardProc@12"); 
 pThisKeyHook = (HHOOK*)GetProcAddress(hinstDLL, "hThisKeyHook");
 hkHook =(* pThisKeyHook) = SetWindowsHookEx(WH_KEYBOARD, hKeyboardProc, hinstDLL, 0); 
 if (!hkHook)
 {
  AfxMessageBox("Can not install the hook");
  return -1;
 }
            
2003-09-09 17:08 回复
在我给你的DLL源码中,实现了一个回调函数

extern "C" LRESULT CALLBACK MyMouseProc(int nCode, WPARAM wParam, LPARAM lParam)

其中CALLBACK这个关键字指定编译器按照WIN32回调接口的标准编译该函数,编译器在编译
时自动将函数名前加上下划线,在函数名后加@和所有参数的字节大小。为了搞清编译器到
底把该函数的名字变成了什么样,我们可以用VC提供的dumpbin程序查看:

dumpbin /exports hook_mouse_dll.dll

从返回结果可以知道函数名被变成了_MyMouseProc@12,所以我GetProcAddress时才用这个
古怪的名字来获得函数地址。

你需要检查一下你自己改过的dll里,键盘钩子的回调函数输出的是什么名字。

另外,我为了在dll保存钩子句柄,从dll中输出了一个全局变量hThisHook。你是否也用类
似的方法输出了变量hThisKeyHook?这都可能和你遇到的内存异常有关。你还可以检查检查
每个GetProcAddress的返回值,看是不是合法的内存地址。

            
2003-09-15 17:35 来信
王掌门,您好!
      我看到了你办的网站,很霸道,令人佩服!不过我在这里有个不情之请,
      能不能给我一段用C或者C++写的数据解压缩算法.不用太大, 看看各位
      高手的编程思路,以及具体写法。
不胜感激!
            
2003-09-17 10:00 回复
不太明白你要源码的用意。如果只是学习,那我建议你直接去学习压缩库zlib
(http://www.gzip.org/zlib/)的源码。编写zlib的人可是压缩领域的大宗师,
跟他学总不会有错的。

            
2003-09-17 16:02 来信
	谢谢你的回复,是这样的,我现在自己编写了一个关于网络视频功能的c/s模式的小软
件,功能是将客户端摄像头采集的数据进行压缩,然后传送给服务器存储,这个过程中我想
需要一个关于多媒体技术的压缩算法,比如mepg系列的算法,因为使用这些算法才能使用大
众的播放器进行播放,同时我的客户端平台是linux,而服务器平台是windows,所以要想算
法对于两个平台都有用,那么算法语言就必须同时适合linux,windows.所以算法最好是c/c+
+写的,这样才能在两个平台上顺利编译通过。
    
要源代码的目的就是为了使用您的算法压缩音视频源数据,然后利用windowmedia播放存放
在服务器上的图像和声音。
     如果您有什么好的建议的话,请给我一点帮助!万分感谢!
    
我知道关于多媒体数据压缩算法很难编写,所以我就像使用现成的源代码进行变异。嘿嘿,
没有办法,现在的程序好像都是这么写的。

            
2003-09-17 17:00 回复
现在开放源码的mpeg编码/解码器很多,连XviD这样的MPEG4程序都是开放源码并且
跨平台的,完全可以拿来用呀。

你可以在这里找找:http://datacompression.info/MPEG.shtml

            
2003-09-17 11:01 来信
如何测试一个程序的内存泄漏?
好像这个问题题目太大了。^_^

在任务管理器中,看到进程的占有内存量在增加,如何判断这是不是内存泄漏?
            
2003-09-17 16:56 回复
任务管理器显示的数据可以参考,但进程占用的内存量增加显然不能说明就是
内存泄漏(没准是正常增加的)。

对内存泄漏的跟踪最好是通过程序自身来解决。这有许多方法。比如,用C++编程时,
我们可以重载new和delete操作符,并在这两个操作符函数里编写自己的监控代码
(其实就是找个位置记录下每次分配和释放的内存地址、内存量就行了),并在
程序结束后,统计是否有内存泄漏。实际上,MFC已经重载了new和delete操作符,
使用MFC的程序在DEBUG模式下运行后,Output窗口都会报出内存泄漏的。C语言程
序则可以编写自己的malloc和free函数。

网上有一些可以监控内存泄漏的类库,用的也是上述方法。你可以找一找。

的确有监控内存泄漏的自动化工具(可能是实时监控进程中堆的状态吧),Rational
的Test Suite里就有这样的工具,不过我没用过。我自己还是喜欢写监控代码的方式。

            
2003-09-17 21:40 来信
您说使用写监控代码的方式来判断程序是否造成内存泄漏,我觉得好像不大可能啊,
如果你的系统已经做成了,而且比较大,那么是不是要把所有在系统中用到的
malloc/free全部替换,然后在判断啊,是不是工程巨大啊?
            
2003-09-18 09:46 回复
系统已经开发完了也不太难。我以前就做过这种事情,先实现自己的 
my_malloc 和 my_free 函数,然后在每个源代码文件#include 
后面加上

#ifdef _DEBUG
	#define malloc my_malloc
	#define free my_free
#endif

这样就行了,不用替换所有malloc/free

我当时的测试方法是把所有分配和释放情况记录到一个文本文件中,程序
运行完以后,把文本文件导入Excel,立刻就可以算出有没有内存泄漏

            
2003-09-18 08:15 来信
王咏刚,你好!

  在凌波微步中,你对异常的神秘的含义的第一个解释是,异常处理时发生的跳转是没有
明确的起跳点的,难道throw语句不是跳出点吗?或者throw本身的实现就很不明确?有些书
上说异常是对可能出现的问题的一种补救,这不是原话。而且使用异常,要比事先检测是否
符合条件要好。如
int x,y;
int z = (x*y)/(x+y);
为什么使用异常更好,是不是因为这个例子无法体现出异常的优势,使用异常相比起来更容
易维护?

在那个JPG的压缩库中,如果因为网络传输的问题导致图像数据不完整时,就会弹出一个对
话框,说无法进行正确解码。我想能不能通过加入异常,在出现上面的错误时,不显示对话
框,而是跳出函数,这样就不用进行人为的干预,虽然损失了一部分连续性。
            
2003-09-18 09:56 回复
我说的“异常处理时发生的跳转是没有明确的起跳点”,这是针对外层程序来说的,比如说
,我们编程时,要用到许多类库(数据库操作、通信操作等),这些类库都有可能抛出异常
,但在我们写的一个包含许多函数调用的try...catch块中,我们不知道那些底层函数到底
什么时候,在哪里返回异常,除非我们用try...catch检查每一次调用。

如果代码都是我们自己的,我们当然知道每个throw发生的位置了。

异常处理和传统的条件判断并没有明显的优劣之分,只是适用的场合不同罢了。

你说的JPEG压缩库是IJG的jpeg-6b吗?jpeg-6b实际上已经给我们提供了用自己的方式处理
错误情况的方法,只要我们实现自己的错误处理函数就可以了,见jpeg-6b源码中所附的文
档libjpeg.doc中“Error handling”一节。
            
2003-09-19 08:17 来信
王咏刚,你好!
   
  在dll中的钩子的回调函数之所以会变成怪样子,是不是就是因为它是回调函数?
另外我写了一个截获键盘消息的钩子,能够完成功能,可是在调试的时候,我发现在
调试窗口中能看到你写的回调函数的地址和那个古怪的名字,可是我的回调函数和
全局变量却只能看到地址,而看不到名称,看来vc也欺生啊。
            
2003-09-19 09:18 回复
在WIN32编程中,用WINAPI或CALLBACK修饰符修饰的C语言函数都会被编译器转换成后面带@
和数字的怪样子。实际上,几乎所有WIN32平台的核心API函数都是那个样子的。

你写的函数和我写的函数编译后的结果不一样,那一定是你的写法和我的写法有差异(也许
是细微的差异),“欺生”好像还不至于。
            
2003-09-23 09:37 来信
王咏刚,你好!
   Win32 Application
Win32 Console Application 
这两种应用程序之间有什么区别呢?如果Win32 Application创建了窗口,
他们的区别就是GUI和CUI?在windows核心编程中说Windows支持两种应
用程序GUI和CUI.

操作系统实际上并不调用编写的进入点函数。它调用的是C / C + +运行
期启动函数。该函数负责对C / C + +运行期库进行初始化,这样,就可
以调用m a l l o c和f r e e之类的函数。它还能够确保已经声明的任何
全局对象和静态C + +对象能够在代码执行以前正确地创建。然后根据联
接开关寻找相应的入口点函数.

Winmain,wWinMain,main,wmain,

C/C++运行期库应该不是标准库吧?那它是什么呢?

链接开关在vc中是通过建立工程时选择Win32 Application,Win32 
Console Application 是确定的吧.那是不是因为微软做了修改使得创建
应用程序时有些选项的入口函数的名字发生了变化,如
int _tmain(int argc, TCHAR* argv[], TCHAR* envp[]),int APIENTRY WinMain()

简单的说,为什么创建动态库的时候为什么选择最后一种类型,而不是前面的。

在vc6中好像能够使用#include <iterator>这种新型头文件格式,可是
这样使用STL ,说迭代器没定义。
#include "stdafx.h"

#include <iterator>
int main(int argc, char* argv[])
{
 istream_iterator< int > inputInt( cin );
 return 0;
}

在TC中是不是就不用设置链接开关呢,只根据入口函数,这样的话就没有C/C++运行库了?

现在才意识到语言和编译器造成的麻烦,我觉得这些知识够写一本书了。
思路很乱,见谅.
    根据软件工程的考虑,不同的信的内容应该使用不同的主题哈.
            
2003-09-23 11:47 回复
Console Application源自以前DOS下的应用程序。在NT、2000、XP这些新的操作系统中,DOS
环境实际上已经不存在了。NT、2000、XP中的WIN32环境专门为这类程序创建了一种新的资源
——控制台(Console)。因此,从本质上说,GUI和CUI都是PE格式的WIN32应用程序,只是在PE
文件(EXE)的头信息里用Subsystem来区分其类型,Subsystem为2表示GUI程序,Subsystem
为3表示CUI程序。

如果是CUI程序,操作系统会自动为程序创建一个Console,然后再转到程序的入口点。如果
是GUI程序,操作系统就直接转到程序的入口点执行,GUI程序自己负责创建窗口。

实际上,GUI程序也可以创建自己的Console,用WIN32的AllocConsole以及其他相关函数就
可以了。

操作系统装载应用程序后,做完初始化工作就转到程序的入口点执行。程序的入口点实际上
是由连接程序在PE文件中标明的,不同的连接器选择的入口函数也不尽相同。在VC下,连接
器对CUI程序设置的入口函数是mainCRTStartup,mainCRTStartup再调用你自己编写的main
函数;对GUI程序设置的入口函数是WinMainCRTStartup,WinMainCRTStartup调用你自己写
的WinMain函数。

mainCRTStartup这些入口函数都是在C/C++运行库中实现的,C/C++运行库其实就是VC环境提
供的一组函数库,它并不是WIN32环境的一部分,它里面的功能也是通过调用WIN32
API来实现的。一般我们选择动态连接方式时,SYSTEM32下看到的那个msvcrt.dll就是这个
函数库了。

动态库是另一种类型的PE文件,VC连接器在PE文件中指明你现在生成的DLL是一个动态连接
库。

VC6中包含的STL并不符合最新的C++标准的定义。道理很简单,最新C++标准于1998年发布,
而VC6是1997年发布的。因此,如果你想在程序中使用STL的话,我建议你还是换用VS.NET环
境中的VC7。当然,也可以下载一个标准的STL库安装到VC6环境里,具体的做法在http://co
ntextfree.net/wangyw/sgistl.htm里有详细的说明。

TC也有很多版本,早期的TC2只能编译DOS程序,后期的TC++3.1已经可以编译Windows程序了
(不过是早期的NE格式,不是PE格式),也有相应的编译、连接开关。早期DOS下的C/C++程
序也需要运行库,只不过那时全是静态连接的,运行库直接被连接到程序里了。

            
2003-09-23 20:54 来信
王咏刚,你好!
   我记得在凌波微步中有一章叫又见goto哈哈。这回还是有关钩子的,在钩子的范围说明中有
些钩子可以和系统的线程相关,也就是钩子的范围是系统的,这是不是说系统也有一个消息
队列,并且有一个线程和它对应?钩子函数可以在系统的消息队列中出现相应的消息时,调
用回调函数,我自己都认为上面的看法是不对的。呵呵。还是说系统级的钩子能够截取系统
和线程之间的消息,或者线程之间的消息呢?怎么理解钩子的系统范围呢?msdn只说线程在
调用函数的时候会调用钩子函数。
  
            
2003-09-25 20:22 回复
你的问题实质上是Windows消息管理机制的问题。对于你实现的一个单独的窗口,所有Windo
ws事件都以消息方式传送到窗口的回调函数中,这非常简单。但是,在整个Windows系统中
,有许多窗口和许多线程,Windows有一套完整的消息管理机制来管理所有消息的传递。你
可以从概念上这样理解:系统拥有一个大的消息池,并负责把相应的消息分发到特定的窗口
。而系统级的钩子就是那些可以监控到消息池中所有消息的钩子。当然,实际的情形比这复
杂得多,你可以参考WIN32核心编程一类的书籍。实际上,要真正把钩子用好,没有Windows
消息管理方面的知识是万万不行的。
            
2003-09-24 16:27 来信
我昨天刚开始我的硕士课程,
老板便叫我在两天之内要求编出二进制自适应算术编码的matlab程序。
目前我手上只有Matlab的普通算术编码的源程序,付在附件里面;我想请教一下您如果能够
将其由一般的算术编码 改成 可以自适应的 , 并且是 二进制的 算术编码呢?
所谓的 二进制的 是不是应该理解 成只考虑 0 , 1 ,即输入输出数据流只有0和1 ?
对于自适应,我觉得应该制定一个相应的表存放数据,而且应该是一个单独的表;但是具体
怎么操作,以及怎么在matlab中将这个表与二进制自适应算术编码的matlab原程序联系关联
调用呢?
整个工程您觉得具体应该有几个子程序? 相互关系应该是怎么样的呢?
占用了您的宝贵时间,不慎感谢!
            
2003-09-25 20:30 回复
“自适应”有很多种,最高级的自适应模型是马尔可夫和PPM模型,我不知道你们老师的要
求究竟是什么。

计算机里所有实用的算术编码算法都是二进制的,其计算过程和输出结果都是基于二进制数
的,没有人会用十进制的方式实现,那样太慢了。

真不好意思,我不太明白你们老师的意图,也没法告诉你该如何编程实现。当然,我也不打
算了解这道试题的所有细节,我这个人不大喜欢帮人做作业,无论你的老师布置该作业的目
的是不是故意***难你。

这里http://datacompression.info/有非常多的关于算术编码和自适应模型的资料,甚至有
许多现成的源代码,你可以参考。

            
2003-09-25 10:05 来信
不好意思,再打扰一下,我以前给您写过信的,就是问你算术编码的问题。我还有一点搞不
明白,就是在您编的压缩程序中的命令行格式中in-file out-file [-o
order],in-file的file格式是什么?即输入文件的格式是什么?我把一些数据保存成.bin
和.txt格式的文件,然后把它们做为输入文件写在命令行里,我是这样写的“an
in-data.txt out-cdata.txt -o 3”,可提示显示“Fatal error:Error opening
in-data.txt for input”,我找不到原因,请您在白忙之中给予答复,谢谢!
            
2003-09-25 20:33 回复
你可能弄错了。如果你要压缩的文件名是"data.txt",你希望压缩后的文
件名是"data.out",那你直接写

an data.txt data.out -o 3

就行了。

实际上,既然有源码,Debug一下不就全清楚了吗?
            
2003-09-27 17:26 来信
咏刚,你好:

   不知道你还能不能记得以前有一个人给你发邮件,反映一个关于在Win2000下的一个GDI
MemoryDC绘图错误而在WinXP下却是正确的那么一个问题,那时你回了信,可能是由于太忙
于出差(好像你那一段在上海出差),后来你就没有再回复。但这个问题仍然困扰着我,虽
然客户没有再要求使用Win2000,但我始终相信这是一个Windows
BUG。后来我知道WinXP使用了GDI+,而不是GDI,提供GDI
API函数只是为了向后兼容,因此我想肯定Windows
改正了这个问题了。不知道你现在还有空讨论这个问题吗?
            
2003-09-28 11:32 回复
是去年12月的事情吧?那两封邮件是你发给我的吗?我当时的确比较忙,没有对这个问题做
详细的测试。WinXP的GDI+只是一组新的GDI接口,原来的GDI接口仍然没有变化。你说的那
个问题的确挺有意思的,我这两天抽空再看看,有结果的话就告诉你。你有什么想法也可以
直接通知我。
            
2003-09-28 11:48 来信
非常感谢你的回复!
   不知道你还有没有我以前写的email,如果没有我会再给你发一个。
            
2003-09-29 16:38 回复
昨天对你说的那个Win2000下画线位置错误的问题做了一些测试,那个错误在我试过的4台Wi
n2000计算机(包括Server和Profession版上)都存在,而且,我还进一步发现,当我使用Wi
ndows下的“远程桌面连接”工具连接到一台Win2000服务时,在虚拟桌面上运行测试程序,
还可以发现更有趣的错误:我直接在窗口DC中画线(使用MoveTo和LineTo),当线宽大于等
于2时,线的坐标取

(300, -10000)-(1, 10000)
(300, -9000)-(1, 9000)
(300, -8268)-(1, 8268)

等值时,原应该穿过窗口的线根本就看不到,而只要将坐标改成

(300, -8267)-(1, 8267)

这条线就立刻可见了。这个现象只在虚拟桌面上存在,直接在服务器上运行测试程序就不能
重现了。这说明win2000在gdi画图时,当坐标较大时的确会有非常奇怪的错误,而且这个错
误还和当前显示用的Device有关(虚拟桌面的Device和本地桌面的Device不同)。

我上午通过邮件和微软研发中心的一个做技术的朋友讨论这个问题,他给我发了两篇微软的
知识库文章(见微软知识库文章 KB215336 和 KB299533)。这两篇文章在网上也可以找到,
文章讨论的问题都和坐标非常大时GDI的异常有关。尤其是那个画弧线发生异常的错误和我
们见到的错误如出一辙。显然,win2000的gdi里类似的bug不少。如果仔细在微软知识库里
找的话,我们还能发现DirectX在画图时也存在少数因为坐标过大引起的错误。

也有可能win2000的开发人员不认为这些问题是bug(或只把它们当作不那么重要的bug),
实际上,也很少有程序员会使用那么大的坐标。你发现的问题只在Win2000下可见而在Win98
和WinXP上不存在的事实似乎验证了程序员间的一个传言:WinXP并不单纯是从Win2000演化
来的,很可能WinXP从Win9x/WinMe中借鉴了不少代码。——我要是有机会联系上微软开发Wi
ndows的核心技术人员,一定得问问他这个问题。
            
2003-09-29 09:43 来信
王咏刚,你好!

  进程是不活泼的。若要使进程完成某项操作,它必须拥有一个在它的环境中运行的线程
,该线程负责执行包含在进程的地址空间中的代码。实际上,单个进程可能包含若干个线程
,所有这些线程都“同时”执行进程地址空间中的代码。为此,每个线程都有它自己的一组
C P U寄存器和它自己的堆栈。每个进程至少拥有一个线程,来执行进程的地址空间中的
代码。如果没有线程来执行进程的地址空间中的代码,那么进程就没有存在的理由了,系统
就将自动撤消该进程和它的地址空间。若要使所有这些线程都能运行,操作系统就要为每个
线程安排一定的C P U时间。它通过以一种循环方式为线程提供时间片(称为量程),造成
一种假象,仿佛所有线程都是同时运行的一样。
   
上面的话摘自windows核心编程,我不明白的是操作系统为每个应用线程分配时间片,把鼠标
消息发送给应用程序线程,这些工作是不是操作系统以线程的形式完成的如rundll32之类的?
谢谢
            
2003-09-29 16:45 回复
在WIN32环境中,操作系统有自己的进程和线程,一般我们叫内核进程或内核线程。WIN32
在Intel x86体系下的保护模式下运行,内核进程享有更高的优先级(参见Intel x86
保护模式的相关文档)。我们自己的进程和线程都是由内核进程创建和管理的。为线程分
时和为线程发送消息的工作也是由内核进程来完成的。你摘录的那段话中“操作系统”其
实就是指操作系统创建的内核进程与内核线程而言。