我现在正在做一个硕大的com组件(winxp+vc6sp5),里面包含了300多个atl object,但是偶然一次当我再新添加一个atl object 后,vc的DEBUG版本就编译部过去了,但是Release 版本还可以正常编译。错误信息如下:
Generating Code...
Linking...
.\Debug\MyDll.obj : error : Internal error during ReadSymbolTable
  ExceptionCode            = C0000005
  ExceptionFlags           = 00000000
  ExceptionAddress         = 00462842
  NumberParameters         = 00000002
  ExceptionInformation[ 0] = 00000000
  ExceptionInformation[ 1] = 0170B000
CONTEXT:
  Eax    = 40D5A910  Esp    = 0012F098
  Ebx    = FFFF8000  Ebp    = 020D6019
  Ecx    = 40D5A910  Esi    = 40F5A96C
  Edx    = 0174B008  Edi    = 40F5A96C
  Eip    = 00462842  EFlags = 00010246
  SegCs  = 0000001B  SegDs  = 00000023
  SegSs  = 00000023  SegEs  = 00000023
  SegFs  = 00000038  SegGs  = 00000000
  Dr0    = 0012F098  Dr3    = FFFF8000
  Dr1    = 020D6019  Dr6    = 40D5A910
  Dr2    = 00000000  Dr7    = 00000000
Error executing link.exe.
Tool execution canceled by user.是不是atl object的数量在vc中有限制?哪位专家可以帮帮我?或者替我向m$问一下是怎么回事?(不好意思,我用的是D版的vc,所以不敢直接找m$)

解决方案 »

  1.   

    atl object跟普通的class一样,是可以展开的透明的C++ Code组成,没有限制,可以一直增多直到那些全局变量以及静态变量多到堆栈溢出为止,但是这是运行期的错误,而且肯定远远大于几百这个数量级。
    我想你可能是项目有什么损坏,因为项目太大,VC6也有很多bug,项目越大它出现的几率也越大。你可以尝试用手工添加看看。
    另外,一个Project几百个Atl Object还不好,因为这样可能导致那个全局的_ATL_OBJMAP_ENTRY数组达到几百上千个,这样每次DllGetClassObject的时候都要遍历这个表会影响效率。如果你仅仅只用了一个idl文件,那样如果你对idl稍微作一点改动,midl就需要重新编译这个idl文件,编译起来肯定会很烦。
      

  2.   

    to felics :
    vc6最新的补丁是什么?我已经打了sp5了to arxing(阿行) :
    呵呵,你终于出现了,为什么这么久不开msn呢?有空聊聊嘛:)
    我的项目文件应该是没有坏的。因为我在Release版可以正常编译,或者如果我手动删除了新添加的alt object后,Debug版又可以正常编译了。to arxing(阿行), Skt32(荒城之月), aoosang(智慧的鱼):
    我也觉得这样的设计是不太好。但是我有苦衷的。
    因为我的atl object的创建行为分成组建外部的创建和组件内部的创建,组件内部的创建是使用了一些优化的代码,可以简化创建过程。如果一个atl object的函数结果是创建另一个atl object,那我就会在函数处理过程中使用组件内部的创建方式来生成新的object。所以,如果我将现在的一个组件分割成几个组件,那么就要将所有atl object的内部创建函数导出,而我却不希望这些函数被外部用户使用。如果各位有什么更好的设计方案,请提出来,我会虚心听教的。(刚刚查看m$的源码,才知道dllGetClassObject果然如Arxing所说的,是使用遍历的方法,汗!!!为什么不使用哈希表呢?请问能改成使用哈希表的方式吗?)
      

  3.   

    最近眼睛受细菌感染,差点坏了,不能正常工作,现在好得差不多了吧。
        ATL使用遍历的方法主要是从代码复杂度以及速度方面权衡考虑,使用一个静态数组会始代码简单的多。而且纵观MS以及其它公司提供的几乎所有成熟组件,一般暴露出来的组件不会太多,接口也不会太多,每个接口的方法也不会太多,ADO,HTMLDocument等算是最复杂的组件了吧,可是它们的类型库包含的信息也远远少于你的这个项目,采用遍历的式是比较适合的。比较典型的还有窗口消息处理,QueryInterface(因此接口不会太多),以及我们常用的使用类型库的Invoke(因此接口的方法不会太多)等。
        如果你想改用别的算法,你只需要自己写一个DllGetClassObject。至于DllRegisterServer,DllUnregisterServer,使用频率不高,没什么必要改动。
        你不把某些组件暴露给客户,很简单,把OBJECT_ENTRY对应这个组件的宏去掉就可以,客户就没法获得类厂了。把idl里面的coclass也去掉,客户没法看到你的类型库包含这个组件。另外,把CComCoClass<>这个基类也去掉,因为你不需要使用CLSID了,如果你内部还要使用CLSID,就要手工添加一个CLSID变量。另外,对于接口,如果外部不需要使用的话,你也可以把它从idl抹去,然后手工写一个虚类代替。但是如果这个接口可能需要跨公寓使用的话,最经常遇到的是IInterface1::put_Interface2(IInterface2 *p),如果你自己不能保证线程安全的话,你还是把它从idl留着,以便产生调度代码。
        把idl文件分拆是必要的,在你现在这种情况下,编译速度可以增大几倍甚至几十倍。把很少改动的接口写到另一个idl文件里面,然后在主idl里面用import包含进来。把子idl以及其产生的_i.c包进主项目,_p.c包进ps项目,子idl文件最好不要产生类型库。
        至于debug为什么加不进class了,你先要检查相关的代码以及文件是否完整加进了项目(.h, .cpp, rc,idl以及OBJECT_ENTRY都要检查一下),如果这些都好的话,也许是dsp文件不允许这么大,也许是debug信息不能正确产生等等,总之不是class的关系,ATl Object跟普通的class是没有什么两样的,我尝试了往一个项目添加了1000个class没问题,但是我没法测试一个项目有几千个文件,太麻烦了。
      

  4.   

    to arxing(阿行):
    首先祝你的眼睛尽快康复!怎么受感染的,给我说说,让我等程序员平时也注意一下。有空晚上开开msn吧,大家聊聊。
    我想你对我说的情况有些误解了。我并不是要隐藏某些组件,而是要隐藏这些组件的内部创建过程,但是客户还是可以用CoCreateInstance来从组件外部来创建atl object的实例的。只不过我在组件内部直接使用new CComObject<MyObj>()来创建,而且优化了初始化实例的步骤。如果所有atl object都在一个组件中,那就无所谓隐藏了,但是如果要把现在的组件分拆成几个组件,那么这个内部的创建过程就要暴露出来,这是我不想见到的。请问有没有更好的拆分方案?你说的把idl文件分拆的办法,我没有做过,是否只是分拆idl文件,不分拆整个dll?我现在使用的办法是将一部分idl的内容发到.h文件中定义成macro,然后在idl文件中include这个.h文件,然后插入macro。不知道这个方法是不是和你的方法殊途同归?其实我也是觉得ATl Object跟普通的class是没有什么两样的,但是我遇到的现象使我很迷惘,因为当我多了一个atl object后Debug编译就是通不过,而少了一个后就可以了。然而Release编译就一直正常。我怀疑是因为我的类型库太庞大而复杂了,而Debug编译比Release编译会生成多很多调试的数据,所以超出了link时的符号表的数量限制,导致出错。我的atl object中有很多都是有很多的函数的,给些数据你参考一下,我现在的编译设置中/Zm 要设置成400才能够用,Debug编译出来的dll接近14M,Release编译才3M多一点。我的project中有接近1500个源文件吧。你说M$等公司所提供的组件接口数量都比较少,但是我现在做的是将一个c++的类库转换成com组件,而这个C++类库又是很庞大的,所以出现了我在组件中有300多个类接口的情况了。虽然接口类数量很多,但是我的条理自以为还是比较清晰的。我现在重新编译一次需要差不多1个小时,特别是在将所有cpp文件都generate code以后要等很长时间才出现link的提示,然后又是很长时间的等待才完成编译。不知道是为什么会这样?对vc编译设置熟悉的朋友可否给些优化的建议?
      

  5.   

    关于拆分组件,总有一些组件不相关的,它们就可以分类了。
    把idl文件包含到头文件,一点用都没有,因为idl文件没有发生任何本质的变化。如果把idl看作一个单元的话,你的结构是:idl1+idl2+idl3+...,本质上跟一个idl没有区别,所以只要稍微有一点改动,整个链就需要重新编译,但是如果你把idl拆分了,那就变成了idl1;idl2;idl3;...,例如idl2改动了,编译器察觉到idl1,idl3...等没有变化,所以不需要为它们作任何工作。再从c++编译链接上考虑,每个idl单元总是生成一个.c文件,它们都各自编译成一个个单独的obj,如果你只有一个idl单元的话,每次改动都需要重新编译这个.c文件,但是分成多个,只需要编译改动了c文件就行了。其实idl编译是最慢的,c,c++的编译对比之下微不足道,所以仅前面的原因就足够让你产生理由拆分idl了,一般来说,你现在编译需要一个小时,其实按照你的项目这个规模,你完全可以把时间控制在10分钟之内。心动了吧?
      

  6.   

    to arxing(阿行) :
    不是很明白你说的idl的拆分是怎样的做法,是不是一个project中包含多个idl文件?还是要拆分成多个project?能不能再详细说说?不知道你说的“idl编译是最慢的”是指那段时间,但是我如果重新编译project,一开始应该就是编译idl文件吧,这个阶段倒很快,一分钟内搞定。慢的是后面的cpp文件的编译(但也不是花最长时间的部分),最慢的部分就是我上面说的,在将所有cpp文件都generate code以后要等很长时间才出现linking......的提示,这段时间没有任何进度信息的输出,不知道编译器在做什么,然后又是很长时间的等待才完成编译。所以我觉得最麻烦的不是重新编译idl文件,而是因为每增加一个atl object所导致的idl文件的改动,需要重新编译idl文件并且引起重新编译project。
      

  7.   

    就是一个项目包含多个idl文件。
    一般情况下,cpp文件是独立开来的,一个cpp改动,另一个cpp也同样跟着改动,假设你有一个CYourObject的class,为了使用idl编译出来的interface结构,你的YourClass.h文件必然包含YourProject.h(它就是由idl编译产生的),假设你的idl文件改动了,YoureProject.h必然跟着改变,所以,所有的ATLObject相关的cpp文件以及所有包含了YourProject.h的cpp都要跟着改变,因此它们都要重新编译。因此它们都属于idl编译部分。如果你个idl分成各个不同的部分,情况就不一样了,
    #include "idl1.h"
    class object1 
    {
      ...
    }#include "idl2.h"
    class object2 
    {
      ...
    }如果你改变了idl1.idl,结果只是idl1.h改变,导致object1.cpp需要重新编译,但是idl2.h没有改变,所以object2.cpp不需要重新编译。
      

  8.   

    我一般把每个interface单独做成一个idl,每个coclass作成一个idl,这样编译速度达到很高,基本上跟一个没有idl的项目差不多。你自己在速度和改动的工作量之间权衡。
    对于interface之间互相访问的问题,idl跟c++差不多,支持先声明后实现。所以
    // idl1.idl
    interface _Interface2;
    interface _Interface1 : public IUnknown
    {
      HRESULT put_Interface2(IInterface2* p);
    }
    ......// idl2.idl
    #import "idl1.idl"
    interface _Interface2 : public IUnknown
    {
      HRESULT get_Interface1(IInterface **ppv);
    }
    这是允许的,所以你不用担心互相引用方面的问题。
      

  9.   

    to felics(felics) :
    1、可以肯定不是病毒的问题
    2、内存也是很充足的了256M
      

  10.   

    to arxing(阿行):
    你说的快速编译应该是使用增量编译才会很快吧?如果Rebuild All会不会很快?应该也是要很长时间才行吧?
      

  11.   

    rebuild当然失去意义,你仍然需要一个小时。
    把idl分开只能让你省略了一些不必要的重新编译。
      

  12.   

    csdn不是有很多微软的技术支持专家吗,能不能帮我问一下这个编译错误的问题啊?
      

  13.   

    arxing什么时候升星了?好久没在CSDN仔细看贴子了。
    我认为在组件内部用new生成组件实例的做法不好。我曾经这么干过,但是当组件改成COM+组件时,用new生成的实例好象不能具有COM+的支持。
      

  14.   

    to  horris(僧推月下门):
    我这里根本就不用考虑com+的需求。
      

  15.   

    能不能用VC7?反正你的VC6也是D版的.
    以前也出现过类似的编译错误, 扔给我一大堆寄存器内容. 好像是我使用<<符号连接一大串操作时出现的.我的解决方法是把它分开成多个语句.不过这个对你好像没有帮助.
    修改debug编译的设置有没有希望解决?
      

  16.   

    to chenzhen(雨辰) :
    不能用vc7,现在用到的lib不支持
    我修改过几处地方,好像都无效。
      

  17.   

    我劝你不要把精力放在如何解决这个问题上,
    而是多想想如何避免这个问题。
    无论如何,在一个Project里做这么多个COM组件是不可取的。
      

  18.   

    不可能,VC6的代码肯定可以移植到vc7上面。
      

  19.   

    to arxing(阿行) :
    我用到一些别的开发库,那些库不支持vc7
      

  20.   

    vc7只是个编译环境,怎么会有不支持vc7的说法呢?最多有vc7不支持什么什么的说法。但是vc6支持的,vc7都支持,如果有什么问题,只能是你的编译环境没有设好。
      

  21.   

    我并不是要隐藏某些组件,而是要隐藏这些组件的内部创建过程,但是客户还是可以用CoCreateInstance来从组件外部来创建atl object的实例的。只不过我在组件内部直接使用new CComObject<MyObj>()来创建,而且优化了初始化实例的步骤。>>不太明白这个创建逻辑,既而要隐藏这些组件的内部创建过程,那么,用户就
    不应该可以用CoCreateInstance来创建实例.
    例如一个TreeView是一个object, TreeView的Node是另外一个object
    ,那么TreeView应该可以直接被客户通过CoCreateInstance来创建,但是,
    我们应该隐藏TreeNode的CoCreateInstance过程, 而最好通过TreeView的
    一个方法来得到TreeNode的实例.例如:
    HRESULT TreeView::CreateNode([out,retval]TreeNode ** ppNode);
    如果是这样,TreeNode的IDL文件还是有必要的, 但是_ATL_OBJMAP_ENTRY
    中就没有必要包含TreeNode的GUID了,因为用户无法直接new出TreeNode对象.