XLL+ Class Library

MtFeedAddin.cpp

Initialisation: OnXllOpenEx()

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:

  1. Calls the parent class function.
  2. Initialises application resources (showing an error message and returning FALSE if it fails).
  3. Sets the run-time properties of the push engine.
  4. Registers any asynchronous add-in functions.

Termination: OnXllClose()

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.

ProcessAsyncMessage

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:

  1. Downcast the message to our message class.
  2. Put the new data in the data cache.
  3. Update any cells connected to the topic.
  4. Delete the message.

In this example, we have again optimised the process, by skipping steps 2 and 3 for messages which are duplicates of previous messages.

Comms client call-back function

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));
}

Asynchronous add-in function

Next, examine the add-in function itself, MtfGet().

This is a typical async add-in function, with the usual steps:

  1. Get a pointer the application object.
  2. Look for the answer in the data cache. Set the return value in xloResult if it is found, or set an error message if not found.
  3. Register the connection between the topic and the calling cell, by calling AddConnection().
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.

Macro function

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();    
}

Next: MtFeedAddinGui.xla >>