enum {TASK_PENDING = 0, TASK_CURRENT = 1, TASK_READY = 2, TASK_UNCLAIMED = 4, TASK_COMPLETE = 8}; typedef struct tag_task { tag_task *prev; // prev task in list, NULL if this is top tag_
Trang 1324 Excel Add-in Development in C/C++
9.10.5 Organising the task list
The example in this section uses the following simple structure to represent a task Notethat a more sensible approach would be to use a Standard Template Library (STL) con-tainer class The, some would say, old-fashioned linked list used here could easily bereplaced with such a container The intention is not to propose the best way of codingsuch things, but simply to lay out a complete approach that can be modified to suit codingpreferences and experience
enum {TASK_PENDING = 0, TASK_CURRENT = 1, TASK_READY = 2,
TASK_UNCLAIMED = 4, TASK_COMPLETE = 8};
typedef struct tag_task
{
tag_task *prev; // prev task in list, NULL if this is top
tag_task *next; // next task in list, NULL if this is last
long start_clock; // set by TaskList class
long end_clock; // set by TaskList class
bool break_task; // if true, processing of this task should end
short status; // PENDING,CURRENT,READY,UNCLAIMED,COMPLETE
char *caller_name; // dll-internal Excel name of caller
bool (* fn_ptr)(tag_task *); // passed in function ptr
xloper fn_ret_val; // used for intermediate and final value
to be made about whether modified tasks are also moved to the end or left where theyare In the former case, the algorithm for deciding which task is next to be processedsimply goes to the next in the list In the latter case, it would need to start looking atthe top of the list, just in case a task that had already been completed had subsequentlybeen modified
Trang 2Miscellaneous Topics 325
The decision made here is that modified tasks are moved to the end of the list TheTaskList class, discussed below and listed in full on the CD ROM, contains threepointers, one to the top of the list, m_pHead, one to the bottom of the list, m_pTail,and one to the task currently being executed,m_pCurrent
A more sophisticated queuing approach would in general be better, for example, one
with a pending queue and a done queue, or even a queue for each state The above
approach has been chosen in the interests of simplicity
It is important to analyse how a list of these tasks can be altered and by what thread,background or foreground The pointersm_pHeadand m_pTailwill only be modified
by the foreground thread (Excel) as it adds, moves or deletes tasks Them_pCurrentpointer is modified by the background thread as it completes one task and looks for thenext one Therefore, the foreground thread must be extremely careful when accessing them_pCurrentpointer or assuming it knows what it is, as it can alter from one moment
to the next The foreground can freely read through the list of tasks but must use acritical section when altering a task that is, or could at any moment become, pointed
to bym_pCurrent If it wants to update m_pCurrent’s arguments, then it must firstbreak the task so that it is no longer current If it wants to change the order of tasks inthe list, it must enter a critical section to avoid this being done at the same time that thebackground thread is looking for the next task
By limiting the scope of the background thread to the value ofm_pCurrent, and thetask it points to, the class maintains a fairly simple thread-safe design, only needing touse critical sections in a few places
The strategy assigns a state to a task at each point in its life cycle Identifying thestates, what they mean, and how they change from one to another, is an important part ofmaking any complex multi-threaded strategy work reliably For more complex projectsthan this example, it is advisable to use a formal architectural design standard, such asUML, with integral state-transition diagrams For this example, the simple table of thestates below is sufficient
Table 9.8 Task states and transitions for a background thread strategy
Pending • The task has been placed on the list and is waiting its turn to be processed.
• The foreground thread can delete pending tasks.
Current • The state is changed from pending to current by the background thread with a
critical section
• The background thread is processing the task
• If the task’s execution is interrupted, its state goes back to pending
Ready • The task has been completed by the background thread which has changed the
state from current to ready
• The task is ready for the foreground loop to retrieve the result
Unclaimed • The foreground thread has seen that the task is either ready or complete and
has marked it as unclaimed pending recalculation of the workbook(s)
• If still unclaimed after a workbook recalculation, the task should be deleted
(continued overleaf )
Trang 3326 Excel Add-in Development in C/C++
Table 9.8 (continued )
Complete • The recalculation of the worksheet cell (that originally scheduled the task)
changes the state from unclaimed to complete
• The task has been processed and the originating cell has been given the final value
• A change of inputs will change the status back to pending
The unclaimed state ensures that the foreground thread can clean up any orphaned tasks:those whose originating cells have been deleted, overwritten, or were in worksheets thatare now closed The distinction between ready and unclaimed ensures that tasks completed
immediately after a worksheet recalculation don’t get mistakenly cleaned up as unclaimed
before their calling cell has had a chance to retrieve the value
9.10.6 Creating, deleting, suspending, resuming the thread
In this example, where management of the thread is embedded in a class, the most obviousplace to start and finally stop the thread might seem to be the constructor and destructor
It is preferable, in fact, to have more control than this and start the thread with an explicitcall to a class member function, ideally from xlAutoOpen Similarly, it is better todelete the thread in the same way fromxlAutoClose
Threads under Windows can be created in a suspended state This gives you two choicesabout how you run your thread: firstly, you can create it in a suspended state and bring it
to life later, perhaps only when it has some work to do Secondly, you can create it in anactive state and have the main function that the thread executes loop and sleep until there
is something for it to do Again for simplicity, the second approach has been adopted inthis example
Similarly, when it comes to suspending and resuming threads, there are two Windowscalls that will do this Or you can set some flag in foreground that tells your backgroundloop not to do anything until you reset the flag The latter approach is simpler and easier todebug, and, more importantly, it also allows the background thread to clean up its currenttask before becoming inactive For these reasons, this is the approach chosen here
9.10.7 The task processing loop
Most of the code involved in making this strategy work is not listed in this book (It isincluded on the CD ROM in the source filesBackground.cppand Background.hwhich also call on other code in the example project.) Nevertheless, it is helpful to discussthe logic in this code behind the main function that the thread executes (When creatingthe thread, the wrapper functionbackground thread main()is passed as an argu-ment together with a pointer to the instance of theTaskList class that is creating thethread.) The loop references three flags, all private class data members, that are used tosignal between the fore- and background threads These are:
• m ThreadExitFlagSet: Signals that the thread should exit the loop and return,thereby terminating the thread This is set by the foreground thread in theDeleteTaskThread()member function of the TaskListclass
Trang 4Miscellaneous Topics 327
• m SuspendAllFlagSet: Signals that the background thread is to stop (suspend)processing tasks after the next task has been completed This is set by the fore-ground thread in the SuspendTaskThread() member function of the TaskListclass
• m ThreadIsRunning: This flag tells both the background and foreground threadswhether tasks are being processed or not It is cleared by the background thread inresponse to m SuspendAllFlagSet being set This gives the foreground thread
a way of confirming that the background thread is no longer processing tasks It isset by the foreground thread in theResumeTaskThread()member function of theTaskListclass
// This is the function that is passed to Windows when creating
// Find next task to be executed Sets m_pCurrent to
// point to the next task, or to NULL if no more to do.
Trang 5328 Excel Add-in Development in C/C++
9.10.8 The task interface and main functions
In this example, the only constraint on the interface function is that it is registered asvolatile It is also helpful to register it as a macro-sheet equivalent function which onlytakesoperarguments Its responsibilities are:
1 To validate arguments and place them into an array ofxlopers
imple-// LongTaskExampleMain() executes the task and does the work.
// It is only ever called from the background thread It is
// required to check the break_task flag regularly to see if the
// foreground thread needs execution to stop It is not required
// that the task populates the return value, fn_ret_val, as it does
// in this case It could just wait till the final result is known.
bool long_task_example_main(tag_task *pTask)
Trang 6// LongTaskExampleInterface() is a worksheet function called
// directly by Excel from the foreground thread It is only
// required to check arguments and call ExampleTaskList.UpdateTask()
// which returns either an error, or the intermediate or final value
// of the calculation UpdateTask() errors can be returned directly
// or, as in this case, the function can return the current
// (previous) value of the calling cell This function is registered
// with Excel as a volatile macro sheet function.
xloper * stdcall LongTaskExampleInterface(xloper *arg)
xloper arg_array[1]; // only 1 argument in this case
static xloper ret_val;
// UpdateTask makes deep copies of all the supplied arguments
// so passing in an array of shallow copies is safe.
arg_array[0] = *arg;
// As there is only one argument in this case, we could instead
// simply pass a pointer to this instead of creating the array
Trang 7330 Excel Add-in Development in C/C++
}
ret_val.xltype |= xlbitDLLFree; // memory to be freed by the DLL
return &ret_val;
}
9.10.9 The polling command
The polling command only has the following two responsibilities:
• Detect when a recalculation is necessary in order to update the values of volatile longtask functions (In the example code below the recalculation is done on every call intothe polling function.)
• Reschedule itself to be called again in a number of seconds determined by a configurableTaskListclass data member
int stdcall long_task_polling_cmd(void)
{
if(ExampleTaskList.m_BreakPollingCmdFlag)
return 0; // return without rescheduling next call
// Run through the list of tasks setting TASK_READY tasks to
// TASK_UNCLAIMED Tasks still unclaimed after recalculation are
// assumed to be orphaned and deleted by DeleteUnclaimedTasks().
bool need_racalc = ExampleTaskList.SetDoneTasks();
// if(need_racalc) // Commented out in this example
{
// Cause Excel to recalculate This forces all volatile fns to be
// re-evaluated, including the long task functions, which will then
// return the most up-to-date values This also causes status of
// tasks to be changed to TASK_COMPLETE from TASK_UNCLAIMED.
// Use command name as given to Excel in xlfRegister 4th arg
cpp_xloper CmdName("LongTaskPoll"); // as registered with Excel
Trang 8Miscellaneous Topics 331
9.10.10 Configuring and controlling the background thread
TheTaskList::CreateTaskThread()member function creates a thread that is active
as far as the OS is concerned, but inactive as far as the handling of background worksheetcalculations is concerned The user, therefore, needs a way to activate and deactivate thethread and the polling command
As stressed previously, the C API is far from being an ideal way to create dialogsthrough which the user can interact with your application In this case, however, it is veryconvenient to place a dialog within the same body of code as the long task functions Youcan avoid using C API dialogs completely by exporting a number of accessor functionsand calling them from a VBA dialog
The example project source file, Background.cpp, contains a command functionlong task config cmd(), that displays the following C API dialog that enables the
user to control the thread and see some very simple statistics (See section 8.13 Working
with custom dialog boxes on page 273.)
Figure 9.1 Long task thread configuration dialog
This dialog needs to be accessed from either a toolbar or menu The same source filealso contains a command function long task menu setup()that, when called forthe first time, sets up a menu item on theToolsmenu (A second call removes this menuitem.) (The spreadsheet used to design and generate the dialog definition table for thisdialog,XLM ThreadCfg Dialog.xls, is included on the CD ROM.)
9.10.11 Other possible background thread applications and strategies
The strategy and example outlined above lends itself well to certain types of lengthybackground calculations There are other reasons for wanting to run tasks in background,most importantly for communicating with remote applications and servers Examples
of this are beyond the scope of this book, but can be implemented fairly easily as anextension to the above One key difference in setting up a strategy for communication
between worksheet cells and a server is the need to include a sent/waiting task state that
enables the background thread to move on and send the next task without having to waitfor the server to respond to the last The other key difference is that the backgroundthread, or even an additional thread, must do the job of checking for communication backfrom the server
Trang 9332 Excel Add-in Development in C/C++
This section is, of course, about how not to crash Excel Old versions of Excel were not
without their problems, some of which were serious enough to cause occasional crashesthrough no fault of the user This has caused some to view Excel as an unsafe choicefor a front-end application This is unfair when considering modern versions Excel, iftreated with understanding, can be as robust as any complex system Third-party add-insand users’ own macros are usually the most likely cause of instability This brief sectionaims to expose some of the more common ways that these instabilities arise, so that theycan be avoided more easily
There are a few ways to guarantee a crash in Excel One is to call the C API whenExcel is not expecting it: from a thread created by a DLL or from a call-back functioninvoked by Windows Another is to mismanage memory Most of the following examplesinvolve memory abuse of one kind or another
If Excel allocated some memory, Excel must free it If the DLL allocated some memory,the DLL must free it Using one to free the other’s memory will cause a heap error Over-running the bounds of memory that Excel has set aside for modify-in-place arguments toDLL functions is an equally effective method of bringing Excel to its knees Over-runningthe bounds of DLL-allocated memory is also asking for trouble
Passingxlopertypes with invalid memory pointers toExcel4()will cause a crash.Such types are strings (xltypeStr), external range references (xltypeRef), arrays(xltypeMulti) and string elements within arrays
Memory Excel has allocated in calls to Excel4() or Excel4v() should be freedwith calls to xlFree Leaks resulting from these calls not being made will eventuallyresult in Excel complaining about a lack of system resources Excel may have difficultyredrawing the screen, saving files, or may crash completely
Memory can be easily abused within VBA despite VB’s lack of pointers For example,overwriting memory allocated by VB in a call toString(), will cause heap errors thatmay crash Excel
Great care must be taken where a DLL exposes functions that take data types that are (orcontain) pointers to blocks of memory Two examples of this are strings andxl arrays
(See section 6.2.2 Excel floating-point array structure: xl array on page 107.) The
danger arises when the DLL is either fooled into thinking that more memory has beenallocated than is the case, say, if the passed-in structure was not properly initialised, or
if the DLL is not well behaved in the way it reads or writes to the structure’s memory
In the case of thexl array, whenever Excel itself is passing such an argument, it can
be trusted Where this structure has been created in a VB macro by the user’s own code,care must be taken Such dangers can usually be avoided by only exposing functions that
take safe arguments such asVARIANTorBSTRstrings andSAFEARRAYs
Excel is very vulnerable to stress when it comes close to the limits of its availablememory Creating very large spreadsheets and performing certain operations can crashExcel, or almost as bad, bring it to a virtual grinding halt Even operations such as copy
or delete can have this effect Memory leaks will eventually stress Excel in this way.Calls to C API functions that take array arguments, xlfAddMenufor example, maycrash Excel if the arrays are not properly formed One way to achieve this is to havethe memory allocated for the array to be smaller than required for the specified rowsand columns
Trang 10Miscellaneous Topics 333
There are some basic coding errors that will render Excel useless, although not sarily crashing it, for example, a loop that might never end because it waits for a conditionthat might never happen From the user’s perspective, Excel will be dead if control hasbeen passed to a DLL that does this
neces-A more subtle version of the previous problem can occur when using a backgroundthread and critical sections Not using critical sections to manage contention for resources
is, in itself, dangerous and inadvisable However, if thread A enters a critical section and
then waits for a state to occur set by thread B, and if thread B is waiting for thread A to
leave the critical section before it can set this state, then both threads effectively freezeeach other Careful design is needed to avoid such deadlocks
Only slightly better than this are DLL functions, especially worksheet functions, thatcan take a very large amount of time to complete Worksheet functions cannot reportprogress to the user It is, therefore, extremely important to have an idea of the worst-case execution time of worksheet functions, say, if they are given an enormous range toprocess If this worst-case time is unacceptable, from the point of view of Excel appearing
to have hung, then you must either check for and limit the size of your inputs or use
a background thread and/or remote process Or your function can check for user breaks(the user pressing Esc in Windows) – see section 8.7.7 on page 206
Care should be taken with some of the C API functions that request information about
or modify Excel objects For example, xlSheetNm must be passed a valid sheet IDotherwise Excel will crash or become unstable
Trang 1210 Example Add-ins and Financial Applications
Developers are always faced with the need to balance freedoms and constraints whendeciding the best way to implement a model Arguably the most important skill a devel-oper can have is that of being able to choose the most appropriate approach all thingsconsidered: Failure can result in code that is cumbersome, or slow, or difficult to maintain
or extend, or bug-ridden, or that fails completely to meet a completion time target.This chapter aims to do two things:
1 Present a few simple worksheet function examples that demonstrate some of the basicconsiderations, such as argument and return types For these examples source code isincluded on the CD ROM in the example project Sections 10.1 to 10.5 cover thesefunctions
2 Discuss the development choices available and constraints for a number of cial markets applications These applications are not fully worked through in the book,and source code is not provided on the CD ROM Sections 10.6 and beyond coverthese functions and applications
finan-Some of the simple example functions could easily be coded in VB or duplicated withperhaps only a small number of worksheet cells The point is not to say that these thingscan only be done in C/C++ or using the C API If you have decided that you want orneed to use C/C++, these examples aim to provide a template or guide
The most important thing that an add-in developer must get right is the function interface.The choices made as to the types of arguments a function takes, are they required or optional;
if optional what the default behaviour is; and so on, are often critical Much of the discussion
in this chapter is on this and similar issues, rather than on one algorithm versus another.The discussion of which algorithm to use, etc., is left to other texts and to the reader whoseown experience may very well be more informed and advanced than the author’s
Important note: You should not rely on any of these examples, or the methods they
contain, in your own applications without having completely satisfied yourself thatthey are correct and appropriate for your needs They are intended only to illustratehow techniques discussed in earlier chapters can be applied
Excel has a number of very efficient basic string functions, but string operations canquickly become unnecessarily complex when just using these Consider, for example, thecase where you want to substitute commas for stops (periods) dynamically This is easilydone using Excel’s SUBSTITUTE() However, if you want to simultaneously substitutecommas for stops and stops for commas things are more complex (You could do this inthree applications ofSUBSTITUTE(), but this is messy.) Writing a function in C that doesthis is straightforward (seereplace_mask()below)
Trang 13336 Excel Add-in Development in C/C++
The C and C++ libraries both contain a number of low-level string functions that caneasily be given Excel worksheet wrappers or declared and used from VBA (The latter
is a good place to start when optimising VB code.) This section presents a number ofexample functions, some of which are just wrappers of standard library functions andsome of which are not The code for all of these functions is listed in the Example project
on the CD ROM in the source fileXllStrings.cpp When registered with Excel, theyare added to the Text category
Function
name
count_char(exported)CountChar(registered with Excel)Description Counts the number of occurrences of a given character
Prototype short stdcall count_char(char *text, short ch);Type string "ICI"
Notes Safe to return a short as Excel will only pass a 255-max character
string to the function Function does not need to be volatile anddoes not access any C API functions that might require it to beregistered as a macro sheet equivalent function
short stdcall count_char(char *text, short ch)
corresponding characters from a replacement string, or removes all
such occurrences if no replacement string is provided
Prototype void stdcall replace_mask(char *text, char
*old_chars, xloper *op_new_chars);
Type string "1CCP"
Trang 14Example Add-ins and Financial Applications 337
Notes Declared as returning void Return value is the 1st argument
modified in place Third argument is optional and passed as anoper(see page 119) to avoid the need to dereference a range reference
void stdcall replace_mask(char *text, char *old_chars, xloper
// Remove all occurrences of all characters in old_chars
for(; *text; text++)
*p = p[1];
} while (*(++p));
Trang 15338 Excel Add-in Development in C/C++
Function name reverse_text(exported)
Reverse(registered with Excel)Description Reverses a string
Prototype void stdcall reverse_text(char *text);
Type string "1F"
Notes Declared as returning void Return value is the 1st argument
modified in place This function is simply a wrapper for the Clibrary functionstrrev() This function is useful in thecreation of Halton quasi-random number sequences, for example
void stdcall reverse_text(char *text)
search string, or zero if none found
Prototype short stdcall first_inclusive(char *text, char
*search_text);
Type string "ICC"
Notes Any error in input is reflected with a zero return value, rather than
an error type This function is simply a wrapper for the C libraryfunctionstrpbrk()
short stdcall find_first(char *text, char *search_text)
Trang 16Example Add-ins and Financial Applications 339
Function
name
find_first_excluded(exported)
FindFirstExcl(registered with Excel)
Description Returns the position of the first occurrence of any character that is not
in a search string, or zero if no such character is found
Prototype short stdcall find_first_excluded(char *text,
char * search_text);
Type string "ICC"
Notes Any error in input is reflected with a zero return value, rather than an
zero if not found
Prototype short stdcall find_last(char *text, short ch);Type string "ICI"
Notes Any error in input is reflected with a zero return value, rather than
an error type This function is simply a wrapper for the C libraryfunction strrchr()
short stdcall find_last(char *text, short ch)
{
if(!text || ch <= 0 || ch > 255)
return 0;
Trang 17340 Excel Add-in Development in C/C++
B (return 1), case sensitive (by default) or not
Prototype xloper * stdcall compare_text(char *Atext,
char *Btext, xloper *op_is_case_sensitive);Type string "RCCP"
Notes Any error in input is reflected with an Excel#VALUE! error Return
type does not need to allow for referencexlopers Excel’scomparison operators<, > and = are not case-sensitive and Excel’s
EXACT()function only performs a case-sensitive check for equality.This function is a wrapper for the C library functionsstrcmp()andstricmp()
xloper * stdcall compare_text(char *Atext, char *Btext,
xloper *op_is_case_sensitive) {
static xloper ret_oper = {0, xltypeNum};
(return 0), A< B (return −1), A > B (return 1), case sensitive (by
default) or not
Trang 18Example Add-ins and Financial Applications 341
Prototype xloper * stdcall compare_nchars(char *Atext,
char *Btext, short n_chars, xloper
*op_is_case_sensitive);
Type string "RCCIP"
Notes Any error in input is reflected with an Excel#VALUE!error Return
type does not need to allow for referencexlopers This function is
a wrapper for the C library functionsstrncmp()andstrincmp()
xloper * stdcall compare_nchars(char *Atext, char *Btext,
short n_chars, xloper *op_is_case_sensitive) {
static xloper ret_oper = {0, xltypeNum};
if(!Atext || !Btext || n_chars <= 0 || n_chars > 255)
Concat(registered with Excel)
Description Concatenate the contents of the given range (row-by-row) using the
given separator (or comma by default) Returned string length limit is
255 characters by default, but can be set lower Caller can specify thenumber of decimal places to use when converting numbers
Prototype xloper * stdcall concat(xloper *inputs, xloper
*p_delim, xloper *p_max_len, xloper *p_num_decs);Type string "RPPPP"
xloper * stdcall concat(xloper *inputs, xloper *p_delim,
Trang 19342 Excel Add-in Development in C/C++
if(num_decs >= 0 && num_decs < 16
&& Inputs.GetArrayElementType(i) == xltypeNum)
{
xloper *p_op = Inputs.GetArrayElement(i);
Excel4(xlfRound, p_op, 2, p_op, &Rounding);
Function name parse(exported)
ParseText(registered with Excel)
Trang 20Example Add-ins and Financial Applications 343
Description Parse the input string using the given separator (or comma by
default) and return an array Caller can request conversion of allfields to numbers, or zero if no conversion possible Caller canspecify a value to be assigned to empty fields (zero by default).Prototype xloper * stdcall parse(char *input, xloper
*p_delim, xloper *p_numeric, xloper *p_empty);Type string "RCPP"
Notes Registered name avoids conflict with the XLMPARSE()function
xloper * stdcall parse(char *input, xloper *p_delim,
xloper *p_numeric, xloper *p_empty) {
// Can' t use strtok as it ignores empty fields
char *p_last = input;
Trang 21344 Excel Add-in Development in C/C++
in Excel 2002.) This can lead to accumulated errors in some cases or complete failure.The functionNORMSDIST(X)is accurate to about±7.3 × 10−8and appears to be based onthe approximation given in Abramowitz and Stegun (1970), section 26.2.17, except thatforX> 6 it returns 1 andX< −8.3 it returns zero.2
There is no Excel function that returns a random sample from the normal distribution.The compoundNORMSINV(RAND())will provide this, but is volatile and therefore may not
be desirable in all cases In addition to its volatility, it is not the most efficient way tocalculate such samples
1 See Jackson and Staunton (2001) for numerous examples of applications of these functions to finance.
2 Inaccuracies in these functions could cause problems when, say, evaluating probability distribution functions from certain models.