Click here to Skip to main content
Click here to Skip to main content

Accessing Excel Spreadsheets via C++

, 6 Oct 2006
Rate this:
Please Sign up or sign in to vote.
Demonstrates how to use WTL and C++ to access an Excel spreadsheet using Visual Studio .NET 2003.

WTL Excel

Introduction

This code demonstrates how to access Excel spreadsheets using C++, COM and ATL using Visual Studio .NET 2003 (v7.1).

Background

I'm an old school C++ programmer and much of my work revolves around using C++. Although I'm not opposed to using new methodologies like C# (and have in fact for other consulting gigs) I always like to come back to C++.

First, the reason why I needed to access Excel in the first place.

My company is developing a product for Windows 2000 and XP and needs to get its strings translated. The Powers-That-Be have decided that the files we'll be delivering to our translator will be Excel files, one per project. All well and good.

But I'm kind of lazy and I don't feel like maintaining by hand a spreadsheet littered with translation strings. I'd much rather write a program that extracts the strings (which reside in .xml files) and stuff them into the Excel spreadsheet. Well, the first part was easy. Just use the handy-dandy IXMLDOMDocument COM interface. Piece of cake.

Then I started exploring how to access Excel programmatically via C++.

Oh... my... God! The lack of documentation on this subject wasn't just skimpy. It was ludicrously non-existent. There were a few scant articles about how to use MFC to do it (don't try it, unless you like generating a billion little modules over the course of a few minutes while the MFC import wizard merrily generates IDispatch code wrappers for Excel's many interfaces). But I'd abandoned MFC long ago to worship at the altar of ATL and WTL, and never looked back.

After hours of search I managed to find one link that talked about where the various interfaces are stored for the various Excel versions. Since I have Office 2003 installed, I went with that one. (It's stored in the actual Excel.EXE binary, by the way).

"Great!" I said to myself. Just add an #import line to stdafx.h and I'm done.

#import "C:\\Program Files\\Microsoft Office\\OFFICE11\\EXCEL.EXE"

Yeah, right. Boy, was I in for disappointment. You get missing, duplicate, triplicate, googleplicate error messages, warnings, curses and general barking behavior from the compiler and/or linker. Time to roll up the sleeves and get ready to wrestle with include files and #import directives.

The first thing I noticed was that a lot of the missing classes had 'mso' prefixes. Hmmmm... Wonder if that means Microsoft Office? Sure enough, it appears that all of the Office products require the same MSO.DLL to perform a lot of their work. So add that in.

#import "C:\\Program Files\\Common Files\\Microsoft Shared\\OFFICE11\\MSO.DLL"

#import "C:\\Program Files\\Microsoft Office\\OFFICE11\\EXCEL.EXE"

Well, that helped a lot. But it was still barking about 'definitions' not having enough parameters. It seems that a lot of the properties, method names, etc. in Microsoft Office collide with standard C/C++ #define declarations. Fortunately, the Visual Studio hierophants over at Microsoft anticipated this and provided a mechanism for resolving these, namely the 'rename' option for the #import directive. It also appears that Office is deeply in bed with Visual Basic, because you need an #import for that as well.

After all was said and done, I finally came up with the magically alchemical formulae for including the COM interfaces of Excel into a C++ program.

#import "C:\\Program Files\\Common Files\\Microsoft Shared\\OFFICE11\\MSO.DLL" \
    rename( "RGB", "MSORGB" )

using namespace Office;

#import "C:\\Program Files\\Common Files\\Microsoft Shared\\VBA\\VBA6\\VBE6EXT.OLB"

using namespace VBIDE;

#import "C:\\Program Files\\Microsoft Office\\OFFICE11\\EXCEL.EXE" \
    rename( "DialogBox", "ExcelDialogBox" ) \
    rename( "RGB", "ExcelRGB" ) \
    rename( "CopyFile", "ExcelCopyFile" ) \
    rename( "ReplaceText", "ExcelReplaceText" )

Voila!

I tried getting fancy and including a 'using namespace Excel;' as well, but the compiler started making growling noises while pawing the ground and I decided to quit while I was ahead. I can type in a few 'Excel::' prefixes to keep things social.

Using the code

The application is a simple WTL dialog based applet. The dialog contains a single list-view control in report mode. It's compiled with Unicode so I don't have to worry about messy MBCS strings. All the code that interfaces with Excel is in the OnLoad( ) method that gets invoked when the user presses the "Load..." button.

The first thing this method does is nuke the items in the list control.

m_list.DeleteAllItems( );

while ( m_list.DeleteColumn( 0 ) );

Then it prompts the user to select the Excel file by using the WTL class CFileDialog. WTL, by the way, if you aren't altogether that familiar with it, is a truly barebones wrapper (with a few notable exceptions) around the Win32 API. Case in point. The CFileDialog takes a pointer to a series of null terminated strings that define the filters to help the user with the file selection process. These are somewhat of a pain to define on the fly so I wrote a simple class that takes a single string with vertical bars (|) that are placeholders for the null terminators. It has an operator LPCTSTR() method that returns a pointer to a string buffer with the vertical bars replaced with their null terminators.

I give to you the AFileFilter class.

class AFileFilter
{
public:
    AFileFilter( LPCTSTR pszFilter ) :
      m_strFilter( pszFilter ),
      m_pszFilter( NULL )
    {
        m_pszFilter = m_strFilter.GetBuffer( 0 );

        LPTSTR      psz = m_pszFilter;

        while ( *psz )
        {
            LPTSTR      pszNext = ::CharNext( psz );

            if ( *psz == _T('|') )
                *psz = _T('\0');

            psz = pszNext;
        }

        return;
    }

    virtual ~AFileFilter( )
    {
        m_strFilter.ReleaseBuffer( );

        return;
    }

public:
    operator LPCTSTR( ) const
    {
        return ( m_pszFilter );
    }

protected:
    CString     m_strFilter;
    LPTSTR      m_pszFilter;
};

Now to the meat of the method. First we need a pointer to the Excel application. If you run this in the debugger you'll notice a couple second pause when tracing through this code as Excel is loaded by COM. I took this to be a good sign the first time I debugged the program: I was actually invoking Excel! (I always step through lines of new code in the debugger the first time I run a program, don't you?)

Excel::_ApplicationPtr pApplication;

if ( FAILED( pApplication.CreateInstance( _T("Excel.Application") ) ) )
{
    Errorf( _T("Failed to initialize Excel::_Application!") );
    return;
}

The Errorf() method just allows me to quickly format a message with optional parameters on the fly for the user.

Next, we have to open the .xls file in Excel. This is done via the Workbooks property and its Open() method.

_variant_t  varOption( (long) DISP_E_PARAMNOTFOUND, VT_ERROR );

Excel::_WorkbookPtr pBook;

pBook = 
  pApplication->Workbooks->Open( dlgFile.m_szFileName, 
                                 varOption, varOption, varOption, varOption, 
                                 varOption, varOption, varOption, varOption, 
                                 varOption, varOption, varOption, varOption );

If you leave out even one of those varOption parameters, it won't work. Gee, I wish I knew what they all did.

Next I get the pointer to the first Sheet in the workbook. Naturally the index to the first Sheet is 1, not 0. (Remember: Office is in bed with Visual Basic. MPEG at 11mps on BitTorrent!)

Excel::_WorksheetPtr pSheet = pBook->Sheets->Item[ 1 ];

Phew! Finally, we can start sifting through the spreadsheet data. Pretty near as I can figure out, this is all done via the Range object. Now the Range object, if used in a scripted language like C# or Visual Basic is a bold, wonderful thing with playful interfaces and powerful methods. In C++, it's like arguing with Zeus while he's fingering his Aegis.

The basic method for using it, I found, was just to oversupply it with a range. I then used a hack by scanning for empty cells as an the end-of-column or end-of-row marker. I'm sure there are much more elegant ways of using this object in C++, which I carefully leave as an exercise for the student.

First thing I do is fetch the first row, which I'm assuming is the header row delineating all the column names.

Excel::RangePtr pRange = 
   pSheet->GetRange( _bstr_t( _T("A1") ), _bstr_t( _T("Z1" ) ) );

The code scans this row via the Item operator of the Range object. I go through a two-step process of first stuffing the cell contents into a _variant_t and then into a _bstr_t because I'm lazy and don't want to check the type of the _variant_t.

_variant_t  vItem = pRange->Item[ 1 ][ iColumn ];
_bstr_t     bstrText( vItem );

When the bstrText turns up empty, it's time to stop adding columns.

I then follow madness with lunacy and created a humdinger of an instance of a Range object.

pRange = pSheet->GetRange( _bstr_t( _T("A2") ), 
         _bstr_t( _T("Z16384" ) ) );

I use a simple nested loop to create the rows in the list-view control and set the column data.

At the end, I close the book with a VARIANT_FALSE to prevent any inadvertent changes from creeping into the .xls file.

pBook->Close( VARIANT_FALSE );

Finally, I Quit the Excel application. This is an important step because if you neglect to do it, Excel will be left floating in memory and worse yet, it will keep a lock on the file you opened above. Yeck!

pApplication->Quit( );

That's the lot! Have fun!

Points of Interest

Oh, yeah. What if you want to modify data in the spreadsheet? Well, that turns out to be quite simple. Just use the Item property as a left operand.

_bstr_t     bstrText( _T("Some text!") );
_variant_t  vItem( bstrText );
    
pRange->Item[ 5 ][ 1 ] = vItem;

Keep the C++ fires burning. They'll need us. They'll always need us.

History

First revision.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

Share

About the Author

Michael C. Robert
Web Developer
United States United States
No Biography provided

Comments and Discussions

 
QuestionDo you know if Excel 2013 still works? PinmemberWilliam C Bonner13-Feb-14 17:31 
AnswerRe: Do you know if Excel 2013 still works? PinmemberMember 219045711-Sep-14 7:49 
QuestionStill works, minor changes Pinmembermerano1-Jul-13 10:18 
QuestionBad way to access Excel PinmemberSergey Chepurin17-Jan-13 0:18 
This is a bad way to access Excel. When "compiler started making growling noises", it probably has a good reason. And preferring WTL to MFC in this case does not make things much better. Moreover, now after 7 years there are improvements around the Net like this one - How to interface with Excel in C++[^][] that simply does not compile. But people try and get angry.
AnswerRe: Bad way to access Excel PinmemberAndyUk0617-Apr-13 0:28 
GeneralRe: Bad way to access Excel PinmemberSergey Chepurin17-Apr-13 2:04 
QuestionOn the Mac? Any way of doing something similar? Pinmembertonyvsuk21-Dec-11 0:30 
Generalexcel add-in using c++ in cygwin PinmemberMember 787556826-Apr-11 19:08 
GeneralMy vote of 4 Pinmemberhuibenpaodechong13-Apr-11 18:16 
GeneralVersion Pinmembermurrine14-Feb-11 4:55 
GeneralBug: if there is a blank in the middle of the sheet PinmemberCharles Song6-Jul-10 2:59 
GeneralRead Date and time PinmemberMsmVc30-Sep-09 19:40 
GeneralSheetCalculate Event PinmemberNarVish29-Sep-09 3:30 
GeneralExcell Pinmembershamus9921-Jul-09 8:21 
GeneralNice work PinmvpRajesh R Subramanian26-May-09 23:09 
QuestionHow can open multiple sheets of one excel. Pinmember"_$h@nky_"30-Mar-09 21:18 
AnswerHow to add a comment? Pinmemberphandung151128-Oct-12 19:28 
GeneralThanks ... PinmemberMaximilien3-Mar-09 9:53 
GeneralGet Functions with enum return type PinmemberBhagvat Gohel2-Mar-09 2:33 
GeneralInteresting quirk... the CreateInstance is failing .... deep inside comip.h PinmemberMinok12-Dec-08 14:09 
GeneralRe: Interesting quirk... the CreateInstance is failing .... deep inside comip.h Pinmemberzhangdb23-Mar-09 19:33 
GeneralSo appreciate~ PinmemberAllen.W19-Aug-08 16:26 
Generallinker error Pinmemberunclemilford14-Jul-08 17:17 
Generalwarning C4192: PinmemberTClarke14-Jul-08 1:55 
GeneralOpen read-only PinmemberOleg_Krilov29-Jan-08 23:58 
GeneralRe: Open read-only PinmemberProductDE10-Aug-10 20:48 
GeneralOPEN an Excel file with MACROS DISABLED using C++ Pinmemberhigh_low_jack3-Nov-07 7:29 
GeneralAccessing a currently open instance of Excel PinmemberAlvin Poon31-Oct-07 18:22 
QuestionHow to copy and insert a row? PinmemberTulio28-Sep-07 5:49 
GeneralRead a number and the name of the sheet Pinmemberpendejo6666-Jul-07 1:19 
GeneralExcellent Pinmemberflippydeflippydebop30-May-07 5:39 
GeneralRead formated cell PinmemberTulio25-May-07 3:07 
GeneralRe: Read formated cell PinmemberMichael C. Robert29-May-07 3:36 
GeneralGetting VB out of the Office bed ... PinmemberD6MCK14-Feb-07 6:49 
Generaluse in vc++ 6.0 Pinmembercepri_zhangyt12-Jan-07 0:16 
GeneralAbout those varOption's Pinmembercaparicajr8-Dec-06 10:04 
GeneralGood work PinmemberMe2332328-Nov-06 16:21 
QuestionProblem with formatting numbers as text Pinmembermerlin06926-Oct-06 1:52 
AnswerRe: Problem with formatting numbers as text PinmemberMichael C. Robert26-Oct-06 5:17 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.141223.1 | Last Updated 6 Oct 2006
Article Copyright 2006 by Michael C. Robert
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid