C++讀取Excel的XLS文件的方法有很多,,但是也許就是因為方法太多,,大家在選擇的時候會很疑惑,。
由于前兩天要做導(dǎo)表工具,比較了常用的方法,,總結(jié)一下寫個短文,,
1. OLE的方式
這個大約是最常用的方式,這個方式其實啟動了一個EXCEL的進(jìn)程在背后讀寫EXCEL文件,,這個方式的最大好處是什么事情都能做,。包括設(shè)置EXCEL的格式,增加刪除Sheet,,讀寫單元格,,等等。功能幾乎是最全的,而且使用起來也不是特別的難,。
其基本方法都是使用導(dǎo)出的.h文件進(jìn)行OLE操作,,但是由于OLE的接口說明文檔不多,想非常完美的使用她們也不是太容易,,好在例子也很多,。
網(wǎng)上普遍認(rèn)為OLE速度慢,EXCEL的OLE讀寫方式也基本一樣,。但是讀取速度可以改進(jìn),,如果在讀取的加載整個Sheet的Range的全部數(shù)據(jù),而不是一個個單元格讀取,,那么速度還是相對不錯,。想想原理也很簡單,整體讀取減少了OLE的交互次數(shù),。OLE的寫入方式一般只能幾個進(jìn)行比較方便,,所以速度可能要快很多。
我自己的親身體會是,,一個EXCEL文件,,100多列的字段,如果采用一個個單元格的讀取方式,,1s大約3條左右的記錄,,如果整體讀取,速度可以提高幾十倍,。
OLE讀寫EXCEL方式功能很強大,,讀取速度還可以,但寫入速度不高,,當(dāng)然這個方式不可能移植的,,而且你必須安裝了EXCEL。
2.Basic EXCEL
方式
這是CodeProject上的一個推薦開源工程了,,
http://www./KB/office/BasicExcel.aspx
作者是基于EXCEL的文件格式進(jìn)行的處理。但是為什么叫Basic EXCEL呢,。他不支持很多東西,,公式,文件格式,,表格合并等(有人說中文支持也不好),,所以可以認(rèn)為他只支持最基本的EXCEL表格,我自己的嘗試是如果這個EXCEL文件有其他元素(公式,,格式等),,使用Basic
EXCEL讀取會失敗。
OLE讀寫EXCEL方式功能比較弱,由于是直接根據(jù)文件格式操作,,讀寫速度都不錯,,你也不需要按照EXCEL,另外這個方式是可以移植的,,但是有一些成本,,其代碼比較晦澀難懂,而且沒有注釋,,另外即使在Windows平臺上,,告警也很多。
3.Sourceforge
上的幾個EXCEL庫,。
Sourceforge 上有幾個開源的的EXCEL庫,,但是完善的不多,有的是為了PHP讀寫EXCEL準(zhǔn)備的,,包括libXLS,,XLSlib,SmartEXCEL等,。我下載了幾個實驗了一下,,在Widonws都沒有編譯成功。也罷了,。
4.ODBC的方式
這個親身沒有嘗試過,,但是按照原理,應(yīng)該只能讀寫,。速度嗎,,ODBC的速度本來就是出名的慢了。
http://www./document/viewdoc/?id=421
5.ADO的方式
ADO的方式聽說應(yīng)該就是使用OLEDB的方式,。和OLE的方式應(yīng)該沒有本質(zhì)區(qū)別,。我看了看例子也和OLE很像
6.LibXL
LibXL 是一個收費的EXCEL的庫。
http://www./
按照他的說明,,他可以不依賴EXCEL讀取XLS文件,。包括設(shè)置格式等??蠢硬僮鲬?yīng)該很簡單,。但是是否可以移植到Linux平臺,我估計難度也不小,。呵呵,。
由于要收費,沒有法子測試了,。
7.網(wǎng)上一些號稱不用OLE讀取EXCEL例子
初步看了一下,,這個應(yīng)該是網(wǎng)上探索EXCEL格式文檔的例子,。可以實際操作的方式不強,。
一,、直接通過ODBC讀、寫Excel表格文件
首先,,我們要明白的是,,VC是通過ODBC來訪問Excel表格的,也就是說,,VC將Excel表格,,當(dāng)作數(shù)據(jù)庫來處理。當(dāng)然了,,也可以通過讀以tab鍵隔開的文件來處理這樣的文件,,但是,我還是更加愿意用讀取數(shù)據(jù)庫的方式來訪問Excel表格,。
第二,,既然是數(shù)據(jù)庫,那么,,就需要建立一個與該庫對應(yīng)的dsn,,這個,而且,,在建立dsn之前,,首先要確定,已經(jīng)安裝了Excel的驅(qū)動,。
第三,,要訪問數(shù)據(jù)庫中的表格,就要先打開該表格,,如此,,就需要一個與之對應(yīng)的RecordSet
想要通過ODBC直接讀、寫Excel表格文件,,首先,,應(yīng)確保ODBC中已安裝有Excel表格文件的驅(qū)動"MICROSOFT EXCEL DRIVER (*.XLS)"。然后,,可根據(jù)下面步驟進(jìn)行:
1. 在StdAfx.h文件中加入:
#include <afxdb.h>
#include <odbcinst.h>
2. 通過ODBC直接創(chuàng)建Excel文件(暫定文件名:Demo.xls)
//創(chuàng)建并寫入Excel文件
void CRWExcel::WriteToExcel()
{
CDatabase database;
CString sDriver = "MICROSOFT EXCEL DRIVER (*.XLS)"; // Excel安裝驅(qū)動
CString sExcelFile = "c:\\demo.xls"; // 要建立的Excel文件
CString sSql;
TRY
{
// 創(chuàng)建進(jìn)行存取的字符串
sSql.Format("DRIVER={%s};DSN='';FIRSTROWHASNAMES=1;READONLY=FALSE;CREATE_DB=\"%s\";DBQ=%s",sDriver, sExcelFile, sExcelFile);
// 創(chuàng)建數(shù)據(jù)庫 (既Excel表格文件)
if( database.OpenEx(sSql,CDatabase::noOdbcDialog) )
{
// 創(chuàng)建表結(jié)構(gòu)(姓名,、年齡)
sSql = "CREATE TABLE demo (Name TEXT,Age NUMBER)";
database.ExecuteSQL(sSql);
// 插入數(shù)值
sSql = "INSERT INTO demo (Name,Age) VALUES ('徐景周',26)";
database.ExecuteSQL(sSql);
sSql = "INSERT INTO demo (Name,Age) VALUES ('徐志慧',22)";
database.ExecuteSQL(sSql);
sSql = "INSERT INTO demo (Name,Age) VALUES ('郭徽',27)";
database.ExecuteSQL(sSql);
//sSql =L"INSERT INTO Grade (序號,射殺方,被射殺方,時間)
VALUES ('" + mStr[0] + L"','" + mStr[1]+L"','" + mStr[2] +L"','" + mStr[3]+L"')"; mStr[0]為字符串類型
}
// 關(guān)閉數(shù)據(jù)庫
database.Close();
}
CATCH_ALL(e)
{
TRACE1("Excel驅(qū)動沒有安裝: %s",sDriver);
}
END_CATCH_ALL;
}
3. 通過ODBC直接讀取Excel文件(暫定文件名:Demo.xls)
// 讀取Excel文件
void CRWExcel::ReadFromExcel()
{
CDatabase database;
CString sSql;
CString sItem1, sItem2;
CString sDriver;
CString sDsn;
CString sFile = "Demo.xls"; // 將被讀取的Excel文件名
// 檢索是否安裝有Excel驅(qū)動 "Microsoft Excel Driver (*.xls)"
sDriver = GetExcelDriver();
if (sDriver.IsEmpty())
{
// 沒有發(fā)現(xiàn)Excel驅(qū)動
AfxMessageBox("沒有安裝Excel驅(qū)動!");
return;
}
// 創(chuàng)建進(jìn)行存取的字符串
sDsn.Format("ODBC;DRIVER={%s};DSN='';DBQ=%s", sDriver, sFile);
TRY
{
// 打開數(shù)據(jù)庫(既Excel文件)
database.Open(NULL, false, false, sDsn);
CRecordset recset(&database);
// 設(shè)置讀取的查詢語句.注意此處表名的寫法,excel里的表默認(rèn)為系統(tǒng)表,,所以訪問表時表明應(yīng)為[表明$]
sSql = "SELECT Name, Age "
"FROM demo "
"ORDER BY Name ";
// 執(zhí)行查詢語句
recset.Open(CRecordset::forwardOnly, sSql, CRecordset::readOnly);
// 獲取查詢結(jié)果
while (!recset.IsEOF())
{
//讀取Excel內(nèi)部數(shù)值
recset.GetFieldValue("Name ", sItem1);
recset.GetFieldValue("Age", sItem2);
// 移到下一行
recset.MoveNext();
}
// 關(guān)閉數(shù)據(jù)庫
database.Close();
}
CATCH(CDBException, e)
{
// 數(shù)據(jù)庫操作產(chǎn)生異常時...
AfxMessageBox("數(shù)據(jù)庫錯誤: " + e->m_strError);
}
END_CATCH;
}
4. 獲取ODBC中Excel驅(qū)動函數(shù)
CString CRWExcel::GetExcelDriver()
{
char szBuf[2001];
WORD cbBufMax = 2000;
WORD cbBufOut;
char *pszBuf = szBuf;
CString sDriver;
// 獲取已安裝驅(qū)動的名稱(涵數(shù)在odbcinst.h里)
if (!SQLGetInstalledDrivers(szBuf, cbBufMax, &cbBufOut))
return "";
// 檢索已安裝的驅(qū)動是否有Excel...
do
{
if (strstr(pszBuf, "Excel") != 0)
{
//發(fā)現(xiàn) !
sDriver = CString(pszBuf);
break;
}
pszBuf = strchr(pszBuf, '\0') + 1;
}
while (pszBuf[1] != '\0');
return sDriver;
}
二、通過OLE/COM實現(xiàn)對Excel表格的操作
1,、添加OLE/COM支持,。
首先,應(yīng)用程序必須添加對OLE/COM的支持,才能導(dǎo)入OLE/COM組件,。
本文使用的是MFC對話框程序,,在創(chuàng)建工程的向?qū)е羞x中Automation選項即可為程序自動添加相應(yīng)的頭文件和OLE庫初始化代碼。
通過查看源代碼,,可以知道在stdafx.h的頭文件中,,添加了OLE/COM很多類所需添加的頭文件。
#include // MFC
自動化類
同時,,在應(yīng)用程序類的InitInstance函數(shù)中,,添加了OLE/COM的初始化代碼,如下所示:
// 初始化 OLE
庫
if (!AfxOleInit())
{
AfxMessageBox(IDP_OLE_INIT_FAILED);
return FALSE;
}
2,、導(dǎo)入并封裝Excel中的接口
Excel作為OLE/COM庫插件,,定義好了各類交互的接口,這些接口是跨語言的接口,。VC可以通過導(dǎo)入這些接口,,并通過接口來對Excel的操作。
由于本文只關(guān)心對Excel表格中的數(shù)據(jù)的讀取,,主要關(guān)注幾個_Application,、Workbooks、_Workbook,、Worksheets,、_Worksheet、Range等幾個接口,。Excel的各類接口的屬性,、方法可以通過MSDN的OfficeDevelopment進(jìn)行查詢。
VS2010導(dǎo)入OLE/COM組件的接口的步驟為:Project->Class Wizard->Add Class->MFC Class From TypeLib,,先選擇要導(dǎo)入的組件所在的路徑,,即Excel.exe所在的路徑,然后再選擇
要導(dǎo)入的Excel類型庫中的接口,。
在完成接口導(dǎo)入后,,VS2010將自動為導(dǎo)入的接口創(chuàng)建相應(yīng)的實現(xiàn)類,用于對接口屬性和方法的實現(xiàn),。由于標(biāo)準(zhǔn)的C++沒有屬性訪問器,,只能添加一個兩個存取函數(shù)來實現(xiàn)對屬性的訪問,通過在屬性名稱前加上get_和put_前綴分別實現(xiàn)對屬性的讀寫操作,。即,,由VC自動完成C++類對接口的封裝。
本文所導(dǎo)入的接口對應(yīng)的類和頭文件的說明如下所示
3,、導(dǎo)入Excel的整個類型庫
接口對應(yīng)類只是對接口的屬性和方法進(jìn)行了封裝,,而Excel中的數(shù)據(jù)類型,,如枚舉類型卻并為并不能使用,因此,,為了更方便的操作Excel,,還需要導(dǎo)入Excel的數(shù)據(jù)類型。
通過查看導(dǎo)入接口對應(yīng)的頭文件可以發(fā)現(xiàn),,在所有導(dǎo)入接口的頭文件中,,都會有這么行:
#import"D:\\Program Files\\Microsoft Office\\Office12\\EXCEL.EXE"no_namespace
這行代碼的作用是導(dǎo)入Excel整個類型庫到工程中。
由VS2010自動產(chǎn)生的導(dǎo)入代碼存在以下幾個問題:
(1)如果導(dǎo)入了多個接口,,每個頭文件都會把類型庫導(dǎo)入一次,,如果引用多個頭文件,會導(dǎo)致類型庫重復(fù)導(dǎo)入,。
(2)Excel類型庫中有些類型會跟MFC類庫的某些類型沖突,。
(3)Excel類型庫的某些類型跟其他Office和VB的某些庫相關(guān),如果不導(dǎo)入相關(guān)庫,,將導(dǎo)致這些類型無法使用,。。
以上三點問題的解決方法如下:
(1)僅在_Application接口對應(yīng)頭文件中導(dǎo)入Excel類型庫,。
(2)對沖突的類型進(jìn)行重命名,。
(3)在導(dǎo)入Excel類型庫之前,先導(dǎo)入Office和VB的相關(guān)庫,。
更改后的導(dǎo)入類型庫的代碼如下:
/*導(dǎo)入Office的類型庫*/
#import"C:\\Program Files\\Common Files\\MicrosoftShared\\OFFICE12\\MSO.DLL" \
rename("RGB","MSORGB") \
rename("DocumentProperties","MSODocumentProperties")
usingnamespace Office;
/*導(dǎo)入VB的類型庫*/
#import"C:\\Program Files\\Common Files\\Microsoft Shared\\VBA\\VBA6\\VBE6EXT.OLB"
usingnamespace VBIDE;
/*導(dǎo)入Excel的類型庫*/
#import"D:\\Program Files\\Microsoft Office\\Office12\\EXCEL.EXE" \
rename("DialogBox","ExcelDialogBox") \
rename("RGB","ExcelRGB") \
rename("CopyFile","ExcelCopyFile") \
rename("ReplaceText","ExcelReplaceText") \
no_auto_exclude
Using namespace Excel;
編譯程序后,,會在Debug或Release目錄下生成三個文件mso.tlh、vbe6ext.tlh和excel.tlh,。通過打開文件可知,,該三個文件的命名空間分別是Office、VBIDE和Excel,。導(dǎo)入了Excel的整個類型庫后,,就可以使用Excel中的所有類型了。
4,、操作Excel步驟
操作Excel的主要步驟如下:
(1)創(chuàng)建一個Excel應(yīng)用程序,。
(2)得到Workbook的容器。
(3)打開一個Workbook或者創(chuàng)建一個Workbook,。
(4)得到Workbook中的Worksheet的容器,。
(5)打開一個Worksheet或者創(chuàng)建一個WorkSheet。
(6)通過Range對WorkSheet中的單元格進(jìn)行讀寫操作,。
(7)保存Excel,。
(8)釋放資源。
5,、批量處理Excel表格
VC通過OLE/COM操作Excel,,是通過進(jìn)程間的組件技術(shù),。因此,,每次讀寫Excel中的單元格時,,都要進(jìn)行進(jìn)程間的切換。當(dāng)數(shù)據(jù)量大,,如果一個單元格一個單元格的讀取,,主要的時間都花費在進(jìn)程切換中。因此讀取多個單元格,,將可有效的提高程序的運行效率,。
對多個單元格的讀寫操作可以通過CRange中以下兩個成員函數(shù)來完成。
VARIANTget_Value2();
voidput_Value2(VARIANT& newValue);
其中,,輸入?yún)?shù)newValue只要輸入一個二維數(shù)組,,即可實現(xiàn)向Excel中一次寫入多個單元格的值。
其中,,VARIANT中實現(xiàn)二維數(shù)據(jù)的方法可參考
http://www.cnblogs.com/xianyunhe/archive/2011/09/13/2174703.html
當(dāng)然,,在對CRange類進(jìn)行操作之前,要設(shè)置CRange類對應(yīng)的單元格,。
6,、Excel表格的保存
(1)如果要保存打開的工作簿,使用CWorkbook類的Save函數(shù)就可以保存工作簿,,原文件將被覆蓋,。
(2)如果是新創(chuàng)建的工作簿,或者是要另存為,,可使用CWorkbook類的SaveAs函數(shù),。
SaveAs的參數(shù)比較多。其中,,第1個參數(shù)是設(shè)置要保存文件的路徑,;第2個參數(shù)是設(shè)置文件的格式,可在MSDN中查看枚舉類型XlFileFormat來了解Excel的文件格式,。經(jīng)過測試,,在本文所用的測試環(huán)境中,Excel2003的文件格式是xlExcel8,,Excel2007的文件格式是xlExcel4,。
7、獲取當(dāng)前Excel的版本
可以通過CApplication的get_Version函數(shù)來獲得Excel的版本,,其中,,Excel2007的主版本號是12,Excel2003的主版本號是11,。
8,、示例源代碼
主要代碼如下:
m_ListCtrl.SetExtendedStyle(LVS_REPORT |LVS_EX_FULLROWSELECT);
CApplication ExcelApp;
CWorkbooks books;
CWorkbook book;
CWorksheets sheets;
CWorksheet sheet;
CRange range;
LPDISPATCH lpDisp = NULL;
//創(chuàng)建Excel 服務(wù)器(啟動Excel)
if(!ExcelApp.CreateDispatch(_T("Excel.Application"),NULL))
{
AfxMessageBox(_T("啟動Excel服務(wù)器失敗!"));
return -1;
}
/*判斷當(dāng)前Excel的版本*/
CString strExcelVersion =ExcelApp.get_Version();
int iStart = 0;
strExcelVersion =strExcelVersion.Tokenize(_T("."), iStart);
if (_T("11") ==strExcelVersion)
{
AfxMessageBox(_T("當(dāng)前Excel的版本是2003,。"));
}
else if (_T("12") ==strExcelVersion)
{
AfxMessageBox(_T("當(dāng)前Excel的版本是2007。"));
}
else
{
AfxMessageBox(_T("當(dāng)前Excel的版本是其他版本,。"));
}
ExcelApp.put_Visible(TRUE);
ExcelApp.put_UserControl(FALSE);
/*得到工作簿容器*/
books.AttachDispatch(ExcelApp.get_Workbooks());
/*打開一個工作簿,,如不存在,則新增一個工作簿*/
CString strBookPath =_T("C:\\tmp.xls");
try
{
/*打開一個工作簿*/
lpDisp = books.Open(strBookPath,
vtMissing, vtMissing,vtMissing, vtMissing, vtMissing,
vtMissing, vtMissing,vtMissing, vtMissing, vtMissing,
vtMissing, vtMissing,vtMissing, vtMissing);
book.AttachDispatch(lpDisp);
}
catch(...)
{
/*增加一個新的工作簿*/
lpDisp = books.Add(vtMissing);
book.AttachDispatch(lpDisp);
}
/*得到工作簿中的Sheet的容器*/
sheets.AttachDispatch(book.get_Sheets());
/*打開一個Sheet,,如不存在,,就新增一個Sheet*/
CString strSheetName =_T("NewSheet");
try
{
/*打開一個已有的Sheet*/
lpDisp =sheets.get_Item(_variant_t(strSheetName));
sheet.AttachDispatch(lpDisp);
}
catch(...)
{
/*創(chuàng)建一個新的Sheet*/
lpDisp = sheets.Add(vtMissing, vtMissing, _variant_t((long)1), vtMissing);
sheet.AttachDispatch(lpDisp);
sheet.put_Name(strSheetName);
}
system("pause");
/*向Sheet中寫入多個單元格,規(guī)模為10*10
*/
lpDisp = sheet.get_Range(_variant_t("A1"),_variant_t("J10"));
range.AttachDispatch(lpDisp);
VARTYPE vt = VT_I4; /*數(shù)組元素的類型,long*/
SAFEARRAYBOUND sabWrite[2];
/*用于定義數(shù)組的維數(shù)和下標(biāo)的起始值*/
sabWrite[0].cElements =
10;
sabWrite[0].lLbound =
0;
sabWrite[1].cElements =
10;
sabWrite[1].lLbound =
0;
COleSafeArray olesaWrite;
olesaWrite.Create(vt, sizeof(sabWrite)/sizeof(SAFEARRAYBOUND), sabWrite);
/*通過指向數(shù)組的指針來對二維數(shù)組的元素進(jìn)行間接賦值*/
long (*pArray)[2]= NULL;
olesaWrite.AccessData((void **)&pArray);
memset(pArray, 0, sabWrite[0].cElements * sabWrite[1].cElements*
sizeof(long));
/*釋放指向數(shù)組的指針*/
olesaWrite.UnaccessData();
pArray = NULL;
/*對二維數(shù)組的元素進(jìn)行逐個賦值*/
long index[2]= {0,
0};
long lFirstLBound = 0;
long lFirstUBound = 0;
long lSecondLBound = 0;
long lSecondUBound = 0;
olesaWrite.GetLBound(1, &lFirstLBound);
olesaWrite.GetUBound(1, &lFirstUBound);
olesaWrite.GetLBound(2, &lSecondLBound);
olesaWrite.GetUBound(2, &lSecondUBound);
for (long i = lFirstLBound;i <= lFirstUBound; i++)
{
index[0] = i;
for (long j =lSecondLBound; j <= lSecondUBound; j++)
{
index[1] = j;
long lElement = i * sabWrite[1].cElements + j;
olesaWrite.PutElement(index, &lElement);
}
}
/*把ColesaWritefeArray變量轉(zhuǎn)換為VARIANT,并寫入到Excel表格中*/
VARIANT varWrite = (VARIANT)olesaWrite;
range.put_Value2(varWrite);
system("pause");
/*根據(jù)文件的后綴名選擇保存文件的格式*/
CString strSaveAsName = _T("C:\\new.xlsx");
CString strSuffix = strSaveAsName.Mid(strSaveAsName.ReverseFind(_T('.')));
XlFileFormat NewFileFormat = xlOpenXMLWorkbook;
if (0 ==strSuffix.CompareNoCase(_T(".xls")))
{
NewFileFormat= xlExcel8;
}
book.SaveAs(_variant_t(strSaveAsName), _variant_t((long)NewFileFormat),vtMissing, vtMissing, vtMissing,
vtMissing, 0, vtMissing, vtMissing, vtMissing,
vtMissing, vtMissing);
system("pause");
/*讀取Excel表中的多個單元格的值,,在listctrl中顯示*/
VARIANT varRead = range.get_Value2();
COleSafeArray olesaRead(varRead);
VARIANT varItem;
CString strItem;
lFirstLBound = 0;
lFirstUBound = 0;
lSecondLBound = 0;
lSecondUBound = 0;
olesaRead.GetLBound(1, &lFirstLBound);
olesaRead.GetUBound(1, &lFirstUBound);
olesaRead.GetLBound(2, &lSecondLBound);
olesaRead.GetUBound(2, &lSecondUBound);
memset(index, 0, 2*
sizeof(long));
m_ListCtrl.InsertColumn(0, _T(""),
0,100);
for (long j =lSecondLBound; j<= lSecondUBound; j++)
{
CString strColName = _T("");
strColName.Format(_T("%d"), j);
m_ListCtrl.InsertColumn(j, strColName, 0,
100);
}
for (long i =lFirstLBound; i <= lFirstUBound; i++)
{
CString strRowName = _T("");
strRowName.Format(_T("%d"), i);
m_ListCtrl.InsertItem(i-1, strRowName);
index[0] = i;
for (long j =lSecondLBound; j <= lSecondUBound; j++)
{
index[1] = j;
olesaRead.GetElement(index, &varItem);
switch (varItem.vt)
{
case VT_R8:
{
strItem.Format(_T("%d"), (int)varItem.dblVal);
}
case VT_BSTR:
{
strItem = varItem.bstrVal;
}
case VT_I4:
{
strItem.Format(_T("%ld"), (int)varItem.lVal);
}
default:
{
}
}
m_ListCtrl.SetItemText(i-1, j, strItem);
}
}
/*釋放資源*/
sheet.ReleaseDispatch();
sheets.ReleaseDispatch();
book.ReleaseDispatch();
books.ReleaseDispatch();
ExcelApp.Quit();
ExcelApp.ReleaseDispatch();
|