目的:通過vc++讀取和寫入excel
環境:vs2012 office2010
1.創建一個新的工程,選擇mfc application,選擇dialog,在advanced features選擇automation(這一項我不確定有沒有必要,查資料說要選上,我就選上了)
2.創建完工程後,在dialog對話框上右擊,選擇class wizard(或是ctrl+shift+x)
3.在add class下拉框內選擇add class from typelib
4.在add class from下面選擇file,然後你的office安裝目錄下的EXCEL.EXE(我的目錄是C:\Program Files (x86)\Microsoft Office\Office14\EXCEL.EXE)
5.在下面添加6個類到我們的工程,(_Application, Worksheets, _Worksheet, Workbooks, _Workbook, Range)注意有的有下劃線,別添加錯了
6.這個時候如果直接編譯程序會提示錯誤,大體錯誤的信息如下
Error 1 error C1083: Cannot open compiler generated file: 'd:\code\vc\exceltojson\exceltojson\debug\excel.tlh': Permission denied d:\code\vc\exceltojson\exceltojson\capplication.h 3 1 EXCELToJSON
2 IntelliSense: declaration modifiers are incompatible with previous declaration d:\code\VC\EXCELToJSON\EXCELToJSON\Debug\excel.tlh 573 19 EXCELToJSON
出現很多錯誤,幾乎都與excel.tlh這個文件相關。
解決方法就是把導入的6個類對應的頭文件最開始的一句話
#import "C:\\Program Files (x86)\\Microsoft Office\\Office14\\EXCEL.EXE" no_namespace
註釋掉,也就是刪掉。具體原因不明,估計是這個已經更新不用了,但是模版裏面沒有改。所以引用了沒有的東西出的錯
7.做完上面的操作,再編譯一下,可能還有錯誤,錯誤提示如下:
Error 2 error C2059: syntax error : ',' d:\code\vc\exceltojson\exceltojson\crange.h 336 1 EXCELToJSON
3 IntelliSense: expected a type specifier d:\code\VC\EXCELToJSON\EXCELToJSON\CRange.h 336 10 EXCELToJSON
解決方法,把Range這個類自動生成的頭文件(我這是CRage.h)裏面的
VARIANT DialogBox()
{
VARIANT result;
InvokeHelper(0xf5, DISPATCH_METHOD, VT_VARIANT, (void*)&result, NULL);
return result;
}
改成
VARIANT _DialogBox()
{
VARIANT result;
InvokeHelper(0xf5, DISPATCH_METHOD, VT_VARIANT, (void*)&result, NULL);
return result;
}
也就是前面加一個下劃線。具體原因不明,估計是新版本後有同名的函數
(PS: 汗,話說ms也真夠坑爹,自己的各種不兼容)
8.這個時候編譯一下,估計沒什麼問題了,那麼我們就需要寫代碼操作excel了。網上找的代碼都差不多,不過裏面有幾句初始化com的
::CoInitialize(NULL);
if(!AfxOleInit())
{
AfxMessageBox(L"cannot initialize com");
return 0;
}
::CoUninitialize();
在新版本的vs上會引起這個錯誤
Debug Assertion Failed!
Program: D:\code\VC\EXCELToJSON\Debug\EXCELToJSON.exe
File: f:\dd\vctools\vc7libs\ship\atlmfc\src\mfc\oleinit.cpp
Line: 49
For information on how your program can cause an assertion failure, see the Visual C++ documentation on asserts.
(Press Retry to debug the application)
這個錯誤提示更是個坑,主要原因是你的com組件初始化了兩次。是由AfxOleInit這個函數導致的。(PS:你說我初始化兩次不行,直說不就行了,整得跟真事似的,我哪來的f盤。真是無語。再說,這麼常見的問題,爲什麼不能有一個準確的解釋)
解決方法就是把這一段初始化的程序刪掉,不用了。爲什麼?你可以全局匹配一下AfxOleInit,你會發現在app class內(就是和你的dlg class同名,僅僅差了Dlg的一個創建工程是系統自建的類,比如我的EXCELToJSONDlg.cpp,我們的代碼大部分都是寫到這裏面,然後會有一個EXCELToJSON.cpp類。就是在這個類裏面),系統自建的代碼有了這個初始化的程序:
// Initialize OLE libraries
if (!AfxOleInit())
{
AfxMessageBox(IDP_OLE_INIT_FAILED);
return FALSE;
}
9.這樣應該沒什麼問題了,在你的程序內引入上面6個類的頭文件,然後就可以操作了。實例如下:
#include "stdafx.h"
#include "EXCELToJSON.h"
#include "EXCELToJSONDlg.h"
#include "DlgProxy.h"
#include "afxdialogex.h"
#include "CApplication.h"
#include "CRange.h"
#include "CWorkbook.h"
#include "CWorkbooks.h"
#include "CWorksheet.h"
#include "CWorksheets.h"
void CEXCELToJSONDlg::OnBnClickedButtonConvert()
{
// TODO: Add your control notification handler code here
CApplication app;
CRange range;
CWorkbook book;
CWorkbooks books;
CWorksheet sheet;
CWorksheets sheets;
LPDISPATCH lpdisp;
COleVariant vresult;
COleVariant covtrue((short)TRUE);
COleVariant covfalse((short)FALSE);
COleVariant covoptional((long)DISP_E_PARAMNOTFOUND, VT_ERROR);
//create the server
if(!app.CreateDispatch(L"Excel.Application"))
{
AfxMessageBox(L"Cannot start Excel server");
return;
}
app.put_Visible(FALSE);
books.AttachDispatch(app.get_Workbooks());
//open file
lpdisp = books.Open(
ExcelFile,
covoptional, covfalse, covoptional, covoptional, covoptional,
covoptional, covoptional, covoptional, covoptional, covoptional,
covoptional, covoptional, covoptional, covoptional);
//get workbook
book.AttachDispatch(lpdisp);
//get worksheets
sheets.AttachDispatch(book.get_Worksheets());
lpdisp = book.get_ActiveSheet();
sheet.AttachDispatch(lpdisp);
//get the used range
CRange usedrange;
usedrange.AttachDispatch(sheet.get_UsedRange());
//get used row
range.AttachDispatch(usedrange.get_Rows());
long irownum = range.get_Count();
long istartrow = usedrange.get_Row();
//get used column
range.AttachDispatch(usedrange.get_Columns());
long icolnum = range.get_Count();
long istartcol = usedrange.get_Column();
CString skey = L"";
CString svalue = L"";
for(int j=2; j<icolnum+1; j++)
{
JSON JSONObject;
range.AttachDispatch(usedrange.get_Item(COleVariant((long)1), COleVariant((long)j)).pdispVal);
vresult = range.get_Text();
JSONObject.LanguageTag = vresult.bstrVal;
for(int i=2; i<irownum+1; i++)
{
range.AttachDispatch(usedrange.get_Item(COleVariant((long)i), COleVariant((long)1)).pdispVal);
vresult = range.get_Text();
skey = vresult.bstrVal;
range.AttachDispatch(usedrange.get_Item(COleVariant((long)i), COleVariant((long)j)).pdispVal);
vresult = range.get_Text();
svalue = vresult.bstrVal;
}
}
double dvalue = 3;
usedrange.put_Item(COleVariant((long)2), COleVariant((long)2), COleVariant((double)dvalue));//write to excel
book.Save();
book.Close(covoptional, COleVariant(ExcelFile), covoptional);
books.Close();
app.Quit();
}
上面代碼是打開一個指定路徑的excel,然後獲得使用的sheet(也就是你最後一次保存退出的那個sheet,這個你也可以通過裏面的其他方法獲得excel裏面的其他sheet),然後獲得這個sheet內使用過的區域,並遍歷獲取其數據,後面一點是向指定的地方插入一個數據。在vs2012下,裏面的一些函數名字變了,setItem 改成put_Item等,你可以在提示的函數內找一下,看名字,應該能猜出來
xlsx文件其實是一個zip的壓縮包,你可以改其後綴名然後解壓,裏面都是一些xml文件和一些圖片等配置文件。如果你有時間,可以自己解碼excel文件。