daniel_bigham
asked on
ATL COM Question: Array Properties
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?
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?
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
Here is an article
http://www.codeproject.com/dll/ctovbarray_passing.asp - Passing an array from VC++ DLL to VB
Good Luck
Try this too http://www.codeproject.com/atl/udtdemo.asp
ASKER
I've read that article, but it sounds like they're doing something different. I don't want to pass an array.
Ooops sorry, You mean control array...??
Thatz the property of VB right?
Thatz the property of VB right?
ASKER
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.
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.
I have to check.
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.
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.
ASKER
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?
Will "Item" be a method, not a property? What will be its return type and parameters?
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
ASKER
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... !!
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... !!
ASKER
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.
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 width="25%" bgcolor="#888888"><font color="#ffffff">B</font></
<td width="25%" bgcolor="#888888"><font color="#ffffff">C</font></
<td width="25%" bgcolor="#888888"><font color="#ffffff">D</font></
</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.
Good luck with your endeavors. No doubt we'll meet again.
ASKER
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.
For instance, I need to implement the methods like get__NewEnum, but I don't have any clue what code it needs to contain.
ASKER
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
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
np.
Give me a while to compose this for you.
Give me a while to compose this for you.
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(IUnkno wn** ppEnum)
{
if (NULL == ppEnum) return E_POINTER;
return EnumRows(const_cast<IID*>( &IID_IRow) , reinterpret_cast<void**>(p pEnum));
}
// 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.bstr Val), 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(*lCo unt);
}
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-xx xx-xxxxxxx xxxxx),
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*>(&II D_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();
}
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(IUnkno
{
if (NULL == ppEnum) return E_POINTER;
return EnumRows(const_cast<IID*>(
}
// 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(
// 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
case VT_I2:
return this->GetRow(index.lVal, pRow);
case VT_I2 | VT_BYREF:
return this->GetRow(*(index.plVal
// 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(*lCo
}
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
pRows->Release();
return hr;
}
BTW, IEnumRow looks like:
[
object,
uuid(xxxxxxxx-xxxx-xxxx-xx
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**
{
// 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
if (SUCCEEDED(hr))
{
IRow* pRow[1] = {NULL};
while (SUCCEEDED(hr) && (S_FALSE != hr))
pEnumRow->Next(1, &pRow[0], NULL);
pEnumRow->Release();
}
Enjoy !!
ASKER
Will this code compile, or are there things within it that need to be implemented yet?
Some things need replaced:
uuid(xxxxxxxx-xxxx-xxxx-xx xx-xxxxxxx xxxxx)
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::wstr ing &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.
uuid(xxxxxxxx-xxxx-xxxx-xx
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::wstr
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.
ASKER
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.
Mysterious, but not unreachable.
ASKER