我在一个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我调用也没有问题。我现在正好是工程需要,请高手快快帮忙,跪谢啦
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我调用也没有问题。我现在正好是工程需要,请高手快快帮忙,跪谢啦
解决方案 »
- socket NAT
- PPStream编程问题
- 编辑框输入同步显示?
- D3D中鼠标拖拽模型的处理
- ●●●关于string与ClistBox的问题●●●
- 【1000分问题】求最短路径算法,最好有原代码!谢谢各位大侠帮忙,有现成的给俺传一份
- 急,在线等
- 在取消 "Minimize box" 属性的情况下, 如何使点击任务栏上的相应按钮图标后,原本正常显示的对话框最小化?
- 链接动态库出现的怪现象!!!
- 我在单位的主域服务器上用rc=SQLConnect(hdbc,server,SQL_NTS,uid,SQL_NTS,pwd,SQL_NTS);访问数据库,但是改成了独立管理器的形式就不能访问了。那是为什么?
- 如何用程序建立虚拟目录,非IIS的虚拟目录
- 我想用dll技术截获EN_CHANGE消息,请问该用CallWndProc还是GetMsgProc
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自动化的,呵呵
转帖 希望对你有用
在开发软件时,经常要将数据输出到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();
}
添加完以上程序后,可运行看结果。
{
// 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();
}
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的调用却出现了问题。
大家试一下,分数不够再加,谢谢。
请调试查看一下rg.GetItem返回的是否是一个接口指针
_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一次,仍然造成对象释放不完全。
这就是我从这个问题中了解到的,谢谢。