我在一个MFC工程里导入excel9的类型库我想从某一格读出内容,代码如下
IDispatch *pDisp=NULL;::CoGetObject(L"c:\\a.xls",NULL,IID_IDispatch,(void ** )&pDisp);
//打开c:\a.xls_Workbook wb(pDisp);      /////workbook
Worksheets wss(wb.GetSheets());   //////////sheets
_Worksheet ws(wss.GetItem(COleVariant((short)1)));   /////////sheet
Range rg(ws.GetCells());    /////////////cells
_bstr_t bs(rg.GetItem(_variant_t((short)1),_variant_t((short)2)));
/////////读取1行2列,问题发生点
wb.Close(covFalse,vtMissing,vtMissing); //关闭workbook,错误发生点
运行这段代码,在wb.Close的时候Excel服务器进程会报地址非法的错误,并且Excel进程将不会自动关闭,这时候 只能手动关闭Excel.exe进程了。
测试发现问题发生在rg.GetItem,只要调用这个方法,Excel进程就不能正常关闭,而用rg.SetItem就不会有这种情况。调用代码应该不会产生内存泄漏,那几个Excel对象都由MFC包装,都会自动释放的,由rg.GetItem返回的BSTR也应该由_bstr_t自动释放。我找了很多文章,好像基本没看到有用GetItem的例子,都是从其他地方导入到Excel,也就是只用SetItem,SetItem我调用也没有问题。我现在正好是工程需要,请高手快快帮忙,跪谢啦

解决方案 »

  1.   

    看看我的GetItem
    CString str;
    if(m_bIsActived){
    _variant_t varX,varY,var;
    varX = (long)column;
    varY = (long)row;
    var = m_pWorksheet->Cells->GetItem(varY,varX);
    str = (LPSTR)(_bstr_t)var; 
                      m_pRange  = var.pdispVal;
    }
    return str;
    这几天回答得比较多Excel自动化的,呵呵
      

  2.   

    在Visual C++ 中调用Excel 2000 
    转帖  希望对你有用
     
       在开发软件时,经常要将数据输出到Excel 2000中,在Excel 2000中对该数据进行进一步地格式化处理或进行计算处理。在Visual Basic中处理起来较简单,Excel 2000的VB编程帮助中有较为详细的介绍。在Visual C++中如何进行处理了?利用Excel 2000的ActiveX Automate功能,处理起来同VB中类似。但要注意以下几点: 
    对于对象的属性值的读取或赋值,需要用GetProperty()或SetProperty(NewValue)函数,不能象VB中直接通过属性名称取值或赋值。例如:Worksheet.GetCount(), Worksheet.SetName(“Sheet1”)。 
    对集合对象中的成员对象的引用,必须使用集合对象的GetItem()函数。例如:Worksheets.GetItem(ColeVariant((long)1))或Worksheets.GetItem(ColeVariant(“Sheet1”))取得第一个工作表。 
    在COM接口中,时常用到Variant,BSTR,SafeArray数据类型。Variant数据类型是一个联合,可表示几乎所有的类型的数据,具体用法见MSDN中的相关介绍,类_variant_t是对VARIANT数据类型的封装。在Excel 2000的VB编程帮助中,如果提到某函数或属性需要一个值,该值的数据类型通常是Variant,在封装Excel 2000对象的类定义中,说明了具体需要的数据类型。BSTR是一个包括了字符串和字符串长度的数据结构,类_bstr_t是对BSTR数据类型的封装。在Excel 2000的VB编程帮助中提到的字符串通常指BSTR。具体函数参数或属性的数据类型,见封装该对象的类的定义。SafeArray是一个包括数组和数组边界的结构,数组边界外的内容不允许访问。在Excel 2000的VB编程帮助中提到的数组是指SafeArray。关于SafeArray的处理,请见MSDN的相关帮助。 
    对于缺省参数和缺省值。在VB中,函数的参数可以空缺,在VC++中不允许,必须将所有的参数填写完全。如果你希望指定某个参数为缺省值,根据参数数据类型的不同,可指定不同的缺省值。当参数数据类型为字符串时,可以用长度为0的字符串。如果参数是Variant类型,可用常量vtMissing,该常量在comdef.h中定义。也可用_variant_t(DISP_E_PARAMNOTFOUND, VT_ERROR)产生一个Variant对象。 
    Excel对象中的集合对象有时包括的子对象是不一定的,例如:Range对象,可以表示Cell的集合,也可以表示Column的集合或Row的集合,Range.GetItem(1)可以返回Cell或Column或Row对象。 
    对对象的引用或传递对象,使用IDispatch类对象,有时利用Variant对IDispatch进行包装。 
    以下是一段源程序,演示如何启动Excel 2000,利用一个模板文件产生一个新文档,在该文档的”Sheet1”工作表的第一个单元中填写一段文字,设置第一列的列宽,然后调用一个模板中的宏,执行一段程序,最后打印预览该Excel文档。模板文件名称:MyTemplate.xlt。程序在Visual C++ 6.0 sp4,Windows 2000 Professional sp-1下调试通过。 
    首先利用Visual C++ 6.0,建立一个MFC基于对话框的工程项目,共享DLL,Win32平台。工程名称ExcelTest。在主对话框中加入一个按钮, 
    ID IDC_EXCELTEST 
    Caption Test Excel 
    双击该按钮,增加成员函数void CExcelTestDlg::OnExceltest()。 
    在BOOL CExcelTestApp::InitInstance()中,dlg.DoModal();之前增加代码: 
    if (CoInitialize(NULL)!=0) 

    AfxMessageBox("初始化COM支持库失败!"); 
    exit(1); 

    在return FALSE; 语句前,加入: 
    CoUninitialize(); 
    选择Menu->View->ClassWizade,打开ClassWizade窗口,选择Add Class->From a type library,选择D:\Program Files\Microsoft Office\office\Excel9.OLB(D:\Program Files\Microsoft Office\是本机上Microsoft Office 2000的安装目录,可根据个人机器上的实际安装目录修改)。选择_Application、Workbooks、_Workbook、Worksheets、_Worksheet、Range,加入新类,分别为_Application、Workbooks、_Workbook、Worksheets、_Worksheet、Range,头文件Excel9.h,源文件Excel9.cpp。 
    在ExcelTestDlg.cpp文件的头部,#include "ExcelTestDlg.h"语句之下,增加 : 
    #include "comdef.h" 
    #include "Excel9.h" 
    在void CExcelTestDlg::OnExceltest() 函数中增加如下代码: 
    void CExcelTestDlg::OnExceltest()  

    _Application ExcelApp; 
    Workbooks wbsMyBooks; 
    _Workbook wbMyBook; 
    Worksheets wssMysheets; 
    _Worksheet wsMysheet; 
    Range rgMyRge; 
    //创建Excel 2000服务器(启动Excel) 
     
    if (!ExcelApp.CreateDispatch("Excel.Application",NULL)) 

    AfxMessageBox("创建Excel服务失败!"); 
    exit(1); 

    //利用模板文件建立新文档 
    wbsMyBooks.AttachDispatch(ExcelApp.GetWorkbooks(),true); 
    wbMyBook.AttachDispatch(wbsMyBooks.Add(_variant_t("g:\\exceltest\\MyTemplate.xlt"))); 
    //得到Worksheets 
    wssMysheets.AttachDispatch(wbMyBook.GetWorksheets(),true); 
    //得到sheet1 
    wsMysheet.AttachDispatch(wssMysheets.GetItem(_variant_t("sheet1")),true); 
    //得到全部Cells,此时,rgMyRge是cells的集合 
    rgMyRge.AttachDispatch(wsMysheet.GetCells(),true); 
    //设置1行1列的单元的值 
    rgMyRge.SetItem(_variant_t((long)1),_variant_t((long)1),_variant_t("This Is A Excel Test Program!")); 
    //得到所有的列 
    rgMyRge.AttachDispatch(wsMysheet.GetColumns(),true); 
    //得到第一列 
    rgMyRge.AttachDispatch(rgMyRge.GetItem(_variant_t((long)1),vtMissing).pdispVal,true); 
    //设置列宽 
    rgMyRge.SetColumnWidth(_variant_t((long)200)); 
    //调用模板中预先存放的宏 
    ExcelApp.Run(_variant_t("CopyRow"),_variant_t((long)10),vtMissing,vtMissing, 
    vtMissing,vtMissing,vtMissing,vtMissing,vtMissing,vtMissing,vtMissing, 
    vtMissing,vtMissing,vtMissing,vtMissing,vtMissing,vtMissing,vtMissing, 
    vtMissing,vtMissing,vtMissing,vtMissing,vtMissing,vtMissing,vtMissing, 
    vtMissing,vtMissing,vtMissing,vtMissing,vtMissing,vtMissing); 
    //打印预览 
    wbMyBook.SetSaved(true); 
    ExcelApp.SetVisible(true); 
    wbMyBook.PrintPreview(_variant_t(false)); 
    //释放对象 
    rgMyRge.ReleaseDispatch(); 
    wsMysheet.ReleaseDispatch(); 
    wssMysheets.ReleaseDispatch(); 
    wbMyBook.ReleaseDispatch(); 
    wbsMyBooks.ReleaseDispatch(); 
    ExcelApp.ReleaseDispatch(); 

    添加完以上程序后,可运行看结果。
      

  3.   

    hahu(地痞 -- 勿近)用的是#import导入的类型库吧, 采用VTable进行调用的,我试过了,这么调用是没有问题的,但如果用MFC导入的话采用的是自动化接口就会出现我说的问题,用Quit也是没有用的,反正如果调用过Range的GetItem的话,再调用workbook的close就会出错,如果程序不调用close只是释放所有的对象,就如 coolaka(zhengyb)的例子,但是在调用Range的GetItem后,Excel.exe进程仍然不会被释放掉。大家最好把我的例子试一下。
      

  4.   

    楼主不用再试了!Excel进程无法退出,我三年以前就碰到了这个问题,找了公司好多高手都没有搞定,最后只好换用了别的方法,你等会儿我给你翻翻代码。
      

  5.   

    找到了,你从HtmlView继承并生成ExcelView类,然后在这个类里面添加处理函数就可以了。void CExcelView::OnOleReadcontents(CString SourcePath, CString TargetPath, CStringArray &RecNames, CStringArray *RecContents, CString RecFlag, BOOL bDelEmptyLine)
    {
    // OLE Variant for Optional.
    COleVariant VOptional((long)DISP_E_PARAMNOTFOUND, VT_ERROR);

    _Application objApp;
    _Workbook objBook;
    Workbooks objBooks;
    Worksheets objSheets;
    _Worksheet objSheet;
    Comments comments;
    Comment comment;
    Range objRange;
    VARIANT ret;

    // Instantiate Excel and open an existing workbook.
    objApp.CreateDispatch("Excel.Application");
    objBooks = objApp.GetWorkbooks();
    objBook = objBooks.Open(SourcePath,
    VOptional, VOptional, VOptional, VOptional,
    VOptional, VOptional, VOptional, VOptional,
    VOptional, VOptional, VOptional, VOptional);
    objSheets = objBook.GetWorksheets();
    int SheetsCount=objSheets.GetCount();//这里获得了sheet的数目,这里应当有一个循环,读取所有的sheet

    FILE *fp;
    if( access(TargetPath,0)==-1 )
    fp=fopen(TargetPath,"wt");
    else
    fp=fopen(TargetPath,"wt");

    for(int sheet=1;sheet<=SheetsCount;sheet++){//注意:从1开始。
    objSheet = objSheets.GetItem(COleVariant((short)sheet));--这里内容有删减,挑着看方法即可-----------------------------------------------
    fprintf(fp,"\n");
    objRange = objSheet.GetUsedRange();//这里获得所有的已用的Cells
    //objRange = objSheet.GetRange(COleVariant("A1"), COleVariant("F50"));
    ret = objRange.GetValue();
    if(ret.vt == VT_EMPTY) continue;
    //Create the SAFEARRAY from the VARIANT ret.
    COleSafeArray sa(ret);

    //Determine the array's dimensions.
    long lNumRows;
    long lNumCols;
    sa.GetUBound(1, &lNumRows);
    sa.GetUBound(2, &lNumCols);

    //Display the elements in the SAFEARRAY.
    long index[2];
    VARIANT val;
    int r, c;
    for(r=1;r<=lNumRows;r++)
    {
    CString contents;
    bool bEmpty=true;
    for(c=1;c<=lNumCols;c++)
    {
    index[0]=r;
    index[1]=c;
    sa.GetElement(index, &val);
    switch(val.vt)
    {
    case VT_BOOL:
    {
    bool flag=val.boolVal;
    if(flag==true) contents+="TRUE   ";
    else contents+="FALSE    ";
    bEmpty=false;
    break;
    }
    case VT_R4://VT_UI4://unsigned long
    {
    unsigned long value=val.ulVal;//.fltVal;//.ulVal;
    char buffer[30];
    _ui64toa(value,buffer,10);
    contents+=buffer;
    bEmpty=false;
    contents+="    ";//tab
    break;
    }
    case VT_R8://数字
    {
    //fprintf(fp,"%1.2f",val.dblVal);
    float value=val.dblVal;
    char buffer[20];
    //_ui64toa(value,buffer,10);
    sprintf(buffer,"%f",value);
    CString tmp=buffer;
    int dot=tmp.Find('.');
    CString Left=tmp.Left(dot);
    tmp=tmp.Mid(dot+1);
    if(tmp=="000000") contents+=Left;
    else contents+=buffer;
    //contents+=(CString)value;
    bEmpty=false;
    //if(1==c) Ar=contents.GetLength();
    contents+="    ";//tab
    break;
    }
    case VT_BSTR://文字
    {
    contents+=(CString)val.bstrVal;
    //if(1==c) Ar=contents.GetLength();
    contents+="    ";//tab
    bEmpty=false;
    break;
    }
    case VT_EMPTY://思想:如果本Cell为空,那么就去找它同一列上一行的不为空的cell的数据,直到找到或者到了第一行,取出这个数据的长度,据此进行格式控制
    {//这段代码可以根据用户的实际需要删掉,即:不进行格式控制。
    int iTmp=r;
    while(iTmp>1){
    int Ar=0;//That means: Column A, Row r;用来记录上一行的第一个Cell的字符长度。
    iTmp=iTmp-1;
    index[0]=iTmp;
    index[1]=c;
    sa.GetElement(index, &val);
    switch(val.vt)
    {
    case VT_R8://数字
    {
    float value=val.dblVal;
    char buffer[20];
    itoa(value,buffer,10);
    CString str=buffer;
    Ar=str.GetLength();
    break;
    }
    case VT_BSTR://文字
    {
    CString str=(CString)val.bstrVal;
    Ar=str.GetLength();
    break;
    }
    }
    if(Ar!=0) {
    for (int k=0;k<Ar;k++)
    contents+=' ';//空格
    contents+="    ";//tab
    break;//跳出while
    }
    }
    break;
    }//end of case Empty
    }//end of switch
    }//end of col
    if(fp){
    if(!contents.IsEmpty() && !bEmpty)
    {
    fprintf(fp,"%s",contents);
    fprintf(fp,"\n");
    }
    else if(!bDelEmptyLine) fprintf(fp,"\n");
    }
    }-------------------------------------------------
        if(fp) fclose(fp);  
    //在这里退出excel,没有问题。
    objBook.Close(COleVariant((short)FALSE), VOptional, VOptional);
    objApp.Quit();   
    }
      

  6.   

    http://support.microsoft.com/default.aspx?scid=http://support.microsoft.com:80/support/kb/articles/q192/3/48.asp&NoWebContent=1
      

  7.   

    谢谢楼上各位朋友,待会儿分数一起给。但是还是想不通 ,根据lixiaosan(小三) 朋友提供的资料,如果Excel被显示的话就会出现问题,而我并没有显示Excel。只是读取了单元格的内容。我用VB写了一个调用原理上一样的代码,却没有任何问题:
        Set workbook = GetObject("c:\a.xls")
        Set sheets = workbook.sheets
        Set sheet = sheets.Item(1)
        Set range = sheet.cells
        s = range.Item(1, 2)
        workbook.Close False
    由于没有声明对象,所以也是用Dispatch进行调用。看看VC的代码IDispatch *pDisp=NULL;
    ::CoGetObject(L"c:\\a.xls",NULL,IID_IDispatch,(void ** )&pDisp);
    _Workbook wb(pDisp); 
    Worksheets wss(wb.GetSheets());   
    _Worksheet ws(wss.GetItem(COleVariant((short)1)));   
    Range rg(ws.GetCells());    
    _bstr_t bs(rg.GetItem(_variant_t((short)1),_variant_t((short)2)));
    wb.Close(covFalse,vtMissing,vtMissing); 这两个代码应该调用方式是相同的,但VC的调用却出现了问题。
    大家试一下,分数不够再加,谢谢。
      

  8.   

    机子没装Excel,无法测试楼主的代码,猜测也许rg.GetItem返回的并不是一个VT_BSTR类型的VARIANT,可能是一个VT_DISPATCH类型的VARIANT,如果这样,楼主那样直接将其作为bs的构造参数将导致返回的IDispatch*无法Release,进而占用引用记数而无法关闭服务器。
    请调试查看一下rg.GetItem返回的是否是一个接口指针
      

  9.   

    lop5712(LOP) ,你说的太对了,真的是一个接口,但用_bstr_t居然也能取到字符串,跟踪一下代码先………………
      

  10.   

    问题已经解决了,谢谢各位分析一下自己的错误:首先用Range的GetItem取到的是一个VARIANT,内含IDispatch接口,我一直以为内含的是一个BSTR,所以我已一开始直接用
    _bstr_t bs(rg.GetItem(_variant_t((short)1),_variant_t((short)2)));
    来获取字符串(主要是的确能获得字符串),根据lop5712(LOP)的提醒,发现返回的VARIANT是接口并不是BSTR;看来_bstr_t这个类可以把IDispatch接口直接转换为字符串。跟踪代码发现,在给一个_bstr_t或COleVariant赋值VARIANT类型的时候,如果VARIANT不是VT_BSTR,这时候_bstr_t会调用OLE函数VariantChangeType进行类型转换,根据MSDN说明,如果转换是从一个IDispatch到BSTR,这时VariantChangeType会尝试接口的Value属性,而rg.GetItem返回的接口的确存在Value属性,因而_bstr_t和COleVariant能取到值。根据上述原因,我们应该释放VARIANT所含的接口,但我用#import 指令导入Excel库的时候,却没有碰到这个问题,看来两种方式的代码是有区别的。首先谈一下#import导入的情况:Range 的GetItem声明为:
    inline _variant_t Excel::Range::GetItem ( const _variant_t & RowIndex, const _variant_t & ColumnIndex ) {
        VARIANT _result;
        _com_dispatch_method(this, 0xaa, DISPATCH_PROPERTYGET, VT_VARIANT, (void*)&_result, 
            L"\x000c\x080c", &RowIndex, &ColumnIndex);
        return _variant_t(_result, false);
    }这种情况下返回的是_variant_t(_result, false);
    也就是说已经对返回的VARIANT用_variant_t包装了,而且重要的是第二个参数,第二个参数false告诉_variant_t包装VARIANT时仅仅把内容完整的复制过来,如果不含这个false(默认为true),_variant_t将采用OLE函数VariantCopy,根据MSDN,VariantCopy在碰到BSTR时会再分配一个空间,碰到接口类型时将对接口添加一个引用,所以_variant_t(_result, false)直接把返回值存起来,并在析构的时候自动释放内含的IDispatch。再来看一下用MFC导入的情况:
    声明如下:
    VARIANT Range::GetItem(const VARIANT& RowIndex, const VARIANT& ColumnIndex)
    这时候返回的是未经过包装的VARIANT,不会自动释放,需要手动完成。如果用_bstr_t bs(rg.GetItem(_variant_t((short)1),_variant_t((short)2)));
    将丢失返回的VARIANT,这就是我发生的错误。
    我看到有的网站上的例子是这样的:
    _variant_t vt(rg.GetItem(_variant_t((short)1),_variant_t((short)2)));

    _variant_t vt=rg.GetItem(_variant_t((short)1),_variant_t((short)2));
    这样做其实也是有问题的,虽然取到了VARIANT的值,但是不带false的_variant_t会添加一个引用,析构_variant_t只会Release一次,仍然造成对象释放不完全。
    这就是我从这个问题中了解到的,谢谢。