=============================================================================== version_tracker.txt for WhiskerClientLib (part of the Whisker SDK) =============================================================================== Version information in - WhiskerTask.h (comment at top) - strWhiskerTaskLibraryVersion in WhiskerTask.cpp - SDK.iss - web site whatsnew.shtml SEE ALSO \Whisker\Distribution\Distribution files\Whisker_SDK_Readme - version history for users. Version 2.1 (Rudolf Cardinal) =============================================================================== This and previous versions not released outside the University of Cambridge. Version 2.2 (Rudolf Cardinal & Mike Aitken) =========================================== Addition of 2 commands: DisplayDeleteAllDocuments DisplayDeleteAllDevices BugFix: DisplayKeyboardEvents() was incorrectly described in the type library Previous (wrong): DisplayKeyboardEvents(Device, EventClass) Now (correct): DisplayKeyboardEvents(Document, EventClass) Version 2.2.1 (Mike Aitken) =============================================================================== Modification to TimeStamps; no change to end-user interface. * Major Change: TimeStamps Two versions of IncomingXxx messages, both including & not including timestamps. Default action 'timestamp' version is to call unstampted version. Clients built with previous version should therefore still build OK; addition to overheads is trivial. #Bug Fixes & Changes #WCL1 Error: messages are parsed as 'others' and anything not prefixed is parsed as 'errors' is this a bug in ClientLib? //changed #WCL2 ClientMessage: messages are sent both as Client Messages and as Server Messages. #WCL3 ClaimLine (number/alias version) fails unexpectedly // Aha - ClaimLine made up a number as the value was missing from the Format() call. Fixed. #WCL4 Timestamps. Changed Parse() to fire timestamps when they're used #wCL5 QuerySize now works. #WCL6 SetEventTrans called wrong function. //fixed NOW PART OF WHISKER SDK Changed to use the new style command language. Back compatibility with Cambridge headers, etc. Added support for new commands (DisplayDeleteAllDevices & DisplayDeleteAllDocuments) Version 2.3 =============================================================================== Change to the meaning of Linestate routines. This may require code to be upgraded in VB code, if 'Not' was used to reverse a line state New behaviour: wsLineState: wsOn = 1, wsOff = 0, wsUnknown = 2 LineState()[get property] or LineReadState() return: wsOn, wsOff, or wsUnknown (if line cannot be read) LineState [set property] can be set to: wsOn, wsOff, OR -1 (True in VB). Previously: Functions operated as booleans: 'True' (as defined by the language: if line was on) 'False' (if line was off or could not be read) Version 2.4 =============================================================================== Version 2.4.1 =============================================================================== * v2.4.1 - 30 April 2003: RNC bugfix. (No change to headers.) If the client passed filenames (e.g. AudioLoadWav...) that had spaces in them, the server rejected the calls, because quotes were not being added. Changed (adding strQuote) in: AudioPlayWav AudioLoadWav DisplayAddObject_Bitmap Version 2.5 (RNC) =============================================================================== As v2.4 but debug libraries added to distribution. New CWhiskerTask::LibraryVersion() call. Version 2.6 (RNC) =============================================================================== AudioSilenceAllDevices() added AudioGetSoundLength() added DisplaySetBackgroundEvent() added DisplayClearBackgroundEvent() added Version 2.7 (RNC) =============================================================================== Authenticate() added AuthenticateResponse() added LogOpen() added LogPause() added LogResume() added LogSetOptions() added LogWrite() added LogClose() added Version 2.8 (RNC 7 Aug 2003) =============================================================================== DisplayAddObject_CamcogQuadPattern() added [Minor change: format of warning messages improved as of next build] Version 2.9 (MRFA 30 March 2004) =============================================================================== Minor Change to RemoveSchedule (status message for each schedule removed) Added Duration option to AudioLoadTone Potential memory leak from Schedule vector. Fixed. (RNC 8 May 2004; .ISS modification only; changed distribution executable version to 2.9 from 2.8) - distribute whiskermessages.h with SDK, and other appropriate files from "common" directory Version 2.91 (RNC 3 Sep 2004 and 15 Nov 2004) =============================================================================== - Debug version of WhiskerClientLib crashes sometimes at CWhiskerTask::SocketClose(), at the "delete m_pMainSocket;" call. ASSERT is in DBGHEAP.C: /* Error if freeing incorrect memory type */ _ASSERTE(pHead->nBlockUse == nBlockUse); Stack trace looks like this: from void CFiveChoiceDlg::OnCancel() to void CWhiskerTask::ShutDown() to void CWhiskerTask::DisconnectFromServer() to void CWhiskerTask::SocketClose() if (m_pMainSocket) { m_pMainSocket->Close(); via void CMultiSocket::Close() CSocket::Close(); back to void CWhiskerTask::SocketClose() delete m_pMainSocket; // for some reason, ASSERTs in debug version, in DBGHEAP.C: "/* Error if freeing incorrect memory type */ _ASSERTE(pHead->nBlockUse == nBlockUse);" -- see version_tracker to CMultiSocket::`scalar deleting destructor`(unsigned int) 00435400 push ebp 00435401 mov ebp,esp 00435403 sub esp,44h 00435406 push ebx 00435407 push esi 00435408 push edi 00435409 push ecx 0043540A lea edi,[ebp-44h] 0043540D mov ecx,11h 00435412 mov eax,0CCCCCCCCh 00435417 rep stos dword ptr [edi] 00435419 pop ecx 0043541A mov dword ptr [ebp-4],ecx 0043541D mov ecx,dword ptr [ebp-4] 00435420 call CMultiSocket::~CMultiSocket (00435450) 00435425 mov eax,dword ptr [ebp+8] 00435428 and eax,1 0043542B test eax,eax 0043542D je CMultiSocket::`scalar deleting destructor'+38h (00435438) 0043542F mov ecx,dword ptr [ebp-4] 00435432 push ecx 00435433 call CObject::operator delete (00437e10) to FIVECHOICE! CObject::operator delete(void *) ... 00437E15 call operator delete (0043effe) // so source for that (CObject::operator delete) should be one of the following three [AFXMEM.CPP]: #ifdef _DEBUG // most of this file is for debugging ... void PASCAL CObject::operator delete(void* p) { #ifdef _AFX_NO_DEBUG_CRT free(p); #else _free_dbg(p, _CLIENT_BLOCK); // <--------- *** so why isn't this being called? #endif } #if _MSC_VER >= 1200 void PASCAL CObject::operator delete(void* p, void*) { #ifdef _AFX_NO_DEBUG_CRT free(p); #else _free_dbg(p, _CLIENT_BLOCK); #endif } #endif ... #ifndef _AFX_NO_DEBUG_CRT #if _MSC_VER >= 1200 void PASCAL CObject::operator delete(void *pObject, LPCSTR /* lpszFileName */, int /* nLine */) { #ifdef _AFX_NO_DEBUG_CRT free(pObject); #else _free_dbg(pObject, _CLIENT_BLOCK); #endif } #endif ... #endif // _AFX_NO_DEBUG_CRT #endif // _DEBUG // and yet all three call free() or free_dbg(), not ::operator delete(void *p)... /// so NONE of them is being called... to void __cdecl operator delete(void* p) [AFXMEM.CPP] _free_dbg(p, _NORMAL_BLOCK); // *** RNC: problem is here? // Full source for that segment is this: void __cdecl operator delete(void* p) { #if !defined(_AFX_NO_DEBUG_CRT) && defined(_DEBUG) _free_dbg(p, _NORMAL_BLOCK); // <------------------ this call #else free(p); #endif } to _CRTIMP void __cdecl _free_dbg(void * pUserData, int nBlockUse) ... _free_dbg_lk(pUserData, nBlockUse); to void __cdecl _free_dbg_lk((void * pUserData, int nBlockUse) _CrtMemBlockHeader * pHead; ... pHead = pHdr(pUserData); ... /* Error if freeing incorrect memory type */ _ASSERTE(pHead->nBlockUse == nBlockUse); Aha. Now we're getting somewhere: see MFC help under "Types of Blocks on the Debug Heap" ... _NORMAL_BLOCK A call to malloc or calloc creates a Normal block. If you intend to use Normal blocks only, and have no need for Client blocks, you may want to define _CRTDBG_MAP_ALLOC, which causes all heap allocation calls to be mapped to their debug equivalents in debug builds. This will allow file name and line number information about each allocation call to be stored in the corresponding block header. ... _CLIENT_BLOCK An application can keep special track of a given group of allocations for debugging purposes by allocating them as this type, using explicit calls to the debug heap functions. MFC, for example, allocates all CObjects as Client blocks; other applications might keep different memory objects in Client blocks. Subtypes of Client blocks can also be specified for greater tracking granularity. To specify subtypes of Client blocks, shift the number left by 16 bits and OR it with _CLIENT_BLOCK. For example: #define MYSUBTYPE 4 _free_dbg(pbData, _CLIENT_BLOCK|(MYSUBTYPE<<16)); A client-supplied hook function for dumping the objects stored in Client blocks can be installed using _CrtSetDumpClient, and will then be called whenever a Client block is dumped by a debug function. Also, _CrtDoForAllClientObjects can be used to call a given function supplied by the application for every Client block in the debug heap. Note that again: "MFC... allocates all CObjects as Client blocks"... so why is it trying to free the memory as a Normal block? Yes. Compare it to the creation process: from int CWhiskerTask::Initialise(const CString& servername, int port, const CString& boxname) if ((m_pMainSocket = new CMultiSocket(this))==NULL) to void* PASCAL CObject::operator new(size_t nSize, LPCSTR lpszFileName, int nLine) return ::operator new(nSize, _CLIENT_BLOCK, lpszFileName, nLine); to void* __cdecl operator new(size_t nSize, int nType, LPCSTR lpszFileName, int nLine) So CObject::operator new is using a _CLIENT_BLOCK for allocation, but CObject::operator delete is using a _NORMAL_BLOCK for destruction. Perhaps the answer is this: Q122675 in the Microsoft KB (see also Q121216) http://support.microsoft.com/default.aspx?scid=kb;en-us;122675 - but making CMultiSocket::~CMultiSocket() non-virtual didn't fix it... - and declaring #define _declspec(dllexport) and #define _declspec(dllimport) (with CMultiSocket::~CMultiSocket() virtual) didn't fix it... - Mersenne Twister random-number generator added, 15 Nov 2004. http://www.math.sci.hiroshima-u.ac.jp/%7Em-mat/MT/emt.html http://home.ecn.ab.ca/~jsavard/crypto/co4814.htm http://www.bedaux.net/mtrand/ http://www-personal.engin.umich.edu/~wagnerr/MersenneTwister.html http://www-personal.engin.umich.edu/~wagnerr/MersenneTwister.h Note that [0,3) includes 0 but not 3: square brackets [] are inclusive and round brackets () are exclusive. Version 2.92 (RNC 9 Dec 2004) =============================================================================== * DisplayGetDocumentSize command added * DisplayGetObjectExtent command added * added "if (in == WSM_FAILURE) return false;" to DisplayGetSize Version 2.93 (MRFA Jan 2005) =============================================================================== * ... something to do with multithreaded access to schedules (see below)... * ... uses a CMclCritSec; therefore must compile/link in MCL code (otherwise clients have to) ... achieved by adding MCL.LIB to project Version 2.94 (MRFA Feb 2005) =============================================================================== - Schedule code problems. Failures in 2.91: 1) Removing a schedule during the processing of a scheduled event could mean that the iterator for the scheduled event would be invalidated. Similar issues could arise with 2 threads (GUI and socket) Need a 'housekeeping' arrangement. 2) Removing an event did not match empty events (fixed again... version control issue.) 3) Killing an event does not stop its schedules. - Yikes! Address exceptions everywhere with a piece of test code. Arse. [insert 2 nights and one day of solid confusion, agony and no food] (1) above was not the only problem In fact, the problem appears to arise when schedules are added *as the result of event callback in a CSchedule object*. Aha - there is an iterator in the Event Callback. My guess is that this is invalidated if the callback in turn calls AddSchedule, which pushes to the back of the vector... void CWhiskerTask::ScheduleEvent_Callback(const CString &Param, long lTime) { CMclAutoLock autoLock(m_scheduleVectorCritSec); std::vector::iterator i; for(i = m_vSchedules.begin(); i != m_vSchedules.end();++i) { (*i)->Event(Param, lTime); } Replace with the [] operator. Version 2.11 (RNC 17-Mar-2005; MRFA note. version number => server v. number) =============================================================================== - Text alignment, as extra optional parameters to DisplayAddObject_Text. [Note: other change not documented earlier. DisplayCacheChanges & DisplayShowChanges. Show returnss long not boolean, and returns 0 on failure, or timestamp (if used)] Version 2.12 (MRFA 06-Jan-2006) =============================================================================== [From versiontracker.txt in WhiskerServer Project] - perhaps: option to see multiple server-copy displays simultaneously. Background: Spencer Tye (Merck): Is there any way on the Whisker system that i can display all of the displays the monkeys are seeing side by side on the server screen, basically i want to be able to see what the subjects are seeing so that i can monitor what they are all upto. I've only been able to do this one screen at a time so far. RNC: (1) COSMETICS. It would probably involve a new "left-hand" display tiling n ?possible/?active screens into a display divided into (sqrt(n) rounded up to the next whole number)^2 boxes. I suppose it would need a special (RHS) frame window (call it CMultipleDisplayFrameWnd) subdivided into further (normal display - CDisplayFrameWnd) frame windows. The document update notification system would probably be OK (but see below). The mouse-mapping systems would probably be OK. The display testing menus would be simplest if we treated "looking at the monster window" as being equivalent to "not having any particular display selected" (i.e. "Test All Displays" would work; "Test This Display" wouldn't be available). (2) UNDERLYING. The right-hand view is part of a CSplitterWnd frame window. CWSLeftView::SwitchViewInSplitter does the business on the right-hand view. It's suppled with CRuntimeClass *pViewClass, CDocument* pNewDoc. All the right-hand views are set up to have a single document, and this modification would involve having multiple documents (perhaps necessitating the creation of some sort of document-that-owns-lots-of-documents, so that deselecting the window cleaned everything up afterwards). This is the main issue of complexity that I can see at first glance. So non-trivial. MRFA: Not sure that the multiple documents on a single view is going to work well, for the reasons you suggest. A lot of possibility for breaking the architecture! A (simpler) possibility might be to use separate windows for the monitor displays (these could be handled as if the client had tried to allocate a 'virtual' display - using the existing CClientOwnedDisplayDevice [or whatever it is called] The user can then arrange / tile and size these as required.... This would suffice if these extra windows (1) always attach to the document *after* after the CDisplayDevice [to ensure that these get optimised drawing] (2) display touches are shown (3) are resizable and scale the document... This (latter) suggestion now implemented. A new 'debugview' option to DisplayClaim has been added to the ClientLibrary this option means that the Client library will add a second window (debug view) for a claimed device. Minor changes to Server (2.12.1) allow the second device to show touches. Limitations (but I think these are entirely reasonable) : Only one debug view per client (not one per display). But no plans to have single clients : handling multiple displays. : This only works if the same 'label' is used for the display throughout (i.e. the alias, if set, or the device name if not). Version 3.0 (RNC 5 Mar 2006) =============================================================================== New file structure. OLD, SDK USER'S TYPICAL VIEWPOINT C:\Program Files\Whisker Control\Whisker SDK\C++\whiskertask.h C:\Program Files\Whisker Control\Whisker SDK\common\whiskerstrings.h C:\Program Files\Whisker Control\Whisker SDK\common\rncalgorithm.h C:\Program Files\Whisker Control\Whisker SDK\common\whiskerstrings.h C:\Program Files\Whisker Control\Whisker SDK\common\libraries\mersennetwister\mersennetwister.h OLD, DEVELOPER'S VIEWPOINT D:\Whisker\code\clients\sdk\WhiskerClientLib\whiskertask.h D:\Whisker\code\common\whiskerstrings.h D:\Whisker\code\common\rncalgorithm.h D:\Whisker\code\common\libraries\mersennetwister\mersennetwister.h NEW GENERIC VIEWPOINT for developers: D:\Whisker\code for SDK users: C:\Program Files\WhiskerControl\Whisker SDK ... add both by default, but in that order (risk of version confusion if SDK installed on developer machine) EXAMPLE FILES: \whiskersdk\whiskerstrings.h \whiskersdk\whiskerclientlib\whiskertask.h \whiskersdk\mersennetwister\mersennetwister.h INCLUDE STYLE: #include #include #include NECESSARY MOVEMENTS: whiskerclientlib needs to move (DONE) UNIFICATION METHOD: - add to each project in ... Tools / Options / Directories / Include files ... Tools / Options / Directories / Link files ?? --- --- doesn't actually help with the Link files (at least, you can't specify subdirectories relative to this base directory)... so you have to specify the FULL path for link files - ALTERNATIVE: add to the INCLUDE environment variable (quick-and-dirty way: Control Panel / System / Advanced / Environment variables) and use /USEENV for Visual C++ command-line compilation - then do paths relative to this MY ACTUAL TOOLS / OPTIONS / DIRECTORIES SETUP: ... aside from the defaults: Include files: D:\whisker\code Library files: d:\whisker\code\whiskersdk\libraries\whiskerclientlib\debug d:\whisker\code\whiskersdk\libraries\whiskerclientlib\release d:\whisker\code\whiskersdk\libraries\whiskerclientlib\debug_cambs d:\whisker\code\whiskersdk\libraries\whiskerclientlib\release_cambs (these four all collapse to one directory from the user's POV) d:\whisker\code\whiskersdk\libraries\mcl\lib d:\whisker\code\whiskersdk\libraries\microsoft d:\whisker\code\whiskersdk\libraries\htmlhelp d:\whisker\code\whiskersdk\libraries\cryptopp\cryptopp\release d:\whisker\code\whiskersdk\libraries\mcl\lib d:\whisker\code\whiskersdk\libraries\mcl4mfc\lib IN PRACTICE, THE USER HAS TO ADD: To "Include files": C:\Program Files\WhiskerControl\Whisker SDK To "Library files": C:\Program Files\WhiskerControl\Whisker SDK\whiskersdk\libraries\whiskerclientlib C:\Program Files\WhiskerControl\Whisker SDK\whiskersdk\libraries\mcl\lib C:\Program Files\WhiskerControl\Whisker SDK\whiskersdk\libraries\microsoft LIBRARY LEGALITY See "Library_sources.txt" in the "libraries" directory. PORTABILITY TIPS (March 2007) * Don't put files in the FileView. They retain absolute pathnames. * Use #include for #include files. The addition of one entry to "Tools / Options / Directories / Include files" sorts all this out. * Put library files in Project / Settings..., with no pathname. * Add the library paths to "Tools / Options / Directories / Library files". PROJECTS DONE: - WhiskerClientLib REDONE - ( SDK.ISS ) - WhiskerServer - doesn't need redoing - DatabaseManager - ditto - VerifyWhiskerLog - ditto - WhiskerReset - ditto - WhiskerStatus REDONE - WhiskerTestClient - didn't need redoing - Whisker SDK (in clients\sdk\control\code) - REDONE - DemoDisplayClient (DemoConsoleClient) - REDONE - LogThrashClient - REDONE - SimpleCPPClient - REDONE - KidCausalReasoning - N/A - Visual Basic - ... any of Mike's - NOT DONE - left to Mike :-) - PigTab - NOT DONE - currently not distributing source code. But REDONE for library changes. - AttMem - REDONE - ConditionedReinforcement REDONE - DialysisStimuli REDONE - FearCond REDONE - FiveChoice REDONE - ImpulsiveChoice REDONE - LeverAutoshaping REDONE - LeverReversals REDOE - MonkeyCantab REDONE - PIT REDONE - RatBat REDONE - SecondOrder REDONE - SeekTake REDONE - SimpleSchedules REDONE - VisualAutoshaping REDONE Then: - There's nowhere that I can see to specify the inclusion of libraries-within-this-library, other than in the list of files in the project's FileView. In particular, WINMM.LIB and MCL.LIB are called by functions within WhiskerClientLib, so it makes life a lot easier for the final client if those two libraries are built into the output WHISKERCLIENTLIB_xxx.LIB. However, specifying things in the FileView gives absolute path references, which makes it harder for users to recompile the source. Possibly, we shouldn't do this (***), and should simply require users to add WINMM.LIB and MCL.LIB to any projects they use. - PROBLEM, EXACT CAUSE UNKNOWN: when building MonkeyCantab in debug mode on a different machine (i.e. one that's downloaded the source from the distributable), this error appears: whiskerclientlib_debugcambs.lib(Schedule.obj) : fatal error LNK1202: "C:\Program Files\MonkeyCantab\MonkeyCantab_code\Debug\vc60.pdb" is missing debugging information for referencing module Doesn't happen in release mode. Possible solution (this does seem to work): 1) Remove MCL.LIB and WINMM.LIB from WhiskerClientLib. 2) Add them to all users of WhiskerClientLib. Other side effect: this would also allow users to recompile WhiskerClientLib more portably. Another side effect: gets rid of this message: LINK : warning LNK4098: defaultlib "MSVCRT" conflicts with use of other libs; use /NODEFAULTLIB:library SDK version 3.3 (5 Dec 2008) =============================================================================== - well, all seems to be working - update ReportName() and ReportStatus() to include quotes SDK/library version 3.4 (8 Jan 2009) =============================================================================== - microsecond network latency testing (client-based); adds TestNetworkLatencyFromClientMicrosec() - 12 Jan 2009: Server default changed from "loopback" to "localhost" (Windows Vista compatibility and more general standardization) - applies to embedded control help text only. Library v3.6 (~8 May 2009) =============================================================================== - Conversion to VS2008 Library v3.7 (~8 May 2009) =============================================================================== - Vector "erase" code fixed for VS2008. Library v4.0 (June 2011) =============================================================================== - Video support added (requires WhiskerServer v4.0) Library v4.1 (March 2012) =============================================================================== - Additional console-mode functions. - namespace whisker If any functions appear to vanish, do this: using namespace whisker; - MCL.LIB (or MCLD.LIB) weren't being linked: added back in (Project > Properties > Configuration Properties > Librarian > General > Additional Dependencies) and likewise WINMM.LIB (for timeGetTime). - a bunch more helpful stuff Library v4.2 (March 2012) =============================================================================== - Added CAtomic<> class to headers. - console_SetTextColour() Library v4.3 (April-May 2012) =============================================================================== - minor additions: improved repeatfor(), latency calculation, counterbalancing - bugfix to DisplayAddObject_Text: produced a bad Whisker call with a blank font parameter - get_bitmap_dimensions_bmp() - Options to place bitmaps and video by centre point, rather than top left point. Library v4.5 (May 2014) =============================================================================== - RAII principles - warnings at level 4 - guarantee incoming pointers are const - remove "using namespace" - uncrustify - cppcheck - handy registry support functions Library v4.51 (May 2014) =============================================================================== - altered ~CWhiskerTask() to try to fix intermittent crash at final exit of MonkeyCantab when cleaning up a dummy CWhiskerTask. Library v4.52 (June 2014) =============================================================================== - distribution filefix (xmlparamio / xmlparamio2 filename error in .ISS file) Library v4.6 (5 Dec 2014) =============================================================================== - ReportComment command (server v4.6) - VideoSetVolume command (server v4.6) Library v4.61 (7 Apr 2015) =============================================================================== - Background colour option to DisplayAddObject_Video (server v4.61). - ServerVersionNumeric() - LibraryVersionNumeric() - RED_COMPONENT, GREEN_COMPONENT, BLUE_COMPONENT internally. - Thoughts about occasional failure of tasks using this library to initiate. Reported with MonkeyCantab, ConditionedReinforcement, SeekTake, SeekTakeShock. Unclear whether commonality is (a) common style of handling events, (b) the CWhiskerClient class and e.g. its socket-handling code, or (c) the server. http://tangentsoft.net/wskfaq/articles/csocket.html http://www.flounder.com/kb192570.htm http://www.boost.org/doc/libs/1_46_0/doc/html/boost_asio.html http://boost.cowic.de/rc/pdf/asio_doc.pdf http://www.boost.org/doc/libs/1_39_0/doc/html/boost_asio/reference/ip__tcp/no_delay.html Library v4.62 (10-12 Apr 2015) =============================================================================== - Socket code reworked. Library v4.63 (26 Nov 2015) =============================================================================== - Added IntVectorToSemicolonString(). Library v4.7 (24 Feb 2016) =============================================================================== - Possible threading problem in CPipedEventList::Poll. Crash observed in FearCond task. Vector size was read into initial_size (as 2) but then vector size was 1 by the time of vector access. Threading problem? Hope not - there should only be one thread processing receipt. Or is a consequence of m_vEventInfo[pos].Poll() altering the vector size? That function does call a callback, so the vector size is probably a little unsafe aftewards (even though I thought additions, not removals, were the only thing happening...). Ah. No. That's probably it: StopFlashLine(), KillReply(), and ScheduleHousekeeping() call RemoveEvent(). Library v4.71 (8 Apr 2016) =============================================================================== - Change in distribution only: libraries\sockets code was not being distributed properly. Library v4.8 (23 Sep 2016) - NOT PROPERLY RELEASED =============================================================================== - Changes to support.h/support.cpp to (inelegantly) support MySQL delimiting as well as Access delimiting of table/fieldnames. - Changes to distribution: previous distribution was stupidly poking things in an extra "whiskerclientlib/src" directory, not "whiskerclientlib", so existing code variably broke and there were different header files with the same name. (Those changes are in SDK.iss) Library v4.9 (2017-02-07) =============================================================================== - Supports compilation under Visual Studio 2015 (with C++11 features). Library v4.10 (2019-09-22) =============================================================================== - Bugfix: background colour parameter in CWhiskerTask::DisplayAddObject_Video() didn't have a space before it. Library v4.11 (2020-12-06) =============================================================================== - Year bump. - Include "schedules/" directory (previously missing, e.g. used by MonkeyCantab). =============================================================================== THINGS TO DO: =============================================================================== - *** OUTSTANDING: improve CWhiskerTask copy constructor (see MonkeyCantab notes May/June 2014).