CppChap10.pdf

download CppChap10.pdf

of 32

Transcript of CppChap10.pdf

  • 8/12/2019 CppChap10.pdf

    1/32

    Chapter 9

    Interacting with EXCEL

    We will investigate methods of using C++ routines in Excel and also methods of run-ning Excel from C++. Since C++ has no built in facility for drawing graphs and fordisplaying data in an attractive way, except by using the gui which is limited, the in-teraction will provide us with ways of using the speed, structural features and greateraccuracy of C++ in a user-friendly environment.

    We will look at three scenarios:

    We will construct simple DLLs (Dynamic Link Libraries) which export functionsof a certain type and which can be called from Excel. These functions can be used(after declaring them in a module) in VBA programs and the results returned tothe spreadsheet. Some of them can also be used directly in the spreadsheet asworksheet functions. There is a great deal more that is possible, but the techniquewe present will do for most purposes.

    A DLL is a library whose code can be used at run time and does not have to becompiled together with the code of the application. In contrast, Static Librariescontain functions which must be included at compile time.

    We will construct simple XLLs which can be used as Add-Ins. These are DLLswhich contain special functions that tell Excel what type of functions are containedin the DLL. This enables Excel to register them when the XLL is loaded as anAdd-In and to use them like ordinary worksheet functions. The functions muststill be declared in a VBA module in order to use them in VBA.

    We will show how to call an instance of Excel from a C++ program and how to

    use the Excel objects in the program.

    9.1 A Simple DLL

    In this section we will indicate how to construct a library of functions (DLL) which canbe accessed from Excel.

    1

  • 8/12/2019 CppChap10.pdf

    2/32

    2 CHAPTER 9. INTERACTING WITH EXCEL

    We will construct a DLL which exports the following function quad. It solves aquadratic equation with real coefficients.

    #include

    #include

    using namespace std;

    double __stdcall quad(double a,double b,double c,double &rrt1,

    double &rrt2,double &crt1r,double &crt1i,

    double &crt2r,double &crt2i)

    {

    rrt1=-999999.;rrt2=-999999.;crt1r=-999999.;crt1i=-999999.;

    crt2r=-999999.;crt2i=-999999.;

    if (a == 0.)

    if (b != 0) {rrt1=-c/b;exit(1);}

    else exit(1);

    double sqrtd;

    double oneover2a=0.5*a;

    double d=b*b-4*a*c;

    double realpart = oneover2a*b;

    if (d>=0)

    {

    sqrtd=oneover2a*sqrt(d);

    rrt1=realpart + sqrtd;

    rrt2=rrt1 - sqrtd - sqrtd;

    }

    else{

    sqrtd=oneover2a*sqrt(-d);

    crt1r=realpart;crt2r=realpart;

    crt1i=sqrtd;crt2i=-sqrtd;

    }

    return 0.;

    }

    Here, the result is returned in the last 6 arguments. rrt is a mnemonic for realroot andcrt is a mnemonic for complex root, r for real part and i for imaginarypart. The returning variables have defaults 0f -999999., and the various cases

    can be checked according to whether the return values are defaults or not. Weneed a wrapper function in VBA to do this. The function uses 6 multiplicationsand returns the real and imaginary parts of the roots. 5 Multiplications wouldbe optimal. We could omit the last two arguments, as they provide no newinformation.

    Open a Win32 project in VS and on the second screen check the boxes markedDLL and Empty Project.

  • 8/12/2019 CppChap10.pdf

    3/32

    9.1. A SIMPLE DLL 3

    Add a .cpp module to the Sources and enter the code. Build to check for entryerrors.

    Note that you do not need a prototype for the function quad. However, you wouldneed prototypes for auxiliary functions called by your functions.

    The function(s) that you are to export have their name preceded by __stdcall(note the two underline characters before the first alphabetic character). This isan instruction to the operating system on how to run the function. It is platform-dependent. There are other possible ways to run a function which we do notmention.

    It is easy to pass Integer,Long,Doublearguments to VBA and to the worksheet.Vectors and multidimensional arrays of these types are more difficult to passand will be dealt with in the following example. These types should suffice fornumerical applicaions. However, with a lot more effort, we can arrange to passalmost any type.

    Add a new Source module by choosing one with a .def qualifier. Add a line toit with the word EXPORTS Then add the name(s) of the function(s) (quadin thiscase) one to a line.

    Build the application under the Debug option in the edit box on the top toolbar.Look in the Debugfolder in you project folder. You will find one file with a .dllqualifier. When you are eventually satisfied with everything (after the experimen-tation to be outlined) you should do the build under the Releaseoption to obtaina leaner version. Now add a few breakpoints to the code.

    Create a subfolder of your project folder called XLS.

    (See below for an alternative). Open Excel and in the Tools | Options | Generalmenu and in the edit box markedAt startup, open all files in:put the fullpath of your XLS folder. Save your Excel instance in this folder. This should bethe only file in this folder.

    In VS go to the menu item Project | (ProjName) Properties ... | ConfigurationProperties | Debugging | Command edit box. Click the Down arrow and selectBrowse. Look for your .exe file for Excel (it should be in the Program Files |Microsoft Office | OFFICE11 folder). Click on the file.

    (Alternative to the item above). In the same menu, just below Command, is abox called Command Arguments. In the box, enter the full path of an Excel file

    that you have previously saved and wish to use for debug purposes. You do notneed to follow the above alternative instructions.

    Click on the Run Debug triangle on the top toolbar. The debugger opens yourExcel file.

    Insert a code module and in the declarations section at the start of the moduleinsert Option Explicitand the following declaration and Sub:

  • 8/12/2019 CppChap10.pdf

    4/32

    4 CHAPTER 9. INTERACTING WITH EXCEL

    Declare Function quad _

    Lib "J:\Visual Studio Projects\Finance\qtst\Debug\qtst.dll" _

    (ByVal a As Double, ByVal b As Double, ByVal c As Double, _rrt1 As Double, rrt2 As Double, crt1r As Double, crt1i As Double, _

    crt2r As Double, crt2i As Double) As Double

    You must replace the path to the DLL by your own DLL path.

    Sub quadtst()

    Dim x As Double, rrt1 As Double, rrt2 As Double, crt1r As Double, _

    crt1i As Double, crt2r As Double, crt2i As Double

    x = quad(1, 1, 1, rrt1, rrt2, crt1r, crt1i, crt2r, crt2i)

    End Sub

    Note the ByVal prefacing the first 3 arguments. Without any modification, a

    C++ argument is called by value and a VBA argument is called by reference.Since our C++ function had its first 3 arguments called by value, we must dothe same for our VBA version. The last 6 arguments were called by reference inC++ and so do not need modification in VBA. If you get it wrong the operatingsystem will say something like Bad Calling Convention at run time.

    Place a breakpoint at End Sub and run the sub, looking at the return values inthe Locals Window. Try various values of the first three parameters a,b and c.

    You can now write a wrapper function in VBA which calls the functionquadandinterrogates the return values to find what the situation for your equation is infact. This information can then be put out to the spreadsheet. Try to make itpretty.

    Sometimes, functions of this type can be used directly in as worksheet functions.If you look at user defined functions in the Insert Function dialog box, you willfind it listed under user functions. However, it is usually best to write a VBAwrapper function which calls your function internally and puts out exactly theright information to the spreadsheet. In the present case, the functionquad willnot run when entered on the spreadsheet with help from the Insert Functiondialog. The function expects all arguments to be called by value. If you removedthe last 6 arguments, the function would run - try something similar. To getcall by reference arguments to work you need to inform Excel of the argumentreference types. We will see how to do this when constructing an XLL.

    9.2 A DLL That Takes Array Arguments

    We would like to call the C++ function from VBA with return type one ofdouble,int,longand possibly array arguments with data having one of these three types. The arraysthat we will consider are either vectors (1-dimensional) or matrices (2-dimensional).Very occasionally in numerical work, one needs 3-dimensional arrays, and we will leaveto the reader extensions to this type of array.

  • 8/12/2019 CppChap10.pdf

    5/32

    9.2. A DLL THAT TAKES ARRAY ARGUMENTS 5

    VBA uses a Microsoft defined type called SAFEARRAY to store arrays. This type is astructure containing the bounds of the various dimensions and a pointer to the array

    values. We need to do the following:

    Define our C++ function with the correct type of arguments. The arrays mustbe ofSAFEARRAYtype and the input and output array variables must be pointersto pointers to variables of type SAFEARRAY.

    Find the bounds of the input arrays.

    Construct arrays in C++ (using whatever array types you like) of appropriatesize and type.

    Transfer the array values from the input arrays to the C++ arrays.

    Process the input arrays and values.

    Transfer the desired outputs to the SAFEARRAY type output variables.

    We give an example of how to do this for one of the programs in NR, namely theconstruction of the singular value decomposition (svd) of a rectangular matrix.

    #include "nr.h"

    #include

    #include

    #include "MyFns.h"

    using namespace std;

    double __stdcall svdc(SAFEARRAY **a,SAFEARRAY **w,SAFEARRAY **v)

    {

    long nrows,ncols;

    GET_SAFE_BOUNDS_MAT(a,nrows,ncols);

    Mat_IO_DP ac(nrows,ncols);

    SAFE_to_CArray(a,ac);

    GET_SAFE_BOUNDS_MAT(v,nrows,ncols);

    Mat_IO_DP vc(nrows,ncols);

    SAFE_to_CArray(v,vc);

    long nelts;

    GET_SAFE_BOUNDS_VEC(w,nelts);Vec_IO_DP wc(nelts);

    SAFE_to_CVec(w,wc);

    NR::svdcmp(ac,wc,vc); // Main function called from NR

    CArray_to_SAFE(ac,a);

  • 8/12/2019 CppChap10.pdf

    6/32

    6 CHAPTER 9. INTERACTING WITH EXCEL

    CArray_to_SAFE(vc,v);

    CVec_to_SAFE(wc,w);

    return 0;

    }

    Comments

    The function that we wish to run in C++ is called svdcmp and takes a matrix aas input and outputs a vector w of singular values and a matrix v, as well as amatrixu which is put in place of the matrix a. Hence the argument a is used forboth input and output.

    Note the **a and similar. Thus *a is a pointer to a SAFEARRAY.

    The auxiliary functions used are defined at the end of this section, and have nameswhich are self-explanatory. ac,wc,vcare C++ arrays of appropriate size.

    The main called function from NR is contained in a module called, for example,svdcmp.cppand the contents are of the form

    #include

    #include "nr.h"

    using namespace std;

    void NR::svdcmp(Mat_IO_DP &a, Vec_O_DP &w, Mat_O_DP &v)

    {

    .......

    }

    Other files that must be included in the Source files and in the Build are: pythag.cpp,svd.def,MyFns.cppand in the Header files we need MyFns.h. Thepythag.cppfile is in the NR collection offiles and does a particular calculation in a numerically efficient way. MyFns.cpp,MyFns.hcontain the utility functions used in the program and are now listed:

    MyFns.h:

    #ifndef MYFNS_H

    #define MYFNS_H

    double __stdcall NormSDens(double x,double* v);

    double __stdcall NormSProbInv(double u);void SAFE_to_CArray(SAFEARRAY **p,Mat_IO_DP &q);

    void CArray_to_SAFE(Mat_IO_DP &q,SAFEARRAY **p);

    void SAFE_to_CVec(SAFEARRAY **p,Vec_IO_DP &q);

    void CVec_to_SAFE(Vec_IO_DP &q,SAFEARRAY **p);

    void GET_SAFE_BOUNDS_MAT(SAFEARRAY **p, long & nrows, long &ncols);

    void GET_SAFE_BOUNDS_VEC(SAFEARRAY **p, long & nelts);

  • 8/12/2019 CppChap10.pdf

    7/32

    9.2. A DLL THAT TAKES ARRAY ARGUMENTS 7

    double __stdcall svdc(SAFEARRAY **a,SAFEARRAY **w,SAFEARRAY **v);

    #endif

    MyFns.cpp:

    #include

    #include

    #include "nr.h"

    #include "MyFns.h"

    #include

    using namespace std;

    const double OneDivRootTwoPi = 0.398942280401433;

    void CVec_to_SAFE(Vec_IO_DP &q,SAFEARRAY **p){

    long elt_index[1];

    int nrows = q.size();

    for (int i=0;i

  • 8/12/2019 CppChap10.pdf

    8/32

    8 CHAPTER 9. INTERACTING WITH EXCEL

    for (int j=0;j

  • 8/12/2019 CppChap10.pdf

    9/32

    9.2. A DLL THAT TAKES ARRAY ARGUMENTS 9

    {

    static double a[4]={2.50662823884,-18.61500062529,49.32119773535};

    static double b[4]={-8.47351093090,23.08336743743,-21.06224101826,3.13082909833};static double c[9]={0.3374754822726147,0.9761690190917186,

    0.1607979714918209,0.0276438810333863,0.0038405729373609,

    0.0003951896511919,0.0000321767881768,0.0000002888167364,0.0000003960315187};

    double x=u-0.5;

    double r;

    if (fabs(x)0.0)

    r=1.0-u;

    r=log(-log(r));

    r=c[0]+r*(c[1]+r*(c[2]+r*(c[3]+r*(c[4]+r*(c[5]+r*(c[6]+r*(c[7]+r*c[8])))))));

    if (x

  • 8/12/2019 CppChap10.pdf

    10/32

    10 CHAPTER 9. INTERACTING WITH EXCEL

    u(i, j) = a(i, j)

    Next j

    Next i

    rslt = svdc(u, w, v)

    For k = 0 To nrows - 1

    For L = 0 To ncols - 1

    prod(k, L) = 0

    For j = 0 To ncols - 1

    prod(k, L) = prod(k, L) + u(k, j) * w(j) * v(L, j)

    Next j

    Next L

    Next k

    FormatWkbkColor

    InsertHead "A2", "Matrix A"

    DisplayMat "A3", 5, 5, a

    InsertHead "A9", "Matrix U"

    DisplayMat "A10", 5, 5, u

    InsertHead "A16", "Matrix V"

    DisplayMat "A17", 5, 5, v

    InsertHead "A23", "MatProd UDV"

    DisplayMat "A24", 5, 5, prod

    InsertHead "G9", "DiagMatVec D"

    DisplayVec "G10", 5, 1, w

    End Sub

    Sub DisplayMat(TopCornerCell As String, nrows As Integer, ncols As Integer, u() As Double)

    Dim i As Integer, j As Integer

    For i = 0 To nrows - 1

    For j = 0 To ncols - 1

    Range(TopCornerCell).Cells(i + 1, j + 1).Interior.ColorIndex = xlColorIndexNone

    Range(TopCornerCell).Cells(i + 1, j + 1).NumberFormat = "0.0000000000"

    Range(TopCornerCell).Cells(i + 1, j + 1).Value = u(i, j)

    Next j

    Next i

    End Sub

    Sub DisplayVec(TopCornerCell As String, nrows As Integer, ncols As Integer, u() As Double)

  • 8/12/2019 CppChap10.pdf

    11/32

    9.3. AN AUTOMATION DLL 11

    Dim i As Integer, j As Integer, val As Double

    For i = 0 To nrows - 1

    For j = 0 To ncols - 1Range(TopCornerCell).Cells(i + 1, j + 1).Interior.ColorIndex = xlColorIndexNone

    Range(TopCornerCell).Cells(i + 1, j + 1).NumberFormat = "0.0000000000"

    If nrows = 1 Then val = u(j) Else val = u(i)

    Range(TopCornerCell).Cells(i + 1, j + 1).Value = val

    Next j

    Next i

    End Sub

    Sub FormatCellColors(ran As Range)

    ran.Cells.Interior.ColorIndex = 52

    ran.Cells.Interior.ColorIndex = xlColorIndexNone

    End Sub

    Sub FormatWkbkColor()

    Range("A:Z").Interior.ColorIndex = 16 23

    End Sub

    Sub InsertHead(CellName As String, txt As String)

    Range(CellName).Interior.ColorIndex = 6 xlColorIndexNone

    Range(CellName).Value = txt

    End Sub

    9.3 An Automation DLL

    The DLLs that we have created up to the present require their functions to be registeredusing VBA. The functions cannot be used in the spreadsheet. To register the functions,we can add an interface to the DLL and make it into an XLL whose functions can beused in the spreadsheet, once the XLL has been added as an AddIn using the Tools |AddIns menu. The functions must still be added via Declarations in VBA in order forthem to be usable in VBA.

    The XLL is a relatively old technology (1997) and Microsoft has not been supportingthem further. Some changes in the handling of strings has made them more difficult toimplement. Using the wizards in VS C++, it is easy to create a newer technology AddIncalled an Automation AddIn. These can only be added in more recent versions of Excel

    (2002 onwards). The Tools | AddIns menu has a button for loading Automations. Thefunctions appear on the Insert Function menus and they can be used in the spreadsheet.It is also possible to make them available for use in VBA. The following summarisesthe various points:

    To use your functions in VBA create a DLL as above and Declare the functionsin a VBA code module. They can also be used in the spreadsheet by writing a

  • 8/12/2019 CppChap10.pdf

    12/32

    12 CHAPTER 9. INTERACTING WITH EXCEL

    wrapper function for them in VBA, provided the arguments of the VBA functionand its return return type are suitable.

    To use the functions via an AddIn (in the spreadsheet) create an AutomationAddIn as outlined below. They can also be made available for use in VBA.

    For VBA-usable functions, we use functions whose arguments and return typesare (possibly arrays of) either int, long or double.

    For worksheet-usable functions, all the arguments and return types must be single(non-array) variables of type int,long,double. Argument types can be extendedto take Range arguments and thence to extract the cell-data as doubles, but thisis tricky and requires more effort. It seems more desirable to use VBA. I do notknow how to create a usable function for the worksheet that returns an array.

    Once you have included all the functions you need (worksheet and VBA) you

    can save the workbook as a .xla AddIn and include it when you have the needfrom the Tools| AddIns menu. To create an.xlaAddIn open the workbook andchoose File| Save As and search for the .xlaextension in the drop down Save astype: BOX.

    Creating the Automation AddIn

    In the menu, choose File| New|Project| Visual C++| ATL| ATL Project. Entera name (say, ATLTst), in the resulting wizard, click Application Settings, uncheckthe Attributed checkbox, click the DLL radio button. Click Finish.

    A Solution ATLTst is created containing two projects and a number of files.

    Right click on the ATLTst Project and in the drop-down menu, click the Add |Add Class item. In the resulting wizard, select the ATL Simple Project icon.

    On the next screen, enter in the textbox Short Name the name ATLMyFns andFinish (the remaining textboxes are filled in automatically). Two new files withthis name are added, with extensions .h,.cpp.

    We now add user functions. Choose menu View | Class View (or find the tool-bar button for Class View). Open ATLTst and right click on IATLTst (I is forinterface). Choose Add| Add Method. The Add Method wizard window appears.

    In the Method name textbox write the name of the function, Norm D in thiscase. The function will have a return type HRESULT, which is a system type

    (not native C++) - it receives information when the function has run. The inputand output variables are all arguments of the function. Inputs to the functionmay be any of the types in the drop down menu under Parameter type. Outputsmust be a pointer type in the list.

    Choose type double and check the in checkbox. Enter name x for the variable.Click Add. The variable is added to the large textbox preceded by [in]. Otherinput variables can now be chosen if needed.

  • 8/12/2019 CppChap10.pdf

    13/32

    9.3. AN AUTOMATION DLL 13

    Add a variable of type DOUBLE* with name y. Check the boxes out and retval(return value of the function). Click Finish.

    Other allowed combinations are in and out (must be of pointer type); out only(pointer type).

    Open ATLMyFns.cpp and locate the function declaration NormD. Add the codefor the function and add an include for cmath and using namespace std;. Thecomplete file to date is

    // ATLMyFns.cpp : Implementation of CATLMyFns

    #include "stdafx.h" #include "ATLMyFns.h" #include ".\atlmyfns.h"

    #include using namespace std;

    // CATLMyFns

    STDMETHODIMP CATLMyFns::NormD(DOUBLE x, DOUBLE* y)

    {

    *y=(1/sqrt(2*3.14159265358979))*exp(-x*x/2);

    return S_OK;

    }

    Build the solution. Open Excel and go to menu Tools | Add-Ins. Click on buttonAutomation. Select item ATLMyFns Class. Press OK. The function NormD isnow available for use in the spreadsheet.

    Enter =NormD(0.5) in a cell and press enter. The value appears. Go to menuInsert | fx or click on the fx icon on the toolbar. In the dialog box search thecategory combo box for ATLTst.ATLMyFns.1. A dialog box with the functionsappears - in this case only the function NormD. You can enter the function in thespreadsheet by clicking it and completing the dialog.

    Using the Automation DLL Functions in VBA

    In the VBE (Visual Basic Editor in Excel) choose menu Tools and click on theBrowse button.

    Locate the debug or release folder in your project ATLTst folder. In one of thesefolders locate the ATLTst.tlb file and click Open (this is a Type Library). In thelistbox locate the entry ATLTst 1.0 Type Library. Check the box next to it.

    The following sub illustrates the use of the functions in the class ATLMyFns - it

    only contains one function, namely NormD.

    Sub trial()

    Dim oFns As ATLMyFns, d As Double

    Set oFns = New ATLMyFns

    d = oFns.NormD(0)

    End Sub

  • 8/12/2019 CppChap10.pdf

    14/32

    14 CHAPTER 9. INTERACTING WITH EXCEL

    9.4 Using the COM Interface and the Excel Source

    Files

    Excel programs can be written directly in C++ and Excel can be called from C++using the COM (Component Object Model) interface. The programs look similar towhat one would write in VBA but extensive use is made of pointers. To obtain the MSExcel code one must import the files mso.dll, vb6ext.olb, Excel.exewhich are,respectively, a DLL, a static library and an executable.

    We will simply give a program to show how things are done.

    9.4.1 Imports

    The following code lines (or substitutes for them) can be placed in Imports.h and

    should be included in each code module. Each code statement should appear on aSINGLE LINE (do not use multiple lines).

    #import "C:\Program Files\Common Files\Microsoft Shared\

    office11\mso.dll" rename("DocumentProperties",

    "DocumentPropertiesXL") rename("RGB", "RBGXL")

    #import "C:\Program Files\Common Files\Microsoft Shared\VBA\VBA6\vbe6ext.olb"

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

    rename("DialogBox", "DialogBoxXL") rename("RGB", "RBGXL")

    rename("DocumentProperties", "DocumentPropertiesXL")

    rename("ReplaceText", "ReplaceTextXL") rename("CopyFile",

    "CopyFileXL") no_dual_interfaces

    You must set your paths according to the file locations on your computer.

    9.4.2 The C++ Code

    //Main Code

    #include

    #include "nr.h"

    #include "Functions.h"

    #include "Utilities.h"#include

    #include

    #include

    #include "Imports.h"

    using namespace std;

  • 8/12/2019 CppChap10.pdf

    15/32

    9.4. USING THE COM INTERFACE AND THE EXCEL SOURCE FILES 15

    //At present, the Chart Title and the Axes Titles are always the same -

    //alter them in EXCEL or insert user interface to enter them from the console

    //The following two arrays hold function title, function pointers, display info

    //The first two must be changed if the function list is changed

    //NUMFNSAVAILABLE must reflect the number of functions available for plotting

    const int NUMFNSAVAILABLE=6;

    //Array of function names

    string FnNames[NUMFNSAVAILABLE]={"cosine","sine","NormSDens","NormSCum",

    "NormSInv","Exp"};

    //Following is an array of function pointers

    double (*pFns[NUMFNSAVAILABLE])(double) = {cos,sin,NormSDens,NormSDist,NormSInv,exp};

    void main() {

    //Initialise COM

    CoInitialize(NULL);

    //Initiate Excel

    Excel::_ApplicationPtr xl;

    xl.CreateInstance(L"Excel.Application");

    //Get Pointer to Sheet1, Rename

    while(true) {//Add a workbook. Get pointer to the workbook

    xl->Workbooks->Add((long) Excel::xlWorksheet);

    Excel::_WorkbookPtr pWorkbook = xl->ActiveWorkbook;

    Excel::_WorksheetPtr pSheet = pWorkbook->Worksheets->GetItem("Sheet1");

    pSheet->Name = "Chart Data";

    //Set up data structures

    double leftendpt,rightendpt;

    int N; //Number of points to plot

    int numfns; //Number of functions to plot

    //Setup array which will record which functions to graph - all entries 0 initially

    int process[NUMFNSAVAILABLE]={0};

    //Get User input

    SetUpData(numfns,N,leftendpt,rightendpt,process,FnNames,NUMFNSAVAILABLE);

    //Get Points on X-Axis, Write it to the worksheet

  • 8/12/2019 CppChap10.pdf

    16/32

  • 8/12/2019 CppChap10.pdf

    17/32

    9.4. USING THE COM INTERFACE AND THE EXCEL SOURCE FILES 17

    Excel::AxisPtr pAxisCat = pChart->Axes((long)Excel::xlCategory, Excel::xlPrimary);

    pAxisCat->MinimumScale = leftendpt;

    pAxisCat->MaximumScale = rightendpt;

    pAxis->TickLabels->Font->Name="Arial";

    pAxis->TickLabels->Font->Size = FONTSIZE;

    pAxis->TickLabels->Font->FontStyle = "Regular";

    pAxis->HasTitle = true;

    pAxisCat->HasTitle = true;

    pAxis->AxisTitle->Text = "Prob";

    pAxisCat->AxisTitle->Text = "Point";

    //pAxis->MinorUnit = .01;

    //pAxis->MajorUnit = .001;

    Excel::ChartAreaPtr pChartArea = pChart->ChartArea;

    pChartArea->Border->Weight = 3;

    pChartArea->Border->LineStyle = -1;

    Excel::SeriesPtr pSeries = pChart->SeriesCollection(1);

    pSeries->Border->Weight = Excel::xlThick;

    pSeries->Border->LineStyle = Excel::xlAutomatic;

    pSeries->Border->ColorIndex = 3;

    for(i=1;iSeriesCollection(i+1);pSeries->Border->Weight = Excel::xlThick;

    pSeries->Border->LineStyle = Excel::xlAutomatic;

    pSeries->Border->ColorIndex = 3+2*i;

    }

    pChart->PlotArea->Fill->ForeColor->SchemeColor = 50;

    pChart->PlotArea->Fill->BackColor->SchemeColor = 43;

    pChart->PlotArea->Fill->TwoColorGradient(Office::msoGradientHorizontal,1);

    pChart->ChartArea->Fill->ForeColor->SchemeColor = 20;

    pChart->ChartArea->Fill->BackColor->SchemeColor = 36;

    pChart->ChartArea->Fill->TwoColorGradient(Office::msoGradientHorizontal,1);

    pChart->HasLegend = true;

    pChart->Legend->Position = Excel::xlLegendPositionRight;

    pChart->Legend->Fill->ForeColor->SchemeColor = 20;

    pChart->Legend->Fill->BackColor->SchemeColor = 36;

  • 8/12/2019 CppChap10.pdf

    18/32

    18 CHAPTER 9. INTERACTING WITH EXCEL

    pChart->Legend->Fill->OneColorGradient(Office::msoGradientHorizontal,1,0.5);

    pChart->Legend->Font->Size = FONTSIZE - 4;

    pChart->PlotArea->Border->ColorIndex = 16;

    pChart->PlotArea->Border->Weight = Excel::xlThin;

    pChart->PlotArea->Border->LineStyle = Excel::xlContinuous;

    pChart->HasTitle = true;

    pChart->ChartTitle->Text = "Normals";

    pChart->ChartTitle->Font->Size = FONTSIZE + 5;

    //Display the spreadsheet

    xl->Visible = VARIANT_TRUE;

    //Prevent program from terminating

    cout

  • 8/12/2019 CppChap10.pdf

    19/32

    9.4. USING THE COM INTERFACE AND THE EXCEL SOURCE FILES 19

    double NormSDist(double x);

    #endif

    //Functions.cpp

    #include

    #include "functions.h"

    using namespace std;

    const double OneDivRootTPi =1/sqrt(2*3.14159);// 0.398942280401433;

    double NormSInv(double u) //Inverse Cum Normal

    {

    static double a[4]={2.50662823884,-18.61500062529,41.39119773534,-25.44106049637};static double b[4]={-8.47351093090,23.08336743743,-21.06224101826,3.13082909833};

    static double c[9]={0.3374754822726147,0.9761690190917186,0.1607979714918209,0.027643

    0.0038405729373609,0.0003951896511919,0.0000321767881768,0.0000002888167364,0

    double x=u-0.5;

    double r;

    if (fabs(x)0.0)

    r=1.0-u;

    r=log(-log(r));

    r=c[0]+r*(c[1]+r*(c[2]+r*(c[3]+r*(c[4]+r*(c[5]+r*(c[6]+r*(c[7]+r*c[8])))))));

    if (x

  • 8/12/2019 CppChap10.pdf

    20/32

    20 CHAPTER 9. INTERACTING WITH EXCEL

    double NormSDist(double x) //Cumulative Normal Density

    {

    static double a[5]={0.319381530,-0.356563782,1.718477937,-1.821255978,1.330274429};if (x7.0)

    return 1.0-NormSDist(-x);

    else

    {

    double k=1.0/(1.0+0.2316419*fabs(x));

    double tmp2=NormSDens(x)*(k*(a[0]+k*(a[1]+k*(a[2]+k*(a[3]+k*a[4])))));

    if (x

  • 8/12/2019 CppChap10.pdf

    21/32

    9.4. USING THE COM INTERFACE AND THE EXCEL SOURCE FILES 21

    {

    for(int i=0;i

  • 8/12/2019 CppChap10.pdf

    22/32

    22 CHAPTER 9. INTERACTING WITH EXCEL

    sheetRow++;

    }

    }void WriteRow(Excel::_WorksheetPtr sheet,long sheetRow,

    long sheetColumn,string * values,int numfns)

    {

    // Get cells.

    Excel::RangePtr pRange=sheet->Cells;

    // Next cells contain the values.

    for(int i=0; i < numfns; i++)

    {

    pRange->Item[sheetRow][sheetColumn] = values[i].c_str();

    sheetColumn++;

    }

    }

  • 8/12/2019 CppChap10.pdf

    23/32

    9.5. CREATING XLLS WITH XLW 23

    9.5 Creating XLLs with XLW

    XLW is an open source program available at http://www.sourceforge.net- also onthe cd provided for the course. It simplifies the construction of XLLs consisting of func-tions usable in the spreadsheet. Documentation on the classes and member functionscan be found in the installation by loading the file XLW\docs\index.chm. A solutionthat is already built is available in XLW\xlwExample. You will find there an Excelspreadsheet with the functions from the XLL produced when the solution is built. Inthe function wizard, the functions added are in the category xlw Example.

    In order to produce your own XLL, one way is to add another project to the xlw solutioninXLW\xlwExampleby adding from the File|Add Project(it should be an Empty DLLproject). Then add a cpp file and use the patterns exhibited in the xlwExample.cppfile. We will discuss the steps that must be taken below.

    The same three Win32StreamBuf modules as in xlwExample should be added tothe source files if they are not already present in the source files of another projectin the solution.

    The main cpp file should begin with the following (and in addition, add anyincludes necessary for your functions):

    #include "c:\Program Files\xlw\xlw\xlw.h"

    #include

    #include

    #include

    #include "Win32StreamBuf.h"

    Win32StreamBuf debuggerStreamBuf;

    std::streambuf * oldStreamBuf;

    extern "C"

    {

    ....... add body of file discussed below

    }

    The following is an example of a function added

    LPXLOPER EXCEL_EXPORT xlCirc(XlfOper xlDiam)

    {

    EXCEL_BEGIN;

    // Converts d to a double.

    double ret=xlDiam.AsDouble();// Multiplies it.

    ret *= 3.14159;

    // Returns the result as a XlfOper.

    return XlfOper(ret);

    EXCEL_END;

    }

  • 8/12/2019 CppChap10.pdf

    24/32

    24 CHAPTER 9. INTERACTING WITH EXCEL

    XLOPER,LPXLOPER are, respectively, a data type and a pointer to this typein C++ which is a union of other types and is similar to the Excel variant

    type. Excel functions accept these types in their functions. EXCEL_EXPORTisa macro indicating the function type and has value __declspec(dllexport).XlfOperis a C++ wrapper for theXLOPERtype. xlCircis the function name.

    EXCEL_BEGIN,EXCEL_ENDmust come at the beginning and end of the functionbody. They facilitate error handling and if you wish to use them, look attheir definition in macros.h. The code needs to be modified.

    xlDiamis of type XlfOper, which is a class and has a number of constructorsallowing conversion from C++ types to XlfOper and methods convertingfromXlfOperto native and other C++ types. xlDiam.AsDouble()convertsfrom XlfOper to C++ type double.

    XlfOper(ret) converts the double variable ret to a LPXLOPER which isaccepted by Excel.

    LPXLOPER EXCEL_EXPORT xlConcat(XlfOper xlStr1, XlfOper xlStr2){

    EXCEL_BEGIN;

    // Converts the 2 strings.

    std::string str1 = xlStr1.AsString();

    std::string str2 = xlStr2.AsString();

    // Returns the concatenation of the 2 string as a XlfOper.

    std::string ret = str1+str2;

    return XlfOper(ret.c_str());

    EXCEL_END;

    }

    Here, the xlStr1 is converted to a standard string as is xlStr12 , they areconcatenated and the result converted to a LPXLOPER and returned.

    LPXLOPER EXCEL_EXPORT xlStats(XlfOper xlTargetRange){

    EXCEL_BEGIN;

    // Temporary variables.

    double averageTmp = 0.0;

    double varianceTmp = 0.0;

    std::vector temp = xlTargetRange.AsDoubleVector(XlfOper::RowMajor);

    // All cells are copied. We do the actual work.

    size_t popSize = temp.size();

    for (size_t i = 0; i < popSize; ++i)

    {

    // sums the values.

    averageTmp += temp[i];

  • 8/12/2019 CppChap10.pdf

    25/32

    9.5. CREATING XLLS WITH XLW 25

    // sums the squared values.

    varianceTmp += temp[i] * temp[i];

    }// Initialization of the resultArray.

    double resultArray[2];

    // compute average.

    double& average = resultArray[0];

    average = averageTmp / popSize;

    // compute variance

    double& variance = resultArray[1];

    variance = varianceTmp / popSize - average * average;

    // Create the XlfOper returned with the resultArray containing the values.

    return XlfOper(1,2,resultArray);

    EXCEL_END;

    }

    tempis the conversion of an Excel range XLOPER(several rows and columns)to a single standard row vector (in row-major order). Alternatives areUniDimensional,RowMajor,ColumnMajor).

    A C++resultArrayis initialised and the mean and variance of the numbersin the range are computed and placed in the two elements of the result vector.The C++ vector is converted to a LPXLOPER and returned.

    We do not discuss the functions xlIsInWiz,NbCalls here. The first function isinstructive as it shows how to deal with a cell address (or reference).

    Two other functions must be added: AutoOpen, AutoClose. These are run by

    Excel - the first registers the functions and the second releases Excel and thesteambuffer. There are other such functions but they are not essential and theexample here does not use them.

    long EXCEL_EXPORT xlAutoOpen(){

    oldStreamBuf = std::cerr.rdbuf(&debuggerStreamBuf);

    std::cerr

  • 8/12/2019 CppChap10.pdf

    26/32

    26 CHAPTER 9. INTERACTING WITH EXCEL

    // Registers the function circ.

    circ.Register();

    // Registers the second function xlConcat.

    // Argument descriptions.

    XlfArgDesc str1("string1","First string");

    XlfArgDesc str2("string2","Second string");

    // xlConcat function description as concat.

    XlfFuncDesc concat("xlConcat","Concat","Concatenate two strings in one",

    "xlw Example");

    // Set the arguments for the function. Note how you

    //create a XlfArgDescList from two or more XlfArgDesc (operator+).

    //You can not push the XlfArgDesc one by one.

    concat.SetArguments(str1+str2);

    // Registers the concat function.

    concat.Register();

    // Registers the third function xlStats.

    // Argument descriptions.

    XlfArgDesc pop("Population","Target range containing the population");

    // xlStats Function description

    XlfFuncDesc stats("xlStats","Stats",

    "returns a (1x2) range containing the average and

    the variance of a numeric population","xlw Example");

    // Sets the arguments for the function.

    stats.SetArguments(pop);

    // Registers the stats function.

    stats.Register();

    // Registers the fourth function xlIsInWiz.

    XlfFuncDesc isInWiz("xlIsInWiz","IsInWiz",

    "returns true if the function is called from the

    function wizard, and the address of the caller otherwise",

    "xlw Example");

    isInWiz.Register();

    // Registers the fifth function xlNumberOfCall as volatile

    (unconditionally recalculated)

    XlfFuncDesc nbCalls("xlNbCalls","NbCalls",

    "returns the number of times the function has been

    calculated since the xll was loaded (volatile)",

    "xlw Example",XlfFuncDesc::Volatile);

    nbCalls.Register();

    // Clears the status bar.

    XlfExcel::Instance().SendMessage();

  • 8/12/2019 CppChap10.pdf

    27/32

    9.5. CREATING XLLS WITH XLW 27

    return 1;

    }

    For each function, we need to create two objects: XlfArgDesc,XlfFuncDesc(see the details in the header files of the same name).

    The arguments ofXlfArgDescare given by

    XlfArgDesc(const std::string& name, const std::string& comment, char type=R);

    The first argument gives the function name in Excel and the second gives acomment listed in the Excel function wizard. The third argument has a de-fault which isR and means that the input argument is an XLOPER. For thisimplementation, it is recommended that all input arguments be of this type.If there is more than one argument, an XlfArgDescobject must be created foreach argument and then a list of them (str1+str2for the concatfunction)created and then passed toSetArguments(concat.SetArguments(str1+str2);).

    The arguments ofXlfFunDescare given by

    XlfFuncDesc(const std::string& name, const std::string& alias,

    const std::string& comment, const std::string& category,

    RecalcPolicy recalcPolicy = NotVolatile);

    name is the function name in this (example) program. alias is the nameit will be known as in Excel. category is the category name under whichthe function will appear in the Excel function wizard. If you change thedefault argument by entering Volatile, the function will recalculate in thespreadsheet when it undergoes a minimal change (see the xlNbCallsfunctionfor an example).

    The variable (for example pop) of the function (for example stats) must

    have its argument set via stats.SetArguments(pop);. The function must be registered via, for example, stats.Register();.

  • 8/12/2019 CppChap10.pdf

    28/32

    28 CHAPTER 9. INTERACTING WITH EXCEL

    The entire xlwExample.cpp module

    /*

    Copyright (C) 1998, 1999, 2001, 2002 Jrme Lecomte

    This file is part of xlw, a free-software/open-source C++ wrapper of the

    Excel C API - http://xlw.sourceforge.net/

    xlw is free software: you can redistribute it and/or modify it under the

    terms of the xlw license. You should have received a copy of the

    license along with this program; if not, please email [email protected]

    This program is distributed in the hope that it will be useful, but WITHOUT

    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS

    FOR A PARTICULAR PURPOSE. See the license for more details.

    */

    // $Id: xlwExample.cpp,v 1.12 2003/03/08 22:21:02 jlecomte Exp $

    #include "c:\Program Files\xlw\xlw\xlw.h"

    #include

    #include

    #include

    #include "Win32StreamBuf.h"

    Win32StreamBuf debuggerStreamBuf;

    std::streambuf * oldStreamBuf;

    extern "C"

    {

    LPXLOPER EXCEL_EXPORT xlCirc(XlfOper xlDiam)

    {

    EXCEL_BEGIN;

    // Converts d to a double.

    double ret=xlDiam.AsDouble();

    // Multiplies it.

    ret *= 3.14159;

    // Returns the result as a XlfOper.

    return XlfOper(ret);EXCEL_END;

    }

    LPXLOPER EXCEL_EXPORT xlConcat(XlfOper xlStr1, XlfOper xlStr2)

    {

    EXCEL_BEGIN;

  • 8/12/2019 CppChap10.pdf

    29/32

    9.5. CREATING XLLS WITH XLW 29

    // Converts the 2 strings.

    std::string str1 = xlStr1.AsString();

    std::string str2 = xlStr2.AsString();// Returns the concatenation of the 2 string as a XlfOper.

    std::string ret = str1+str2;

    return XlfOper(ret.c_str());

    EXCEL_END;

    }

    LPXLOPER EXCEL_EXPORT xlStats(XlfOper xlTargetRange)

    {

    EXCEL_BEGIN;

    // Temporary variables.

    double averageTmp = 0.0;

    double varianceTmp = 0.0;

    // XlfExcel::Coerce method (internally called) will return to Excel

    // if one of the cell was invalidated and need to be recalculated.

    // Excel will calculate this cell and call again our function.

    // Thus we copy first all the data to avoid to partially compute the

    // average for nothing since one of the cell might be uncalculated.

    std::vector temp = xlTargetRange.AsDoubleVector(XlfOper::RowMajor);

    // All cells are copied. We do the actual work.

    size_t popSize = temp.size();

    for (size_t i = 0; i < popSize; ++i)

    {// sums the values.

    averageTmp += temp[i];

    // sums the squared values.

    varianceTmp += temp[i] * temp[i];

    }

    // Initialization of the resultArray.

    double resultArray[2];

    // compute average.

    double& average = resultArray[0];

    average = averageTmp / popSize;

    // compute variance

    double& variance = resultArray[1];

    variance = varianceTmp / popSize - average * average;

    // Create the XlfOper returned with the resultArray containing the values.

    return XlfOper(1,2,resultArray);

    EXCEL_END;

    }

  • 8/12/2019 CppChap10.pdf

    30/32

    30 CHAPTER 9. INTERACTING WITH EXCEL

    /*!

    * Demonstrates how to detect that the function is called by

    * the function wizard, and how to retrieve the coordinates* of the caller cell

    */

    LPXLOPER EXCEL_EXPORT xlIsInWiz()

    {

    EXCEL_BEGIN;

    // Checks if called from the function wizard

    if (XlfExcel::Instance().IsCalledByFuncWiz())

    return XlfOper(true);

    // Gets reference of the called cell

    XlfOper res;

    XlfExcel::Instance().Call(xlfCaller,res,0);XlfRef ref=res.AsRef();

    // Returns the reference in A1 format

    std::ostringstream ostr;

    char colChar=A+ref.GetColBegin();

    ostr

  • 8/12/2019 CppChap10.pdf

    31/32

    9.5. CREATING XLLS WITH XLW 31

    long EXCEL_EXPORT xlAutoOpen()

    { oldStreamBuf = std::cerr.rdbuf(&debuggerStreamBuf);

    std::cerr

  • 8/12/2019 CppChap10.pdf

    32/32

    32 CHAPTER 9. INTERACTING WITH EXCEL

    // Registers the fourth function xlIsInWiz.

    XlfFuncDesc isInWiz("xlIsInWiz","IsInWiz","returns true if the function is called

    from the function wizard, and the address of the caller otherwise","xlw Example");isInWiz.Register();

    // Registers the fifth function xlNumberOfCall as volatile (unconditionally recalculated)

    XlfFuncDesc nbCalls("xlNbCalls","NbCalls","returns the number of times

    the function has been calculated since the xll was loaded (volatile)",

    "xlw Example",XlfFuncDesc::Volatile);

    nbCalls.Register();

    // Clears the status bar.

    XlfExcel::Instance().SendMessage();

    return 1;

    }

    long EXCEL_EXPORT xlAutoClose()

    {

    std::cerr