The new function will have been inserted into your code. The interesting code in the common function is marked below:
CXlOper* HISTVOL_Impl(CXlOper& xloResult, const CXlOper* Prices_op) { // Input buffers std::vector<double> Prices; // Validate and translate inputs XlReadVector(*Prices_op, Prices, L"Prices", XLA_TRUNC_ONEMPTY| XLA_TRUNC_ONBLANK); // End of generated code //}}XLP_SRC // TODO - set the value of xloResult, or return another value // using CXlOper::RetXXX() or throw a CXlRuntimeException. return xloResult.Ret(); }
As you can see, the Wizard has generated more code this time. Let us examine the new features.
The argument type has been declared as const CXlOper*.
In the last example, NORMSINV2
, we used CXlOper
to return data to Excel. Here, Excel is using CXlOper
to pass data to our add-in function.
Because CXlOper
is a generic type, we cannot know in advance
what it will contain.
The user may pass a single value instead of a series, or a string value,
or they may omit the value altogether.
Whatever the user specifies, Excel will pass it straight on to our function.
It is the responsibility of the add-in function to ensure that the
argument contains the expected type of data.
The Wizard generates code to handle this.
An STL vector of doubles, Prices, has been declared.
This will be used as a convenient variable-size buffer for the data
passed from Excel in the CXlOper Prices_op
.
Note: The best thing about using an STL vector is that we don't have to worry about memory cleanup.
As soon as Prices
goes out of scope (i.e. at the end of the function),
all its memory will be released.
We do not have to remember to use free() or delete.
The function is going to have do some processing of input to a more useful form.
In particular, the data in Prices_op
has to be extracted from the CXlOper
and put into an array of doubles.
std::vector<double> Prices; XlReadVector(*Prices_op, Prices, L"Prices");
The template method XlReadVector()
is used to do the data extraction.
If the extraction fails for any reason, then an exception of type
CXlRuntimeException will be thrown,
containing details of why the conversion failed.
For example, if the third cell in Prices
contains
the text "Cheese", the exception will contain the following message:
Expected number for Prices[3]
Note: The message does not need to be in English. Depending on the current Windows language, the message may be localized. See International support for details.
If the extraction process has failed, and an exception is thrown,
the function will immediately stop, and the exception will be caught and
handled by the wrapper function, HISTVOL_4
or HISTVOL_12
.
extern "C" __declspec(dllexport) LPXLOPER12 HISTVOL_12(LPXLOPER12 Prices) { XLL_FIX_STATE; CXlOper xloResult, Prices__port(Prices); try { xloResult.HandleResult(HISTVOL_Impl(xloResult, &Prices__port)); } catch(const CXlRuntimeException& ex) { CXllApp::Instance()->DisplayException(xloResult, ex); } return xloResult.Ret12(); }
The exception is handed to the application-level method,
CXllApp::DisplayException(),
which will deal with it in a standard way. (You can override
DisplayException()
in your add-in if you wish.)
The standard behaviour is to display a text string, e.g.:
#ERROR: Expected number for Prices[3]
If it succeeds, then we can be confident that the vector Prices
is now populated with an array of numeric values, and we can safely call
our implementation function, CalcHistVol()
.
The call to XlReadVector(...)
also does
some other useful work.
If the user has left some of the cells at the end of the input range empty,
XlReadVector() will truncate them, rather than putting null values into
Price
.
Add code to the function to call our stand-alone function, as shown below:
CXlOper* HISTVOL_Impl(CXlOper& xloResult, const CXlOper* Prices_op) { // Input buffers std::vector<double> Prices; // Validate and translate inputs XlReadVector(*Prices_op, Prices, L"Prices", XLA_TRUNC_ONEMPTY| XLA_TRUNC_ONBLANK); // End of generated code //}}XLP_SRC try { xloResult = CalcHistVol(Prices, 250.0); } catch(const char*) { xloResult = xlerrNum; } return xloResult.Ret(); }
Depending on whether CalcHistVol() succeeds, we will either set CXlOper to contain the result of the calculation, or set it to contain the error code xlerrNum, indicating that the inputs were out of range is some way.
Our add-in function is now complete and we're ready to build and test it.