Solved

ATL COM Question: Array Properties

Posted on 2003-12-01
22
810 Views
Last Modified: 2013-11-25
I'm just starting to dabble with ATL/COM in VC++.

I'd like to add a property which is an array.

For instance, with the VB listbox control, you can do:

MyListBox.List(5) = "Hey!"

How can I add an array property to my control using VC++ ATL/COM?

0
Comment
Question by:daniel_bigham
  • 10
  • 8
  • 4
22 Comments
 
LVL 1

Author Comment

by:daniel_bigham
Comment Utility
Just a clarification: I'm creating a DLL that you include via VB's References, not an ActiveX Control.
0
 
LVL 23

Expert Comment

by:Roshan Davis
Comment Utility
You can use SAFEARRAY for passing an array to an ATL COM component.

Here is an article
http://www.codeproject.com/dll/ctovbarray_passing.asp - Passing an array from VC++ DLL to VB

Good Luck
0
 
LVL 23

Expert Comment

by:Roshan Davis
Comment Utility
0
 
LVL 1

Author Comment

by:daniel_bigham
Comment Utility
I've read that article, but it sounds like they're doing something different. I don't want to pass an array.
0
 
LVL 23

Expert Comment

by:Roshan Davis
Comment Utility
Ooops sorry, You mean control array...??
Thatz the property of VB right?
0
 
LVL 1

Author Comment

by:daniel_bigham
Comment Utility
Actually no, it's not a control array.

List1.List(5) = "blah"

This code sets the text of the item of index 5 in listbox "List1" to be "blah". It's not an array of control. It's a property of a list box that acts as an array.
0
 
LVL 23

Expert Comment

by:Roshan Davis
Comment Utility
I have to check.
0
 
LVL 9

Expert Comment

by:_ys_
Comment Utility
What you're trying to achieve is known as a default property.

MyListBox.List(5) = "Hey!"
is equivalent to
MyListBox.List.Item(5) = "Hey!"

where Item is the actual function name.
[Could be Items or another variation thereof. I'm not Vb-esque].

Default properties are only supported by automation clients, so even if you do implement it (and it is possible in COM), you still have to implement a non automation aware variation for those clients - typically C++.

Better to just create an Item method and have it accept a VARIANT argument, just as VB does. That way you can index items using ordinals or Ids. And it'll also be accesible by both automation and non-automation clients.
0
 
LVL 1

Author Comment

by:daniel_bigham
Comment Utility
Great. Thanks for your help. So I want to look into creating methods which use VARIANT arguments?

Will "Item" be a method, not a property? What will be its return type and parameters?
0
 
LVL 9

Accepted Solution

by:
_ys_ earned 250 total points
Comment Utility
>I'm just starting to dabble with ATL/COM in VC++.
I understand you're probably not COM-savvy yet. Try to gain as much understanding from the following as possible. Any questions just ask.


Two obvious approaches. One for automation only clients - scripting languages, and one for automation aware clients - VB, VC++, etc.


Automation only
------------------
[
  uuid(xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
]
dispinterface IWhatevers {
    properties:
    methods:
        [id(0x00000001)]
        IDispatch* Item(VARIANT vIndex);
};

The return value would typically be another dispinterface (IWhatever).


Automation aware
---------------------
[
    object,
    uuid(xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx),
    helpstring("IWhatevers Interface"),
    pointer_default(unique)
]
interface IWhatevers : IUnknown
{
    [propget, restricted, id(DISPID_NEWENUM), helpstring("property _NewEnum")]
    HRESULT _NewEnum([out, retval] IUnknown** ppVal);

    [id(DISPID_VALUE), helpstring("method Item")]
    HRESULT Item([in] VARIANT index, [out, retval] IWhatever** ppItem);

    [propget, id(1), helpstring("property Count")]
    HRESULT Count([out, retval] long *pVal);

    [helpstring("method EnumWhatevers"), restricted]
    HRESULT EnumWhatevers([in] REFIID riid, [out, iid_is(riid)] void** ppEnum);
};

IWhatever is typically another interface.
Notice that the Item method, again, takes a VARIANT parameter.


A couple of additional points.

The 'automation only' implementation will work for automation aware clients - it's just clunky and strewn with performance overheads.

If you've used VB you'll undoubtably be aware of 'For Each'. The presence of _NewEnum [id(DISPID_NEWENUM),] allows this to happen with this interface.
On the same note the presence of Item [id(DISPID_VALUE)] allows the all-to familiar:

MyListBox.List(5) = "Hey!"

code to work as well - no need to explicitly state the name of the method 'Item'.


Personally, I'd prefer the second approach as it's more performant. But that's just me. Everyone to their own.
0
 
LVL 1

Author Comment

by:daniel_bigham
Comment Utility
Wow. Awesome response.

I'll take this code and see if I can get it to work. I'm just playing at this point wanting to write a COM component so that I can say "I've done that" incase someone asks "Have you ever", but I'm busy learning other things as well and I don't want to devote the time to go out, buy a book and read it. Besides, COM is an older technology that .NET ... I think... supercedes for the most part.

But like I said, I'd like to have some familiarity with it.

Thanks so much for your help. I was beginning to think my little COM endeavor was a dead end but you've given me what I need... !!
0
What Should I Do With This Threat Intelligence?

Are you wondering if you actually need threat intelligence? The answer is yes. We explain the basics for creating useful threat intelligence.

 
LVL 1

Author Comment

by:daniel_bigham
Comment Utility
Just incase you're interested what I'm attempting to do with COM, here's the VB code that would make use of it. My component would be called "Table".

Dim t as Table
Set t = New Table

t.Rows = 2
t.Columns = 4

t.Width = 100
t.CellPadding = 0

t.Row(1).HAlign = tblCenter
t.Row(1).BackColor = RGB(128, 128, 128)
t.Row(1).TextColor = RGB(255, 255, 255)

t.Column(1).Width = 25
t.Column(2).Width = 25
t.Column(3).Width = 25
t.Column(4).Width = 25

t.SetCell 1, 1
t.SetPrintDirection tblHorizontal
t.Print "A"
t.Print "B"
t.Print "C"
t.Print "D"

t.Cell(2,1) = "Hello, world!"
t.Cell(2,1).NoWrap = True

t.Draw PictureBox1.HDC

----

And this would draw in the picture box the equivalent as Internet Explorers table defined by:

<table width="100%" cellspacing="0" cellpadding="0">
<tr align="center">
  <td width="25%" bgcolor="#888888"><font color="#ffffff">A</font></td>
  <td width="25%" bgcolor="#888888"><font color="#ffffff">B</font></td>
  <td width="25%" bgcolor="#888888"><font color="#ffffff">C</font></td>
  <td width="25%" bgcolor="#888888"><font color="#ffffff">D</font></td>
</tr>
<tr>
  <td width="25%" nowrap>Hello, world!</td>
  <td width="25%"></td>
  <td width="25%"></td>
  <td width="25%"></td>
</tr>

----

The idea to be able to create nicely formatted tables of information with the same flexability you have in IE, and to be able to display them in a standard Windows application.

Notice the line:

t.Cell(2,1) = "Hello, world!"

That is the sort of thing I want to do that caused me to ask the question.

Also,

t.Column(1).Width = 25

The whole "array" thing. Anyway, I'll see if I can make it work.
0
 
LVL 9

Expert Comment

by:_ys_
Comment Utility
Good luck with your endeavors. No doubt we'll meet again.
0
 
LVL 1

Author Comment

by:daniel_bigham
Comment Utility
Ok, I wasn't as close to victory as I thought.

For instance, I need to implement the methods like get__NewEnum, but I don't have any clue what code it needs to contain.
0
 
LVL 1

Author Comment

by:daniel_bigham
Comment Utility
Maybe it would be easiest for you to email me a ZIP file containing a simple example of an implementation that has three objects:

Table, Rows, Row

IRows = IWhatevers
IRow = IWhatever

And IRow would have once property, BackColor.

So from VB you could do:

Dim MyTable as Table
Set MyTable = New Table
For Each Row r in MyTable.Rows
    r.BackColor = RGB(255,0,0)
Next

I think that would be enough to get me going... your response above fills in a lot of the details but I'm left with all sorts of questions without being able to see an implementation.

If it's really simple to do, that would be great. If implementing those objects is actually a fair bit of work, then I'll just have to keep plugging.

My email address is:
daniel.bigham@bigham.ca
0
 
LVL 9

Expert Comment

by:_ys_
Comment Utility
np.

Give me a while to compose this for you.
0
 
LVL 9

Expert Comment

by:_ys_
Comment Utility
In line with knowledge sharing, and since it's related to the question, I'll post it here, rather then zipping and emailing it.

Besides it's not too long ...


The sample code demonstrates the core functionality required to reproduce a collection class, which is then usable from non automation-only clients. i.e *NOT SCRIPTING LANGUAGES*

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

A common implementation is the IEnumXxxx suite of interfaces.

You'll have your CRow class.
A class CRows, a facade to an underlying collection.
A class CColRows, the real collection of rows.

class CRow : public IRow
{
...
};

class CRows : public IRows
{
public:
    CRows() : m_colRows(0) { m_pColRows = new CColRows({context}); }

    ~CRows() { delete m_pColRows; }
...
public:
//  IRows methods
    STDMETHOD(get__NewEnum) (IUnknown**);
    STDMETHOD(Item) (VARIANT, IRow**);
    STDMETHOD(get_Count) (long*);
    STDMETHOD(EnumRows) (IID*, void**);

private:
    CColRows* m_pColRows; // the underlying collection
};

class CColRows : public IEnumRow
{
    CColRows({context}) : m_lCount(0), m_lIndex(0) { ... }
...
public:
//  IEnumRow methods
    STDMETHOD(Next) (ULONG, IRow**, ULONG*);
    STDMETHOD(Skip) (ULONG);
    STDMETHOD(Reset) (void);
    STDMETHOD(Clone) (IEnumRow**);

private:
    {representation of collection ... std::map etc..}
    unsigned long m_lCount;
    long m_lIndex;
};

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

// Delegate to EnumRows - since they do the same thing
HRESULT CRows::get__NewEnum(IUnknown** ppEnum)
{
    if (NULL == ppEnum) return E_POINTER;

    return EnumRows(const_cast<IID*>(&IID_IRow), reinterpret_cast<void**>(ppEnum));
}

// Delegate to the underlying collection
HRESULT CRows::Item(VARIANT index, IRow** ppRow)
{
    if (NULL == ppRow) return E_POINTER;

    return m_pColRows->Item(index, *pRow);
}

HRESULT CColRows::Item(VARIANT &index, IRow* &pRow)
{
    switch(index.vt)
    {
    //  If the VARIANT is a BSTR, let's look up the Row by identifer!
        case VT_BSTR:
            return this->GetRow(std::wstring(index.bstrVal), pRow);

    //  If the VARIANT is a number, let's look up the Row by index!
        case VT_I4:
            return this->GetRow(index.lVal, pRow);
        case VT_I4 | VT_BYREF:
            return this->GetRow(*(index.plVal), pRow);
    case VT_I2:
            return this->GetRow(index.lVal, pRow);
        case VT_I2 | VT_BYREF:
            return this->GetRow(*(index.plVal), pRow);

    //  If the VARIANT is neither of those, we'll fail.
        default:
            return (pRow = NULL), E_FAIL;
    }
}

//  Delegate to the underlying collection
HRESULT CRows::get_Count(long* lCount)
{
    if (NULL == lCount) return E_POINTER;

    return m_pColRows->get_Count(*lCount);
}

HRESULT CColRows::get_Count(long &lCount)
{
      lCount = ...
      return S_OK;
}

//  Create a new snapshot of the collection.
//  Every enumeration _has_ to be distinct.
//  - Wouldn't be much use if all 'for each' statements iterated the _same instance_ !!
HRESULT CRows::EnumRows(IID* piid, void** ppEnum)
{
    if (NULL == ppEnum) return E_POINTER;

    CColRows* pRows = new CColRows({context});
    pRows->AddRef();
    HRESULT hr = pRows->QueryInterface(piid, ppEnum);
    pRows->Release();

    return hr;
}


BTW, IEnumRow looks like:
[
    object,
    uuid(xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx),
    helpstring("IEnumRow Interface"),
    pointer_default(unique),
    restricted
]
interface IEnumRow : IUnknown
{
    [helpstring("method Next")]
    HRESULT Next([in] ULONG celt, [out, size_is(celt), length_is(*pcFetched)] IRow** rgptr, [out] ULONG* pcFetched);

    [helpstring("method Skip")]
    HRESULT Skip([in] ULONG celt);

    [helpstring("method Reset")]
    HRESULT Reset(void);

    [helpstring("method Clone")]
    HRESULT Clone([out] IEnumRow** ppEnum);
};


HRESULT CColRows::Next(ULONG celt, IRow** rgptr, ULONG* pcFetched)
{
    if ((1 < celt) && (NULL == pcFetched)) return E_INVALIDARG;

    if (pcFetched) *pcFetched = 0;

    HRESULT hr = S_OK;
    unsigned long ulCount = 0;

    for (unsigned long i = 0; i < celt; ++i)
    {
        ++m_lIndex;

        if ((unsigned long)m_lIndex > m_lCount)
            break;

        if (NULL == rgptr) return E_POINTER;

    //  place an CRow [/IRow pointer] into *(rgptr++)
        if (FAILED(hr = this->NextRow(*(rgptr++))))
            break;

        ++ulCount;
    }

//  celt != *pcFetched
    if (celt != ulCount)
        hr = S_FALSE;

    if (NULL != pcFetched)
        *pcFetched = ulCount;

    return hr;
}

HRESULT CColRows::Skip(ULONG celt)
{
    HRESULT hr = S_OK;

    m_lIndex += celt;

    if ((unsigned long)m_lIndex > m_lCount)
    {
    //  c elt not skipped
        m_lIndex = m_lCount;
        hr = S_FALSE;
    }

    return hr;
}

HRESULT CColRows::Reset()
{
    m_lIndex = -1;
    return S_OK;
}

HRESULT CColRows::Clone(IEnumRow** ppEnum)
{
//  Repeat of CRows::EnumRows - create a new instance and QI etc.
}

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

A VB client could use this as:

Dim colRows As IRows
Set colRows = ...

For Each IRow In colRows
...


This is equivalent to the VC++ clients:

IRows* pRows = ...;

HRESULT hr;
IEnumRow* pEnumRow = NULL;
hr = pRows->EnumRows(const_cast<IID*>(&IID_IEnumRow), reinterpret_cast<void**>(&pEnumRow));

if (SUCCEEDED(hr))
{
    IRow* pRow[1] = {NULL};

    while (SUCCEEDED(hr) && (S_FALSE != hr))
        pEnumRow->Next(1, &pRow[0], NULL);

    pEnumRow->Release();
}


0
 
LVL 9

Expert Comment

by:_ys_
Comment Utility
Enjoy !!
0
 
LVL 1

Author Comment

by:daniel_bigham
Comment Utility
Will this code compile, or are there things within it that need to be implemented yet?
0
 
LVL 9

Expert Comment

by:_ys_
Comment Utility
Some things need replaced:

uuid(xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
all elipses
contextual comments {representation of collection ... std::map etc..}, {context}

You'll also need to provide an implementation for CColRows::GetRow.

HRESULT CColRows::GetRow(int iIndex, IRow* &pRow);
HRESULT CColRows::GetRow(std::wstring &sIndex, IRow* &pRow);

These just find a concrete CRow, having the index provided, and assign it to pRow - if your using std::map as your _real_ collection, just rip it from here.


If I was to do everything, you wouldn't learn as much.
0
 
LVL 1

Author Comment

by:daniel_bigham
Comment Utility
Ok, thanks a bunch for your help. I'll see if I can "make it go"... this COM stuff sure is mysterious when you haven't read a book on it.

0
 
LVL 9

Expert Comment

by:_ys_
Comment Utility
Mysterious, but not unreachable.
0

Featured Post

How to run any project with ease

Manage projects of all sizes how you want. Great for personal to-do lists, project milestones, team priorities and launch plans.
- Combine task lists, docs, spreadsheets, and chat in one
- View and edit from mobile/offline
- Cut down on emails

Join & Write a Comment

Suggested Solutions

Introduction: Dialogs (1) modal - maintaining the database. Continuing from the ninth article about sudoku.   You might have heard of modal and modeless dialogs.  Here with this Sudoku application will we use one of each type: a modal dialog …
For most people, the WrapPanel seems like a magic when they switch from WinForms to WPF. Most of us will think that the code that is used to write a control like that would be difficult. However, most of the work is done by the WPF engine, and the W…
The purpose of this video is to demonstrate how to set up the WordPress backend so that each page automatically generates a Mailchimp signup form in the sidebar. This will be demonstrated using a Windows 8 PC. Tools Used are Photoshop, Awesome…
This video will show you how to get GIT to work in Eclipse.   It will walk you through how to install the EGit plugin in eclipse and how to checkout an existing repository.

771 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

10 Experts available now in Live!

Get 1:1 Help Now