XLL, RTD, and COM Appdomains

Add-in Express™ Support Service
That's what is more important than anything else

XLL, RTD, and COM Appdomains
XLL, RTD, and COM Appdomains 
sgonzales




Posts: 15
Joined: 2010-03-22
I have an assembly with an XLL module, RTDs, and a COM add-in module. The RTD functions are wrapped inside the XLL.

I noticed that the RTDs are in the DefaultDomain while the XLL and COM both are in another. Since they all need access to a shared object, I placed the shared object inside the COM, and used "COMAddIns.Item(index).Object" to access the shared object.

It works fine if I start with an empty spreadsheet. However, my problem is when opening an existing spreadsheet with RTD calls. I noticed the load order is XLL -> RTD -> COM. Using "COMAddIns.Item(index).Object" returns null in the RTDs' ServerStart() method since the COM is not created yet. I tried moving the shared object to the XLL and use ExcelApp.Evaluate("xllPublicStaticMethodRetuningSharedObj()"), but it does not work inside the RTD.

My questions are:
1. Is there a way to force all the components to load in one app domain?
2. My RTD functions use the shared object most often. Is there a performance penalty each time the shared object from a different app domain is used even if I assign the shared object to an RTDs' property once I get a handle to it?
3. All examples I found access shared objects inside a COM or XLL. How do I access a shared objects inside an RTD?

Thanks in advance.
Posted 23 Mar, 2010 13:36:09 Top
Andrei Smolin


Add-in Express team


Posts: 18825
Joined: 2006-05-11
Hi Sgonzales,

I've created a sample project that (among other things) demonstrates how to create such a shared object. Please see http://www.add-in-express.com/creating-addins-blog/2010/03/24/addin-xll-rtd-one-assembly/.

Note that the RTD server is loaded in the same AppDomain. I suggest that you re-register your project. Please let me know if this helps.


Andrei Smolin
Add-in Express Team Leader
Posted 24 Mar, 2010 11:27:43 Top
sgonzales




Posts: 15
Joined: 2010-03-22
Hi Andrei,
Thank you very much for the reply. I failed to mention that, for my RTD, I'm implementing AddinExpress.RTD.IRtdServer interface directly since my RTD won't know the topics to subscribe to until user inputs it at runtime.

I added the sample RTD below to your project and I see that it still loads the RTD in the default domain, which is a different app domain from where the XLL, COM, and RTD extending ADXRTDServerModule loads. Thus the "shared" object is still created twice, one on each domain.

Here's the code:
----------------------------------------------------------------------------------

using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using AddinExpress.RTD;
using System.Threading;

namespace comaddin_xll_rtd_cs
{
    public class Server : IRtdServer
    {
        bool isInitialized = false;
        private MyCommonData commonData;
        protected IRTDUpdateEvent _callback;

        #region IRtdServer Members
        public Server()
        {
            Log.WriteLine("RTD constructor");
        }


        public void CallbackUpdateNotify()
        {
            while (true)
            {
                Thread.Sl eep(1000);
                _callback.UpdateNotify();
            }
        }


        public object ConnectData(int TopicID, ref Array Strings, ref bool GetNewValues)
        {
            Thread InstanceCaller = new Thread(new ThreadStart(CallbackUpdateNotify));
            InstanceCaller.Start();
            return "";
        }


        public void DisconnectData(int topicId)
        {

        }

        public int Heartbeat()
        {
            return 1;
        }

        public Array RefreshData(ref int TopicCount)
        {
            Random rnd = new Random();
            object[,] update = new Object[2, 1];
            update[0,0] = 0;
            update[1,0] = Convert.ToString(rnd.Next(1000));
            TopicCount = 1;
            return update;
        }


        public int ServerStart(IRTDUpdateEvent CallbackObject)
        {
            isInitialized = true;
            Log.WriteLine("RTD. AppDomain=" + AppDomain.CurrentDomain.FriendlyName);
            commonData = MyCommonData.CurrentInstance;
            if (!commonData.IsInitialized)
            {
                Log.WriteLine("RTD. Initializing common data...");
                commonData.Initialize();
            }
            Log.WriteLine("RTD. Common data are initialized.");
            Log.WriteLine("RTD. Checking other objects...");
            commonData.CheckObjects();

            _callback = CallbackObject;
            return 1;
        }

        public bool IsInitialized
        {
            get { return isInitialized; }
        }


        public void ServerTerminate()
        {
        }

        #endregion
    }
}

----------------------------------------------------------------------------------


Mistakes are the portals of discovery.
- James Joyce (1882 - 1941)
Posted 24 Mar, 2010 14:20:33 Top
Andrei Smolin


Add-in Express team


Posts: 18825
Joined: 2006-05-11
Hi Sgonzales,

Such an RTD server will be loaded to the default AppDomain because it bypasses the Add-in Express loader.

I've just tried the following approach:

- open my project at http://www.add-in-express.com/creating-addins-blog/2010/03/24/addin-xll-rtd-one-assembly/
- open the designer of RTDServermodule1
- select adxrtdTopic1
- set adxrtdTopic1.String01 = "*"
- now modify adxrtdTopic1_RefreshData as follows:

private object adxrtdTopic1_RefreshData(object sender)
{
    AddinExpress.RTD.ADXRTDTopic actualtopic = sender as AddinExpress.RTD.ADXRTDTopic;
    Random rnd = new Random();
    return actualtopic.String01 + " " + rnd.Next(1000).ToString();
}


Now test it. Is this what you are looking for?


Andrei Smolin
Add-in Express Team Leader
Posted 25 Mar, 2010 12:57:54 Top
sgonzales




Posts: 15
Joined: 2010-03-22
Hi Andrei,
Another reason we implemented AddinExpress.RTD.IRtdServer interface directly versus extending ADXRTDServerModule was because, to our understanding, the ADXRTDServerModule sends an upd ate every 5 seconds (default value, or whatever value is se t in the Interval property). However, we needed a real-time update every time we receive a new quote.

Please correct me if my understanding is incorrect. If so, let me know how to get a real-time update using ADXRTDServerModule. Thanks!
Posted 29 Mar, 2010 15:30:52 Top
Andrei Smolin


Add-in Express team


Posts: 18825
Joined: 2006-05-11
Hi Jeffrey,

RTD Server is an Excel technology; IRTDServer is a COM interface for creating an RTD server. An RTD server is a passive participant, kind of a data source, it is polled by Excel in the specified time interval, 5 seconds is the default value. You can change this setting, but you cannot make your RTD server an active player; it cannot initiate the process of refreshing data. You can bypass this using a COM add-in.


Andrei Smolin
Add-in Express Team Leader
Posted 30 Mar, 2010 05:39:55 Top
Kristofer Spinka




Posts: 1
Joined: 2010-03-30
Hi Andrei, I've been tracking this thread, it's both confusing and very interesting.

With regards to the RTD server, if I had an add-in that is notified of an update via an event-triggered mechanism, I can notify Excel's COM interface via IRTDUpdateEvent::NotifyUpdate() ("publish" data to the RTD server's "topics changed list"), on the IRTDUpdateEvent instance passed in on the IRTDServer::ServerStart() method, that there is new data available and then I could potentially call Excel.Application.RTD.RefreshData to force Excel to poll my RTD add-in to discover this new data that I have "published". In this scenario, I can then disable Application.RTD.ThrottleInterval (se t it to -1) and achieve "real-time" updates. However, forcing Excel's hand in this manner negates the amortization benefits of the batching interval (a.k.a. ThrottleInterval).

The problem with this scenario, is that I cannot see how to call the NotifyUpdate() equivalent from within an Add-In-Express RTD. I am probably confused by the example, but it appears as though Add-In-Express adds an additional polling timer that calls NotifyUpdate() at a preconfigured interval, not on-demand. This definitely breaks the whole RTD paradigm. Will you please explain to us how to call the NotifyUpdate() properly?

In your latest reply, you state that this whole mess can be bypassed by using a COM add-in, will you please explain what this means/how to do this?

Thank you,

/kristofer
Posted 30 Mar, 2010 18:13:14 Top
Andrei Smolin


Add-in Express team


Posts: 18825
Joined: 2006-05-11
Hi Kristofer,

I'm sorry for giving you incorrect information in my previous message. The corrected version of what I said is as follows:

An Add-in Express based RTD server is normally a passive participant; it is being called by Excel every 5 seconds, see the Interval property of the RTD server module. To initiate the process of refreshing data (i.e. to become an active participant), the RTD server calls ADXRTDServerModule.UpdateTopics().

Kristofer Spinka writes:
it appears as though Add-In-Express adds an additional polling timer that calls NotifyUpdate() at a preconfigured interval, not on-demand


This is true, ADXRtdServerModule.Interval property is mapped to the Interval property of that timer. In the Tick event of the timer, ADXRTDServerModule.UpdateTopics() is called. This makes your RTD server autoupdatable, and this gives me the ground to call such an RTD server passive.

The current implementation of ADXRtdServerModule.Interval contains a bug, however - you cannot set this property to zero. A fix will be available in Beta 2 of version 2010 and in an intermediate build of version 2009 (on request only).

On using a COM add-in:

- if your cells are predefined, the COM add-in can just set new values to those cells;
- if you have arbitrary cells specified by the user, you can provide a UDF to be used in formulas: the UDF provides new data and the COM add-in can find all cells containing formulas referencing the UDF and call Range.Calculate for those cells.

But such a solution requires handling all situations in which you cannot enter a value to a cell or recalculate it: when the user enters a formula; when a dialog is shown, when Excel recalculates, etc. From that point of view, the RTD Server is a better solution.

Note that Excle.Application.RTD.ThrottleInterval = -1 effectively turns off all other RTD servers on the end user's PC.


Andrei Smolin
Add-in Express Team Leader
Posted 02 Apr, 2010 09:49:24 Top
sgonzales




Posts: 15
Joined: 2010-03-22
Andrei,
I'm porting my RTD server (implementing AddinExpress.RTD.IRtdServer) to use ADXRTDServerModule. However, there are some discrepancies between the functions of IRtdServer interface and ADXRTDServerModule class. One of interest is the lack of return value of the ADXRTDServerModule.Connect function. We use the return value of IRtdServer.ConnectData to return error messages if the topic fails validation or some parameter is invalid. Where do you recommend adding parameter validation?
Posted 19 Apr, 2010 05:35:07 Top
Andrei Smolin


Add-in Express team


Posts: 18825
Joined: 2006-05-11
Hi Jeffrey,

You can generate an exception in the Connect event of the corresponding topic and process that exception in the OnError event of the RTD server module.

Is this what you are trying to achieve?


Andrei Smolin
Add-in Express Team Leader
Posted 19 Apr, 2010 05:59:36 Top