December 2008 - Posts

Was Softwear a Joke?

Earlier this month I heard about Microsoft’s Softwear line of t-shirts. It was supposedly launching on the 15th of December. Launching where?

I checked out the Microsoft company store and there’s no sign of them. But then I also noticed that MVPs get redirected to a different login page. The store also seems to only be offering older products. For example there’s no sign of the BlueTrack mice. There appears to be a shiny new store for employees only, or am I dreaming?

I’m not complaining but it is the first year I have MVP dollars to spend but nothing worth spending them on.  :)

Posted by KennyKerr with 1 comment(s)

AVCHD Editing Software

I have enjoyed the ease of use and surprising power of the built-in Windows Movie Maker product but it really hasn’t progressed with the times and the newest “Windows Live” version of Movie Maker seems to be going backwards.

Recently we purchased a high definition camcorder that saves natively to AVCHD. We love the camcorder but are struggling with what editing software to use. I downloaded the Adobe Premiere Elements trial, which natively supports AVCHD , but its nowhere near as intuitive as Movie Maker. I had hoped to try Pinnacle Studio but they don’t seem to be interested in offering a trial version.

Dear reader, what do you suggest?

Posted by KennyKerr with 8 comment(s)

Excel RTD Servers: A Topic’s Initial Value

What is the initial value displayed for a particular topic? Before you come up with an answer take another look at the ConnectData method used by Excel to subscribe to a particular topic.

In IDL it looks like this:

HRESULT ConnectData([in] long topicId,
                    [in] SAFEARRAY(VARIANT)* strings,
                    [in, out] VARIANT_BOOL* newValue,
                    [out, retval] VARIANT* value);

In C# it looks like this (I’ve removed the MarshalAs attribute for clarity):

object ConnectData(int topicId,
                       ref Array strings,
                       ref bool newValue);

These are actually equivalent. The CLR just takes the retval parameter and uses that as the return value and converts the HRESULT into an exception. This is much the same as what Visual Basic and scripting languages do.

So ConnectData returns a VARIANT/object to Excel but is it the initial value displayed for a particular topic? Well it depends on the newValue parameter. What makes this interesting is that it’s an [in, out] parameter, called a ref parameter in C#. Why would that be? Clearly Excel is trying to tell us something.

What Excel tells the RTD server about the value

On input the newValue parameter indicates whether Excel already has a value to initially display. This will never happen if you start with a blank Excel workbook but can happen if you open a previously saved workbook that includes RTD functions. Excel effectively caches the last value of each RTD topic in the workbook. The idea is that Excel can display an initial value while the RTD server goes about any potentially time consuming operations to reestablish data connections.

So if Excel has a cached value to display then newValue will be false. If Excel does not have a cached value to display then newValue will be true, indicating that a new value is needed. Of course that doesn’t mean you have to provide one.

What the RTD server tells Excel about the value

On output the newValue parameter indicates whether Excel should use the returned value or not.

If newValue is false then Excel will ignore the value returned by ConnectData. If it doesn’t have a previously cached value then it displays the “#N/A” warning. Of course this is replaced with an actual value once it receives one via RefreshData.

If newValue is true then Excel will immediately replace whatever value it may already have with the value returned by ConnectData.

If you’re looking for one of my previous articles here is a complete list of them for you to browse through.

Produce the highest quality screenshots with the least amount of effort! Use Window Clippings.

Posted by KennyKerr with no comments

Excel RTD Servers: How to use UpdateNotify Properly

UpdateNotify is the primary method provided by the IRTDUpdateEvent callback interface that an RTD server uses to let Excel know that updates are available. I have however noticed that this method tends to be used incorrectly by many developers (specifically those developers without a strong foundation in COM). I’ve also received a few emails from readers reinforcing my belief that its proper usage is not obvious to many developers.

There are two very important requirements that you must keep in mind. This applies equally to developers using C# or C++ for their implementations.

The first is that the IRTDUpdateEvent interface pointer that the RTD server receives must only be called from the apartment in which it was received. Since this interface pointer is received in a call to the RTD server’s ServerStart method it follows that this interface must only be called from the apartment that the RTD server lives in. This is where a strong foundation in COM is helpful. If you’re a .NET developer you’re probably scratching your head wondering what on earth an apartment is.

I’ll leave a discussion of apartments for another day but at a minimum you need to understand that all COM objects (such as an RTD server) are grouped into apartments. A COM object lives in exactly one apartment and methods of that object can only be called directly from within that apartment. There are two types of apartments. The single-threaded apartment consists by definition of a single thread so only code on that thread can make direct calls to objects living in that apartment. The multithreaded apartment consists of one or more threads and objects living in that apartment can be called directly from any thread that belongs to that apartment. There’s so much more to say about apartments but that should be sufficient for this discussion.

A C# RTD server running in the same process as Excel will be created in a single threaded apartment. Therefore calls to UpdateNotify must only be made from this same thread that the RTD server lives on. How do you know which thread this is? It’s the thread that calls your object’s constructor and all of the IRtdServer methods. How do you ensure that calls to UpdateNotify are only made from this thread outside of one of the IRtdServer methods? One way is to send a message to a hidden window that is also living on that thread. The message pump provided by the single threaded apartment will dispatch the message to a handler on the thread and that handler can call UpdateNotify. This is why both of the minimal C# and C++ implementations use a hidden window.

For completeness I should mention (for the sake of nitpickers) that you may receive (or construct) an interface pointer that actually refers to a proxy object that allows you to call methods on the object from a different apartment. Developers with a background in COM may lean toward this approach. This is however not as useful as it sounds since it can lead you to call UpdateNotify excessively from a worker thread.

And this leads to the second thing to keep in mind. Excel will not update RTD functions more frequently than every two seconds or so. Therefore you should not call UpdateNotify more frequently than that. Some developers assume that Excel will faithfully respond to each call to UpdateNotify with a corresponding call to RefreshData. This is not the case. Remember also that since UpdateNotify is handled by Excel’s UI thread that excessive calls may lead Excel to become unresponsive since its message pump may be overwhelmed.

If you’re looking for one of my previous articles here is a complete list of them for you to browse through.

Produce the highest quality screenshots with the least amount of effort! Use Window Clippings.

Posted by KennyKerr with 13 comment(s)

Excel RTD Servers: C# without the Excel Assembly Reference

In the description of a minimal C# implementation I suggested you simply reference the “Microsoft Excel Object Library” to get the definition of IRtdServer. Of course this introduces an unnecessary reference and can be problematic. This assembly ends up being tied to a particular version of Excel even though the COM interfaces for the RTD server remains the same. As we’ve seen in the C++ implementation, there’s no reason to have a hard reference to any Excel binaries and we can define all of the interfaces ourselves.

Rather that rely on Excel’s type library for the definitions you can define them yourself. Earlier in this series I showed you what the interfaces look like in C# but they weren’t usable on their own. The trick is that you need to tell the CLR how to marshal some of the more interesting parameters. Of course now that we’ve worked through a minimal C++ implementation you should be quite comfortable with the underlying types as seen by COM and Excel.

So it’s just a matter of using the MarshalAs attribute to tell the CLR how to marshal those multi-dimensional arrays. Specifically, they need to be marshaled as safe arrays of variants. Here’s what it looks like:

[Guid("A43788C1-D91B-11D3-8F39-00C04F3651B8")]
public interface IRTDUpdateEvent
{
    void UpdateNotify();

    int HeartbeatInterval { get; set; }

    void Disconnect();
}

[Guid("EC0E6191-DB51-11D3-8F3E-00C04F3651B8")]
public interface IRtdServer
{
    int ServerStart(IRTDUpdateEvent callback);

    object ConnectData(int topicId,
                       [MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_VARIANT)] ref Array strings,
                       ref bool newValues);

    [return: MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_VARIANT)]
    Array RefreshData(ref int topicCount);

    void DisconnectData(int topicId);

    int Heartbeat();

    void ServerTerminate();
}

You can now remove the Excel references and simplify your deployment overall.

If you’re looking for one of my previous articles here is a complete list of them for you to browse through.

Produce the highest quality screenshots with the least amount of effort! Use Window Clippings.

Posted by KennyKerr with 1 comment(s)

Excel RTD Servers: Multiple Topics in C#

The last few entries in this series got a bit long but I didn’t feel like spinning off a mini-series just to focus on COM fundamentals. I skimmed over a lot but hopefully it was enough to give you an idea of what’s involved. Hopefully from now on there should be enough groundwork in place to keep things short.

Having got a minimal single-topic RTD server working in C# and C++ it makes sense to take a look at what it takes to support multiple topics. Fortunately it’s quite straightforward. For starters we’ll update the minimal C# implementation to support multiple topics. But first let’s recap how topics are fed into the RTD server.

The Excel RTD function is prototyped as follows:

=RTD(progId, server, topic1, [topic2], ...)

Unfortunately this is a bit misleading. The topics here really should be called parameters. So an RTD function starts with a ProgId, an optional server name, and one or more parameters. Each unique list of parameters forms an individual topic for which Excel will call the RTD server’s ConnectData method.

Imagine you’ve populated four cells in Excel with the following functions:

A1 =RTD("ProgId", ,  "one")
A2 =RTD("ProgId", ,  "one")
A3 =RTD("ProgId", ,  "one", "two")
A4 =RTD("ProgId", ,  "two", "one")

Excel will call the RTD server’s ConnectData method once for A1 and A2 as they have the same list of parameters. It will then call ConnectData for both A3 and A4 as they have a different list of parameters. As far as the RTD server is concerned Excel is interested in three unique topics with each topic identified by a unique number provided by Excel. That number is how the RTD server communicates values back for individual topics when Excel calls the RTD server’s RefreshData method.

With that let’s update the C# RTD server to support multiple topics. The initial version just printed out the time as follows:

DateTime.Now.ToString("hh:mm:ss:ff")

Instead of hard coding the format string lets take the first parameter of the topic and use that as the format string so that the user can display the time in whatever format he or she desires.

Start by replacing the m_topicId member variable with a dictionary of topic Ids and format strings:

private Dictionary<int, string> m_topics;

You should create the dictionary in the ServerStart method and then add topics to it in the ConnectData method:

string format = strings.GetValue(0).ToString();
m_topics[topicId] = format;

The DisconnectData method also needs a bit of work. Previously we simply stopped the timer. Now that wouldn’t make sense as there may still be other topics that are in use. Instead we need to simply remove the specific topic from the dictionary.

Finally, the RefreshData method now has to enumerate the topics in the dictionary and build the multi-dimensional array. Remember that the first dimension is for the list of topic Ids and the second is for the values corresponding to those topics.

You can test it in Excel using a variety of RTD functions with different format strings. Here’s an example:

=RTD("Kerr.Sample.RtdServer", , "hh:mm:ss")

Below is the complete source code for the updated RTD server:

[
    Guid("B6AF4673-200B-413c-8536-1F778AC14DE1"),
    ProgId("Kerr.Sample.RtdServer"),
    ComVisible(true)
]
public class RtdServer : IRtdServer
{
    private IRTDUpdateEvent m_callback;
    private Timer m_timer;
    private Dictionary<int, string> m_topics;

    public int ServerStart(IRTDUpdateEvent callback)
    {
        m_callback = callback;

        m_timer = new Timer();
        m_timer.Tick += new EventHandler(TimerEventHandler);
        m_timer.Interval = 2000;

        m_topics = new Dictionary<int, string>();

        return 1;
    }

    public void ServerTerminate()
    {
        if (null != m_timer)
        {
            m_timer.Dispose();
            m_timer = null;
        }
    }

    public object ConnectData(int topicId,
                              ref Array strings,
                              ref bool newValues)
    {
        if (1 != strings.Length)
        {
            return "Exactly one parameter is required (e.g. 'hh:mm:ss').";
        }

        string format = strings.GetValue(0).ToString();

        m_topics[topicId] = format;
        m_timer.Start();
        return GetTime(format);
    }

    public void DisconnectData(int topicId)
    {
        m_topics.Remove(topicId);
    }

    public Array RefreshData(ref int topicCount)
    {
        object[,] data = new object[2, m_topics.Count];

        int index = 0;

        foreach (int topicId in m_topics.Keys)
        {
            data[0, index] = topicId;
            data[1, index] = GetTime(m_topics[topicId]);

            ++index;
        }

        topicCount = m_topics.Count;

        m_timer.Start();
        return data;
    }

    public int Heartbeat()
    {
        return 1;
    }

    private void TimerEventHandler(object sender,
                                   EventArgs args)
    {
        m_timer.Stop();
        m_callback.UpdateNotify();
    }

    private static string GetTime(string format)
    {
        return DateTime.Now.ToString(format, CultureInfo.CurrentCulture);
    }
}

If you’re looking for one of my previous articles here is a complete list of them for you to browse through.

Produce the highest quality screenshots with the least amount of effort! Use Window Clippings.

Posted by KennyKerr with no comments

Excel RTD Servers: Minimal C++ Implementation (Part 2)

Continuing on from part 1, here I’m concluding the walkthrough of a minimal RTD server written in C++.

It’s a COM Class

What we haven’t done yet is actually define the COM class to implement the RTD interface. Start by defining the class as follows:

class DECLSPEC_NOVTABLE DECLSPEC_UUID("B9DCFAAD-4F86-44d4-B404-9E530397D30A") RtdServer :
    public CComObjectRootEx<CComSingleThreadModel>,
    public CComCoClass<RtdServer, &__uuidof(RtdServer)>,
    public IDispatchImpl<IRtdServer>
{

It’s not actually as daunting as it looks.

DECLSPEC_NOVTABLE is an optional hint to the compiler indicating that we don’t need the vtable, which can result in a reduction in code size.

DECLSPEC_UUID associates a GUID with the class so that you can later reference it using the __uuidof keyword. This avoids having to define the GUID elsewhere in a CPP file for example.

CComObjectRootEx provides the code for implementing the reference counting portion of the IUnknown interface that IDispatch derives from.

CComCoClass provides the code for creating instances of the COM class.

IDispatchImpl provides the implementation of IDispatch. I’ll talk more about this implementation in the upcoming section on Automation.

Next up is the implementation of IUnknown’s QueryInterface method. For this ATL provides a family of macros:

BEGIN_COM_MAP(RtdServer)
    COM_INTERFACE_ENTRY(IRtdServer)
    COM_INTERFACE_ENTRY(IDispatch)
END_COM_MAP()

This actually just builds a data structure and a set of supporting functions. The actual IUnknown methods are provided by another class such as CComObject used to create instances of a particular COM class.

We also need to identify a resource that includes the registration script for the class:

DECLARE_REGISTRY_RESOURCEID(IDR_RtdServer)

For this to work you need to add a resource script to your project and add a text file as a "REGISTRY" resource type. Here’s what a minimal registration script looks like for an in process RTD server:
    
HKCR
{
    Kerr.Sample.RtdServer
    {
        CLSID = s '{B9DCFAAD-4F86-44d4-B404-9E530397D30A}'
    }
    NoRemove CLSID
    {
        ForceRemove {B9DCFAAD-4F86-44d4-B404-9E530397D30A} = s 'Kerr.Sample.RtdServer'
        {
            InprocServer32 = s '%MODULE%'
            {
                val ThreadingModel = s 'Apartment'
            }
        }
    }
}

This script just adds a ProgId and CLSID to the registry so that instances can be created given one or the other. It also defines the threading model for the COM class indicating in what type of apartment the COM class expects to be created. In this case, I’ve specified “Apartment” to indicate that the COM class needs to be created on a thread that is initialized as a single thread apartment and includes the necessary plumbing to support that apartment model namely a message pump. ATL takes care of parsing the script when registering and unregistering the COM server and updating the registry accordingly.

Now we can finally declare the interface methods that we must implement ourselves:

HRESULT __stdcall ServerStart(/*[in]*/ IRTDUpdateEvent* callback,
                              /*[out]*/ long* result) override;

HRESULT __stdcall ConnectData(/*[in]*/ long topicId,
                              /*[in]*/ SAFEARRAY** strings,
                              /*[in,out]*/ VARIANT_BOOL* newValues,
                              /*[out]*/ VARIANT* values) override;

HRESULT __stdcall RefreshData(/*[in,out]*/ long* topicCount,
                              /*[out]*/ SAFEARRAY** data) override;

HRESULT __stdcall DisconnectData(/*[in]*/ long topicId) override;

HRESULT __stdcall Heartbeat(/*[out]*/ long* result) override;

HRESULT __stdcall ServerTerminate() override;

And that’s it for the class definition. The only thing remaining is to add the class to ATL’s map of COM classes so that it can automatically register, unregister, and create instances of it.

OBJECT_ENTRY_AUTO(__uuidof(RtdServer), RtdServer)

It’s an Automation Server

Component Automation, more commonly known as OLE Automation, is a set of additional specifications built on top of the COM specification geared towards improving interoperability with scripting languages, tools and applications. There are pros and cons to Automation. For example the Automation Marshaller can be very handy in lieu of custom proxies even for C++-only systems. On the other hand Automation types and interfaces can be quite unwieldy to use from C++. Fortunately ATL does a great job of making Automation programming in C++ a whole lot less painful.

Although there are many parts to Automation, we only need to cover a few of them to support the RTD server. Firstly we need to implement the IDispatch-based RTD interface. ATL’s implementation of IDispatch relies on a type library so we’ll need one of those too. Finally, the RTD interface relies on Automation safe arrays and variants so we’ll need to know how to handle those.

An interface, like IRtdServer, that derives from IDispatch is known as a dual interface because it allows interface methods to be access either directly via the interface vtable or indirectly via the methods provided by IDispatch for resolving a method by name and then invoking it. Excel does the latter. ATL provides a generic implementation of IDispatch that relies on a type library to resolve method names and invoke methods. If you’re coming from a .NET background, a type library is the precursor to CLR metadata.

A type library is generated by the MIDL compiler given an IDL file as input. IDL is used for much more than just generating type libraries but that’s all we need it for right now. In a future post I’ll share some other tricks that you can perform with IDL and RTD servers.

Start by adding a “Midl File” called TypeLibrary.idl to your project. Here’s what it should contain. I won’t go into detail into what this all means but it should be pretty self-explanatory and it should not surprise you to learn that IDL stands for Interface Definition Language.  :)

import "ocidl.idl";

[
  uuid(A43788C1-D91B-11D3-8F39-00C04F3651B8),
  dual,
  oleautomation
]
interface IRTDUpdateEvent : IDispatch
{
    [id(0x0000000a)]
    HRESULT UpdateNotify();

    [id(0x0000000b), propget]
    HRESULT HeartbeatInterval([out, retval] long* value);

    [id(0x0000000b), propput]
    HRESULT HeartbeatInterval([in] long value);

    [id(0x0000000c)]
    HRESULT Disconnect();
};

[
  uuid(EC0E6191-DB51-11D3-8F3E-00C04F3651B8),
  dual,
  oleautomation
]
interface IRtdServer : IDispatch
{
    [id(0x0000000a)]
    HRESULT ServerStart([in] IRTDUpdateEvent* callback,
                        [out, retval] long* result);

    [id(0x0000000b)]
    HRESULT ConnectData([in] long topicId,
                        [in] SAFEARRAY(VARIANT)* strings,
                        [in, out] VARIANT_BOOL* newValues,
                        [out, retval] VARIANT* values);

    [id(0x0000000c)]
    HRESULT RefreshData([in, out] long* topicCount,
                        [out, retval] SAFEARRAY(VARIANT)* data);

    [id(0x0000000d)]
    HRESULT DisconnectData([in] long topicId);

    [id(0x0000000e)]
    HRESULT Heartbeat([out, retval] long* result);

    [id(0x0000000f)]
    HRESULT ServerTerminate();
};

[
    uuid(358F1355-AA45-4f59-8838-9A21E7F4628C),
    version(1.0)
]
library TypeLibrary
{
    interface IRtdServer;
};

The MIDL compiler parses this file and produces a few things. The main thing we need is a type library but it also produces the C and C++ equivalent of the IDL interface definitions so that we don’t have to define those ourselves. The type library produced by the MIDL compiler needs to be included in the DLL as a resource. You can achieve this by adding the following to your resource script:

1 TYPELIB "TypeLibrary.tlb"

Finally, you can update your ATL module class to tell it where to find the type library:

class Module : public CAtlDllModuleT<Module>
{
public:

    DECLARE_LIBID(LIBID_TypeLibrary)
};

LIBID_TypeLibrary will be declared in the header file and defined in the C source file produced by the MIDL compiler.

It’s an RTD Server

With all that out of the way we can finally add the actual minimal RTD server implementation. For this example I’m just going to port the minimal C# implementation that simply supports a single topic displaying the time.

We need just a few variables to make it work:

TimerWindow m_timer;
long m_topicId;

TimerWindow is simple C++ implementation of the Windows Forms Timer class used by the C# implementation. It just creates a hidden window to handle WM_TIMER messages and then calls the RTD callback interface on this timer:

class TimerWindow : public CWindowImpl<TimerWindow, CWindow, CWinTraits<>>
{
private:

    CComPtr<IRTDUpdateEvent> m_callback;

    void OnTimer(UINT_PTR /*timer*/)
    {
        Stop();

        if (0 != m_callback)
        {
            m_callback->UpdateNotify();
        }
    }

public:

    BEGIN_MSG_MAP(TimerWindow)
        MSG_WM_TIMER(OnTimer)
    END_MSG_MAP()

    TimerWindow()
    {
        Create(0);
        ASSERT(0 != m_hWnd);
    }

    ~TimerWindow()
    {
        VERIFY(DestroyWindow());
    }

    void SetCallback(IRTDUpdateEvent* callback)
    {
        m_callback = callback;
    }

    void Start()
    {
        SetTimer(0, 2000);
    }

    void Stop()
    {
        VERIFY(KillTimer(0));
    }
};

As with the C# implementation, since the timer relies on window messages it requires a message pump to function. Fortunately this particular RTD server lives in a single threaded apartment that provides one.

Now we can implement the RTD server methods quite simply. I’m not going to explain here why they’re called as I’ve already talked about the semantics in the walkthrough of the minimal C# implementation.

HRESULT ServerStart(/*[in]*/ IRTDUpdateEvent* callback,
                              /*[out]*/ long* result)
{
    if (0 == callback || 0 == result)
    {
        return E_POINTER;
    }

    m_timer.SetCallback(callback);
    *result = 1;
    return S_OK;
}

ServerStart passes the callback interface to the timer which holds a reference to it. It returns 1 to indicate that all is well.

HRESULT ServerTerminate()
{
    m_timer.SetCallback(0);
    return S_OK;
}

ServerTerminate clears the callback held by the timer to ensure that it isn’t accidentally called subsequent to termination.

HRESULT ConnectData(/*[in]*/ long topicId,
                              /*[in]*/ SAFEARRAY** strings,
                              /*[in,out]*/ VARIANT_BOOL* newValues,
                              /*[out]*/ VARIANT* values)
{
    if (0 == strings || 0 == newValues || 0 == values)
    {
        return E_POINTER;
    }

    m_topicId = topicId;
    m_timer.Start();
    return GetTime(values);
}

ConnectData saves the topic identifier, starts the timer, and returns the initial time.

HRESULT GetTime(VARIANT* value)
{
    ASSERT(0 != value);

    SYSTEMTIME time;
    ::GetSystemTime(&time);

    CComBSTR string(8);

    swprintf(string,
             string.Length() + 1,
             L"%02d:%02d:%02d",
             time.wHour,
             time.wMinute,
             time.wSecond);

    value->vt = VT_BSTR;
    value->bstrVal = string.Detach();

    return S_OK;
}

GetTime is a simple helper function that produces a string with the current time and returns it as a variant.

HRESULT DisconnectData(/*[in]*/ long /*topicId*/)
{
    m_timer.Stop();
    return S_OK;
}

DisconnectData simply stops the timer.

HRESULT Heartbeat(/*[out]*/ long* result)
{
    if (0 == result)
    {
        return E_POINTER;
    }

    *result = 1;
    return S_OK;
}

Heartbeat simply returns 1 to indicate that all is well.

HRESULT RefreshData(/*[in,out]*/ long* topicCount,
                               /*[out]*/ SAFEARRAY** result)
{
    if (0 == topicCount || 0 == result)
    {
        return E_POINTER;
    }

    CComSafeArrayBound bounds[2] =
    {
        CComSafeArrayBound(2),
        CComSafeArrayBound(1)
    };

    CComSafeArray<VARIANT> data(bounds, _countof(bounds));
    LONG indices[2] = { 0 };

    HR(data.MultiDimSetAt(indices, CComVariant(m_topicId)));

    CComVariant time;
    HR(GetTime(&time));

    indices[0] = 1;
    HR(data.MultiDimSetAt(indices, time));

    *result = data.Detach();

    *topicCount = 1;
    m_timer.Start();
    return S_OK;
}

The RefreshData method is where you need to finally deal with Automation safe arrays and you can start to appreciate just how much work the CLR’s marshaller does for you. Fortunately ATL provides a fairly clean wrapper for the various structures and API functions needed to create and interact with safe arrays. RefreshData creates a safe array by first defining its dimensions and bounds using ATL’s CComSafeArrayBound class. Here the safe array will have two dimensions. The first dimension has two elements and the second has one. The first dimension of the array will always have two elements. The first element is for the topic Ids and the second is for the values. The second dimension will have as many elements as there are topics with data to return. In our case there is only one.

The rest of the implementation just goes about populating the safe array with the topic Id and value and then restarts the timer.

That’s all for today. I skimmed over many details as this entry was getting way too long, but I hope this walkthrough has given you some idea of what’s involved in writing a minimal RTD server in C++.

With that out of the way, I can start talking about some of the more interesting challenges and techniques you can employ in your implementations and various other topics related to RTD server development.

If you’re looking for one of my previous articles here is a complete list of them for you to browse through.

Produce the highest quality screenshots with the least amount of effort! Use Window Clippings.

Posted by KennyKerr with 2 comment(s)
More Posts