The application implements three variations of a "Concat()" function. Each variation of the function is called once with ASCII input (supported by Excel 4 and 12) and once with Unicode input (supported by Excel 12 only).
| Function | Description |
| Concat() |
|
| Concat4() |
|
| Concat12() |
|
Function Concat() reflects the primary objectives for the XLW upgrade. The function retains old Excel 4 compatible behavior if run under Excel 4, but when called from Excel 12 the same function picks up new Excel 12 functionality, in this case Unicode support.
The flexibility in function Concat() incurs a performance overhead at run time. This overhead is avoided in functions Concat4() and Concat12() which target specific versions of Excel.
class XlfOper { LPXLOPER lpxloper_; public: std::string AsString(); //... };
LPXLOPERs passed from Excel to the Addin are received by Addin functions into arguments of type XlfOper. Consider XLW example function xlConcat(), which accepts as input two strings which are concatenated and returned. This function is registered with Excel such that Excel thinks the datatype of the input parameters is LPXLOPER.
LPXLOPER xlConcat(LPXLOPER xlStr1, LPXLOPER xlStr2);
In fact the function is declared as accepting input parameters of type XlfOper.
Excel passes arguments of type LPXLOPER, which are received by the function into parameters of type XlfOper. The XlfOper constructor is not invoked, the LPXLOPER value passed by Excel simply populates the LPXLOPER data member of the XlfOper argument. XlfOper's member functions then serve as functionality bound to that reference, and the implementation of xlConcat() accesses the LPXLOPER via its XlfOper wrapper.
std::string str1 = xlfStr1.AsString();
The operation above exploits the fact that no type checking is performed in this call across DLL boundaries. This is undefined behavior, which happens to work with all versions of Visual C++ (so far) but there is no guarantee that any compiler should support this feature and in particular MinGW (gcc) does not. MinGW stores PODs (such as LPXLOPER) differently than user defined types (such as XlfOper), and when compiled with MinGW the above code crashes at runtime.
To ensure type safety, the function must be declared exactly as it is registered with Excel - with parameters of type LPXLOPER. These values must then be passed explicitly to the XlfOper constructor.
LPXLOPER xlConcat(LPXLOPER xlStr1, LPXLOPER xlStr2) {
XlfOper xlfStr1(xlStr1);
std::string str1 = xlfStr1.AsString();
//...
}
class XlfOper { union { LPXLOPER lpxloper4_; LPXLOPER12 lpxloper12_; }; public: double AsDouble(); //... };
Now the class requires run time logic to determine which pointer to dereference. Ideally the switch is implemented via polymorphism, e.g. some abstract notion of an LPXLOPER, concretely instantiated as either LPXLOPER (4) or LPXLOPER12 depending on which version of Excel is detected. But no virtual function can be added to class XlfOper because the corresponding vtable would result in XlfOper no longer being bitwise equivalent to LPXLOPER.
The logic around the LPXLOPER/LPXLOPER12 reference is passed off to friend class XlfOperImpl. XlfOper can't hold a reference to XlfOperImpl so instead the latter is implemented as a polymorphic Singleton, an abstract base class which is instantiated at runtime by one of two concrete derived classes, XlfOperImpl4 or XlfOperImpl12, depending on which version of Excel is detected at startup.
class XlfOperImpl { static XlfOperImpl *instance_; public: static XlfOperImpl &instance() { return *instance_; } XlfOperImpl() { instance_ = this; } virtual double AsDouble(const XlfOper &xlfOper) = 0; }; class XlfOperImpl4 : public XlfOperImpl { virtual double AsDouble(const XlfOper &xlfOper) { return xlfOper.lpxloper4_->val.num; } }; class XlfOperImpl12 : public XlfOperImpl { virtual double AsDouble(const XlfOper &xlfOper) { return xlfOper.lpxloper12_->val.num; } };
Calls to XlfOper are forwarded to XlfOperImpl for execution by the appropriate derived class.
double XlfOper::AsDouble() { return XlfOperImpl::instance().AsDouble(*this); }
LPXLOPER xlCirc(XlfOper xlfDiam) { double diam = xlfDiam.AsDouble(); //... }
Or the alternative implementation where MinGW support is required:
LPXLOPER xlCirc(LPXLOPER xlDiam) {
XlfOper xlfDiam(xlDiam);
double diam = xlfDiam.AsDouble();
//...
}
The above code can be compiled without amendment under the new version of XLW into an XLL which is compatible with both Excel 4 and 12. The type XlfOper when run under Excel 12 automatically picks up support for Excel12 features. However both of the code examples above contain explicit references to the Excel 4 datatype LPXLOPER, and in those places the code does not acquire Excel 12 functionality.
Full interoperability requires a function which implements the LPXLOPER type when run under Excel 4 and the LPXLOPER12 datatype when run under Excel 12. This flexibility is achieved through use of the void* type, which accepts LPXLOPER or LPXLOPER12 depending on the version of Excel detected at runtime. The following typedef serves to clarify the intentions of the code:
typedef void* LPXLFOPER;
The function may then be implemented as
LPXLFOPER xlCirc(LPXLFOPER xlDiam) {
XlfOper xlfDiam(xlDiam);
double diam = xlfDiam.AsDouble();
//...
}
When running under Excel 4, XLW registers the above function as receiving and returning LPXLOPER. When running under Excel 12, XLW registers the above function as receiving and returning LPXLOPER12. The single source code implementation serves in either case and when run under Excel 12 the code dynamically picks up support for Excel 12 features.
Where MinGW compatibility is not a concern, the code can be simplified to
LPXLFOPER xlCirc(XlfOper xlfDiam) { double diam = xlfDiam.AsDouble(); //... }
The table below summarizes the advantages and disadvantages of the supported interfaces.
| Class | ||
| XlfOper |
|
|
| XlfOper4 |
|
|
| XlfOper12 |
|
|
XlfArgDesc diameter("diameter", "Diameter of the circle", "B"); XlfFuncDesc circ("xlCirc", "Circ", "Computes the circumference of a circle", "xlw Example"); circ.SetArguments(diameter); circ.Register();
XLRegistration::Arg CircArgs[] = {
{ "Diameter", "Diameter of the circle", "B"}
};
XLRegistration::XLFunctionRegistrationHelper registerCirc(
"xlCirc", "Circ", "Computes the circumference of a circle",
"xlw Example", CircArgs, 1, false);
| Feature | |||
| Value Type | XLF_OPER | P (OPER *) | Q (OPER12 *) |
| Reference Type | XLF_XLOPER | R (XLOPER *) | U (XLOPER12 *) |
| Return Value | LPXLFOPER (void *) | LPXLOPER (XLOPER *) | LPXLOPER12 (XLOPER12 *) |
| Memory Management Macros | EXCEL_BEGIN / EXCEL_END | EXCEL_BEGIN / EXCEL_END_4 | EXCEL_BEGIN / EXCEL_END_12 |
Value Type and Reference Type are codes recognized by XlfArgDesc (deprecated) and XLRegistration::Arg to register the inputs of a user defined addin function.
For more information, please see the definitions of the above terms in the Reference Manual, and refer to the example applications.
|
|
Hosted by |
|