OnXllOpenEx() is called once in the lifetime of the add-in, when it is opened. It must call the parent class method first, before any other initialisation. It should then initialize any application resources. If any of these initializations fail, it should show an error message and return FALSE.
int CMtFeedAddinApp::OnXllOpenEx() { if (!CXllPushApp::OnXllOpenEx()) return FALSE; // Open a channel to the server if (mtcStartClient(&m_client, ClientCallback, this) != 0) { MessageBox(XlHwnd(), "Failed to initialise comms client", GetXllName(), MB_OK|MB_ICONEXCLAMATION); return FALSE; } // Set push properties SetFormatChangedCells(TRUE); // Register functions which will be pushed AddFunction("MtfGet"); return TRUE; }
This is very similar to the code in the MtBackground example. The function does the following:
OnXllClose() is called once in the lifetime of the add-in, just before it is closed. It should terminate any application resources.
void CMtFeedAddinApp::OnXllClose() { if (m_client != 0) { mtcStopClient(m_client); m_client = 0; } }
In this case, it simply closes the comms channel.
CMtFeedAddinApp::ProcessAsyncMessage() is called every time a message is received from the background thread. (The message will have been posted using CXllPushApp::PostMessage().)
void CMtFeedAddinApp::ProcessAsyncMessage(CXllMtMsg* msg) { // Downcast the message CMtFeedAddinMsg* myMsg = static_cast<CMtFeedAddinMsg*>(msg); // If value has changed ... CString strOldValue; if (!m_cache.Lookup(myMsg->m_strTopic, strOldValue) || strOldValue != myMsg->m_strData) { // Update the cache m_cache.Set(myMsg->m_strTopic, myMsg->m_strData); // Update any affected cells UpdateCells(myMsg->m_strTopic); } // Delete the message delete msg; }
This implementation has the usual four steps:
In this example, we have again optimised the process, by skipping steps 2 and 3 for messages which are duplicates of previous messages.
The comms module owns a background thread. The thread waits for asynchronous data to arrive from the server and calls a call-back function for each data packet. In this architecture, it is the responsibility of the client to pass the data packet to the main thread. It does so in the usual manner, by putting a copy of the data into a message object and posting it to the main thread with PostMessage().
void _stdcall ClientCallback(mtcHandle h, int cb, void* pvData, void* pvUserData) { int cbTmp = cb; void *pvMsgData, *pvDataTmp = pvData; char* pszTopic; int nType, cbData; CMtFeedAddinApp* app = (CMtFeedAddinApp*)pvUserData; // Crack the message, so that pszTopicpoints to the null-terminated // string topic,and pvMsgData points to the message body mtcCrackMessage(&cbTmp, &pvDataTmp, &nType, &pszTopic, &cbData, &pvMsgData); // Post copies of topic and data to the main thread app->PostMessage(new CMtFeedAddinMsg(pszTopic, (const char*)pvMsgData)); }
Next, examine the add-in function itself, MtfGet().
This is a typical async add-in function, with the usual steps:
extern "C" __declspec( dllexport ) LPXLOPER MtfGet(const char* Topic, const char* Field) { CXlOper xloResult; //}}XLP_SRC CMtFeedAddinApp* addin = (CMtFeedAddinApp*)XllGetApp(); CString strData; double dValue; // Look up the value in the cache, or return #N/A // if not found if (addin->m_cache.Lookup(Topic, strData) && addin->ExtractField(strData, Field, dValue)) xloResult = dValue; else xloResult = xlerrNA; // Register the connection between the topic and // the cell(s) that called the function addin->AddConnection(Topic); return xloResult.Ret(); }
Note that we use m_cache.Lookup() to retrieve the entire field list for a topic, and then extract the desired field using ExtractField().
It is essential to call AddConnection(), even if the value is already in the cache, in order to instruct the push engine to keep monitoring the connection.
MtfReconnect() is a macro add-in function which can be called by the GUI, to recover a lost connection.
extern "C" __declspec( dllexport ) BOOL MtfReconnect() { //}}XLP_SRC CMtFeedAddinApp* addin = (CMtFeedAddinApp*)XllGetApp(); return addin->Reconnect(); }