RTD and UDF Examples - GigaSpaces-Excel Integration


Author XAP Version Last Updated Reference Download
Pini Cohen 6.6

Overview

This section includes basic and advanced code examples for using the GigaSpaces-Excel integration with Microsoft Excel User-Defined Functions (UDF) and Real-Time Data (RTD).

Basic Examples

HelloUDF – Performing Excel Functions in Space

using System;
using System.Runtime.InteropServices;
using Microsoft.Win32;

using GigaSpaces.Core;
using System.Configuration;

using HelloCommon;

//-----------------------------------------------------------
// This class demonstrate how to build a UDF wrapper of a GigaSpaces .NET proxy.
//
// We show how to wrap space operations to be used from within
// the Excel spreadsheet as a regular Excel functions
//
// All public methods are exposed to the Excel user and after the class is registered and added as an "add-in" will apear in the Excel functions list
//-----------------------------------------------------------
namespace HelloUDF
{
    [ClassInterface(ClassInterfaceType.AutoDual), ComVisible(true)] //required to tag a C# class a UDF
    public class HelloUDF
    {
        private ISpaceProxy _proxy;

        public HelloUDF()
        {
            SpaceInit();
        }

        public String AddMsg(int id, string msg)
        {
            String output;

            HelloMsg theMsg = new HelloMsg();
            theMsg.ID = id;
            theMsg.MSG = msg;
            theMsg.STATUS = "working";

            try
            {
                _proxy.Update<HelloMsg>(theMsg,
                                        null, //no transactions
                                        long.MaxValue, 0, //lease and write timeouts
                                        UpdateModifiers.UpdateOrWrite);

                output = "["+ theMsg.ToString() + "] was written to the space";
            }
            catch (Exception e)
            {
                output = e.Message;
            }
            return output;
        }

        public String SetToDone(int id)
        {
            String output;

            HelloMsg theMsg = new HelloMsg();
            theMsg.ID = id;
            theMsg.STATUS = "done";

            try
            {
                _proxy.Update<HelloMsg>(theMsg,
                                        null, //no transactions
                                        long.MaxValue, 0, //lease and write timeouts
                                        UpdateModifiers.PartialUpdate);
                output = "setting Messgae ID " + id + " to done...";
            }
            catch (Exception e)
            {
                output = e.Message;
            }
            return output;
        }

        [ComRegisterFunctionAttribute] //required to tag a C# class a UDF
        public static void RegisterFunction(Type type)
        {
            Registry.ClassesRoot.CreateSubKey(GetSubKeyName(type));
        }

        [ComUnregisterFunctionAttribute] //required to tag a C# class a UDF
        public static void UnregisterFunction(Type type)
        {
            Registry.ClassesRoot.DeleteSubKey(GetSubKeyName(type), false);
        }

        private static string GetSubKeyName(Type type) //required to tag a C# class a UDF
        {
            string s = @"CLSID\{" + type.GUID.ToString().ToUpper() + @"}\Programmable";
            return s;
        }

        private bool SpaceInit()
        {
            try
            {
                string url = ConfigurationManager.AppSettings["SpaceUrl"]; //an environment variable read from the excel.exe.config file
                _proxy = SpaceProxyProviderFactory.Instance.FindSpace(url);
                _proxy.OptimisticLocking = true;
                return true;
            }
            catch (Exception ex)
            {
                ex.ToString();
                return false;
            }
        }
    }
}

HelloRTD – Loading Data from Space to Excel

using System;
using System.Configuration;
using System.Runtime.InteropServices;//<-- For our interop attributes.
using Microsoft.Office.Interop.Excel;

using GigaSpaces.Core;
using GigaSpaces.Core.Events;

using HelloCommon;

//-----------------------------------------------------------
// This class demonstrate how to build a RTD wrapper to "push" data from
// GigaSpaces to the Excel spreadsheet
//
// We show how to register on a notification for a change, and then push
// this change to a cell in Excel
//
//  Usage of the RTD function within Excel:
//  =RTD("HelloRTD",,)
//
//-----------------------------------------------------------

namespace HelloRTD
{
    [ComVisible(true), ProgId("HelloRTD")] //the name of the progId parameter in the RTD excel function
    public class TestRTD : IRtdServer//this is the interface any C# class must implement to become a RTD
    {
        private IRTDUpdateEvent _xlRTDUpdate; //the type of object to link between this runtime and the Excel
        private ISpaceProxy _proxy; //space proxy
        private int _topicID; //Excel cell id to which we will "wire" the notification
        private string _eventData; //the data we receive from the space upon notification
        IEventRegistration _eventReg; //store the space notification as local object to de-register

        #region IRTD Members
        /**
         * this method is called by Excel when the RTD server start (the first time the workbook calls =RTD(...)
         * here we should creat the connection to the space
         * CallbackObject - the link between this runtime and the Excel
        */
        public int ServerStart(IRTDUpdateEvent CallbackObject)
        {
            // Hold a reference to the callback object.
            _xlRTDUpdate = CallbackObject;

            bool isConnected = SpaceInit(); //connect to the space

            //All is well, return 1.
            return (isConnected ? 1 : 0);
        }
        /**
         * called when closing the Excel
         * */
        public void ServerTerminate()
        {
            clean();
        }
        /**
         * topicID - unique ID of the cell in the Excel
         * RTDparms - must have at least one parameter
         * getNewValues - used by the Excel, no need change
         */
        public object ConnectData(int topicID, ref Array RTDparms, ref bool getNewValues)
        {
            // Registering for notifications on status "done"
            HelloMsg notifyTemplate = new HelloMsg();
            notifyTemplate.STATUS = "done";
            _eventReg = _proxy.DefaultDataEventSession.AddListener
                        <HelloMsg>(notifyTemplate, Space_DataChanged);

            //store the cell this RTD is written in
            _topicID = topicID;
            return "This cell is listening for msg with status 'done' ...";
        }
        /**
         * called when deleteing the RTD function from Excel
         */

        public void DisconnectData(int topicID)
        {
            clean();
        }
        /**
         * called by the Excel when _xlRTDUpdate.UpdateNotify() is called
         * since we call UpdateNotify in each space event,
         * RefreshData is called for each event seperately
         *
         * topicCount = tell the excel how many cells we updated
         * */
        public Array RefreshData(ref int topicCount)
        {
            // This method returns a two-dimensional array of Variant values.
            // The first dimension represents a list of topic IDs;
            // these topic IDs map to the TopicID parameter in the ConnectData method above.
            // This is how Excel associates topics with data.
            // The second dimension represents the values associated with the topic IDs.

            //1st dimention is always 2 - since we have topic and value
            //2nd dimention is the number of cells we want to update in the Excel
            //    as a result of this specific event
            //
            //  |topicID(0,0)|topicID(0,1)|topicID(0,2)|... topicID(0,n)|
            //  |value1 (1,0)|value2 (1,1)|value3 (1,2)|... valueN (1,n)|
            //
            // n = the number of cells we update in each event
            //

            //since we always update a single cell, we build the following array
            object[,] result = new object[2, 1];
            result[0, 0] = _topicID;
            result[1, 0] = _eventData;

            topicCount = 1;
            return result;
        }

        public int Heartbeat()
        {
            return 1; // the RTD server is up and running, 0 indicates error
        }

        #endregion

        /**
         * calleback by GigaSpaces when event occured
         * EventArgs is the data of the event
         */

        private void Space_DataChanged(object sender, SpaceDataEventArgs<HelloMsg> e)
        {
            //store in the _receivedData the data we got from the event
            _eventData = "Message ID: " + e.Object.ID + " ('" + e.Object.MSG + "') was set to Done!";
            //Tell Excel that we have updates. Then the Excel calls back to RefreshData
            _xlRTDUpdate.UpdateNotify();
        }

        private bool SpaceInit()
        {
            try
            {
                string url = ConfigurationManager.AppSettings["SpaceUrl"];
                _proxy = SpaceProxyProviderFactory.Instance.FindSpace(url);
                _proxy.OptimisticLocking = true;
                return true;
            }
            catch (Exception ex)
            {
                System.Console.Write(ex);
                return false;
            }

        }

        private void clean()
        {
            try
            {
                //remove the event
                if (_eventReg != null)
                {
                    _proxy.DefaultDataEventSession.RemoveListener(_eventReg);
                    _eventReg = null;
                }
                // Clear the RTDUpdateEvent reference.
                _xlRTDUpdate = null;
            }
            catch (Exception ex)
            {
                System.Console.Write(ex);
            }
        }
    }
}

HelloMsg

using System.Text;

using GigaSpaces.Core.Metadata;

namespace HelloCommon
{
    [SpaceClass(IncludeProperties = IncludeMembers.Public, IncludeFields = IncludeMembers.None)]

    public class HelloMsg
    {
        private int _id;
        private string _msg;
        private string _status;

        public HelloMsg()
        {
            _id = -1;
        }

        // NullValue instructs the space to consider a value as null when an object instance is  used as a template
        [SpaceID, SpaceRouting, SpaceProperty(Index = SpaceIndexType.Basic, NullValue = -1)]
        public int ID
        {
            get { return _id; }
            set { _id = value; }
        }

        public string MSG
        {
            get { return _msg; }
            set { _msg = value; }
        }

        public string STATUS
        {
            get { return _status; }
            set { _status = value; }
        }

        public override string ToString()
        {
            StringBuilder builder = new StringBuilder();
            builder.Append("Id:" + ID +
                            " ,Msg: " + MSG +
                            " ,Status: " + STATUS);
            return builder.ToString();
        }
    }
}

RTDSample – Loading Data from Space to Excel

using System;
using System.Collections;
using System.Text;
using System.Runtime.InteropServices;//<-- For our interop attributes.

using Microsoft.Office.Interop.Excel;
using GigaSpaces.Core;
using GigaSpaces.Core.Exceptions;
using GigaSpaces.Core.Events;
using System.Collections.Generic;
using System.Configuration;

//-----------------------------------------------------------
// This class demonstrate how to build a RTD wrapper to "push" data from
// GigaSpaces to the Excel spreadsheet
//
// We show how to register on a notification of a tick change, and then push
// the tick value to the Excel
//
//  Usage of the RTD function within Excel:
//  =RTD("GSFeader",,[the tick symbol we want to monitor])
//
//-----------------------------------------------------------
namespace GSStreamer
{
    [ComVisible(true), ProgId("GSFeeder")] //the name of the progId parameter in the RTD excel function
    public class GSStreamer : IRtdServer//this is the interface any C# class must implement to become a RTD
    {
        private IRTDUpdateEvent _xlRTDUpdate;
        private ISpaceProxy _proxy;
        private Dictionary<int, TopicTick> _topicIDTable; //used to map between the Excel cellID and the returned data
        private Dictionary<string, TopicTick> _tickTable;

        public GSStreamer()
        {
            _topicIDTable = new Dictionary<int, TopicTick>();
            _tickTable = new Dictionary<string, TopicTick>();
        }

        #region IRTD Members
        //this method is called by Excel when the RTD server start (the first time the workbook calls =RTD(...)
        //here we should creat the connection to the space
        public int ServerStart(IRTDUpdateEvent CallbackObject)
        {
            // Hold a reference to the callback object.
            _xlRTDUpdate = CallbackObject;

            bool isConnected = SpaceInit();

            //All is well, return 1.
            return (isConnected ? 1 : 0);
        }
        public void ServerTerminate()
        {
            // Clear the RTDUpdateEvent reference.
            _xlRTDUpdate = null;
        }

        //this method is called by Excel for every =RTD(...) execution
        //the topicID is a unique identifier for every cell in the excel, we use it to map where to notify back to
        public object ConnectData(int topicID, ref Array RTDparms, ref bool getNewValues)
        {
            string symbol = (string)RTDparms.GetValue(0);
            // Reading from the Space
            string tickInfo = ReadTick(symbol);

            // Registering for notifications using the space proxy
            TickInfo.TickInfo notifyTemplate = new TickInfo.TickInfo();
            notifyTemplate.Symbol = symbol;
            IEventRegistration eventReg = _proxy.DefaultDataEventSession.AddListener<TickInfo.TickInfo>(notifyTemplate, Space_TickChanged);

            //creating a new topic (which has a referance to the listner) and store it locally
            TopicTick tp = new TopicTick(topicID, symbol, eventReg);

            if (!_tickTable.ContainsKey(symbol))
                _tickTable.Add(symbol, tp);
            if (!_topicIDTable.ContainsKey(topicID))
                _topicIDTable.Add(topicID, tp);

            return (object)tickInfo;
        }
        //this method is called by the Excel when a user erase and RTD method from a given cell.
        public void DisconnectData(int topicID)
        {
            try
            {
                TopicTick tp = _topicIDTable[topicID];
                _topicIDTable.Remove(tp.TopicID);
                _tickTable.Remove(tp.symbol);
                _proxy.DefaultDataEventSession.RemoveListener(tp.EventReg);
            }
            catch (Exception ex)
            {
                ex.ToString();
            }
        }

        public Array RefreshData(ref int topicCount)
        {
            // This method returns a two-dimensional array of Variant values.
            // The first dimension represents a list of topic IDs;
            // these topic IDs map to the TopicID parameter in the ConnectData method above.
            // This is how Excel associates topics with data.
            // The second dimension represents the values associated with the topic IDs.

            object[,] result = new object[2, _topicIDTable.Count];

            //build a array of all the topics that changed to send the Excel
            foreach (TopicTick tp in _topicIDTable.Values)
            {
                if (tp.Changed)
                {
                    tp.Changed = false;
                    result[0, topicCount] = tp.TopicID;
                    result[1, topicCount] = ReadTick(tp.symbol); // we can use the symbol attribute: symbol.Pono instead of reading from space
                    topicCount++;  //Tell Excel how many topics we updated.
                }
            }

            //Return the updates.
            return result;
        }

        public int Heartbeat()
        {
            return 1; // the RTD server is up and running, 0 indicates error
        }

        #endregion

        //this method is invoked when a tick has changed
        private void Space_TickChanged(object sender, SpaceDataEventArgs<TickInfo.TickInfo> e)
        {
            //bookmark the tick that was changed
            _tickTable[e.Pono.Symbol].Changed = true;
            //Tell Excel that we have updates,
            //as a result the excel will call the RefreshData function
            _xlRTDUpdate.UpdateNotify();
        }

        private bool SpaceInit()
        {
            try
            {
                string url = ConfigurationManager.AppSettings["SpaceUrl"]; //an environment variable read from the excel.exe.config file
                _proxy = SpaceProxyProviderFactory.Instance.FindSpace(url);
                _proxy.OptimisticLocking = true;
                return true;
            }
            catch (Exception ex)
            {
                ex.ToString();
                return false;
            }
        }

        private string ReadTick(String symbol)
        {
            // reading the Person Data from the Spaces

            TickInfo.TickInfo template = new TickInfo.TickInfo();
            template.Symbol = symbol;

            TickInfo.TickInfo tick = _proxy.Read<TickInfo.TickInfo>(template);

            if (tick == null)
                return ("Not Exist");
            else
                return tick.ToString();
        }

        private class TopicTick
        {
            public int TopicID;
            public bool Changed;
            public string symbol;
            public IEventRegistration EventReg;

            public TopicTick(int topicId, string symbol, IEventRegistration eventReg)
            {
                this.TopicID = topicId;
                this.Changed = false;
                this.symbol = symbol;
                this.EventReg = eventReg;
            }
        }
    }
}

TickInfo

using System;
using System.Collections.Generic;
using System.Text;

using GigaSpaces.Core.Metadata;

namespace TickInfo
{
    // SpaceClass(IncludeProperties = IncludeMembers.Public) instructs the space to take only public properties
    // SpaceClass(IncludeFields = IncludeMembers.None) instructs the space not to take any fields
    [SpaceClass(IncludeProperties = IncludeMembers.Public, IncludeFields = IncludeMembers.None)]
    public class TickInfo
    {
        private string _symbol;
        private double _open;
        private double _close;
        private double _last;

        // SpaceID is used to indicate a unique field value. This field value is used to generate the Entry UID
        // SpaceProperty(Index = SpaceIndexType.Basic) is used indicate an indexed field
        [SpaceID, SpaceRouting, SpaceProperty(Index = SpaceIndexType.Basic)]
        public string Symbol
        {
            get { return _symbol; }
            set { _symbol = value; }
        }
        [SpaceProperty(NullValue = -1)]
        public double Open
        {
            get { return _open; }
            set { _open = value; }
        }
        [SpaceProperty(NullValue = -1)]
        public double Close
        {
            get { return _close; }
            set { _close = value; }
        }
        [SpaceProperty(NullValue = -1)]
        public double Last
        {
            get { return _last; }
            set { _last = value; }
        }

        public TickInfo()
        {
            this._open = -1;
            this._close = -1;
            this._last = -1;
        }

        public override string ToString()
        {
            StringBuilder builder = new StringBuilder();
            builder.Append(" Symbol: " + Symbol);
            builder.Append(" Open: " + Open.ToString());
            builder.Append(" Close: " + Close.ToString());
            builder.Append(" Last: " + Last.ToString());
            return builder.ToString();
        }
    }
}

What’s Next?