 |
|
 |
Is it possible have more than one COM Collection in one interface? For example:
[id(DISPID_VALUE), propget]
HRESULT Output([in] VARIANT Index, [out, retval] VARIANT* value);
[id(DISPID_NEWENUM), propget]
HRESULT Outputs([out,retval] IUnknown** Enu);
[id(DISPID_VALUE), propget]
HRESULT Input([in] VARIANT Index, [out, retval] VARIANT* value);
[id(DISPID_NEWENUM), propget]
HRESULT Inputs([out,retval] IUnknown** Enu);
When build it, show me errror
error MIDL2021: duplicate id : [ Procedure 'get_Input' ( Interface ....
....
|
|
|
|
 |
|
 |
I know that this a somewhat old question, but since no one has responded, I'll do so briefly...
That is not possible.
Interface properties having the same id must have the same name and a complementary signature. Otherwise, you break the rules of IDispatch.
You should create two collection interfaces, implement them as separate classes. If you wish, you could create a third interface that would give access to both of the collections.
|
|
|
|
 |
|
 |
It is important to note that in fact methods Add and Remove are not part of the COM Collection protocol. Only the following elements are really required to implement a COM Collection: _NewEnum, Item and Count.
And then beside those 3 required elements you can have any number of other methods and properties to your liking that will extend/customize your collection.
|
|
|
|
 |
|
 |
hi,Igor
these days i m searching for some solutions to use collection in com,and i know this is the right one i just wanna as soon as i got this article,it's excellent.
however,there's a little question.
in test.cpp,the second odd for cycle statement was as the same as the one before,but,their results were far different,which is as follows:
Added item: Bill Clinton
Added item: George Bush
Added item: 1941
Added item: 1945
Added item: 1945
Added item: 1945
Added item: 1941
Added item: 1945
why didn't the second for statement come to the same result as expected.
it's not difficult to solve it,after i changed the "_variant_t& GetValue();" into " _variant_t GetValue();",the second result is as the same as the first.
however,i still don't know the detail when and where the element in collection was modified by mistake before i changed.
could you show me some more technology in details?
thanks.
|
|
|
|
 |
|
 |
maybe i got the answer,i think.
when i saw the code generated by compiler in ShoppingCart.tli as follows:
[inline _variant_t IXCollection::GetItem ( const _variant_t & vIndex ) {
VARIANT _result;
VariantInit(&_result);
HRESULT _hr = get_Item(vIndex, &_result);
if (FAILED(_hr)) _com_issue_errorex(_hr, this, __uuidof(this));
return _variant_t(_result, false);
}]
here the "return _variant_t(_result, false);",in which the first parameter of the _variant_t constructor is explained on msdn,'If fAddRef is false, this constructor takes ownership of the supplied interface pointer'.
that's why the second cycle could not show the same results as the first one.
btw,according to stl's approach,the container only support value semantic,
why not value semantic here.so i prefer value semantic instead of reference semantic in the GetValue in CXItem.
what do u think about it,Igor?
thanks.
|
|
|
|
 |
|
 |
Jeffrey,
I am not sure if I understand your question correctly. Could you please rephrase it?
_variant_t& GetValue(); and _variant_t GetValue(); are different declarations: one returns by reference and the other one by value.
|
|
|
|
 |
|
 |
Igor,
i'm sorry for my unclear expression.
the foremost question is the two different results from the two same cycle statement,which i had described at the fore.and i think the arbitrary times we access the collection the same result we shoud get.
i certainly understand the reference declaration and value declaration,
but,i think both the reference semantic and value semantic should not
result in the strange ending,cause we didn't change the value gained from collection manually by mistake.
who did that?
it must be "return _variant_t(_result, false);",which was found in ShoppingCart.tli,this statement would capture the ownership from the source object,and crack the source data.that is to say,the element in source collection must be cracked after this operation.i can't understand why the ms's compiler generates the code like this.the way to avoid this mistake is to use value semantic instead of reference semantic,i.e,_variant_t GetValue()
instead of _variant_t& GetValue().
thanks.
|
|
|
|
 |
|
 |
It's been awhile, let me investigate it. Could be a bug. Thanks.
|
|
|
|
 |
|
 |
Hi Igor,
Thank you for the code, it is very helpful to me, and our discussion about coclass really helped me go forward with my project.
I just thought i'd mention what i believe is a coding error, just so that your sample code comes out cleaner.
In CXCollection::get__NewEnum(...) you have the following code:
hr = CComObject<CXEnumVariant>::CreateInstance(&pEnumVar);
if (FAILED(hr))
</code>hr = E_OUTOFMEMORY;<code>
pEnumVar->AddRef();
the particular point is the black code. Shouldnt this be return E_OUTOFMEMORY ?
DarkByte
|
|
|
|
 |
|
 |
You are absolutely correct. Thank you.
Igor.
|
|
|
|
 |
|
 |
What is the reason that you created CXEnumVARIANT as a coclass ?
Is it so that you can instantiate it from scripint technologies like VB or JScript ?
I'm using your collection as a learning tool into COM, and for my need, i'll have a few specific collections like i have a "Domain" class, a "Domains" collection which will provide a "_Domains" enumerator.
Would it be a bad thing if i kept my enumerators as Interface instead of making noncreatable coclasses ?
DarkByte
|
|
|
|
 |
|
 |
DarkByte, an interface and a coclass are completely different concepts.
An interface describes public methods and a coclass idl keyword specifies a class and an interface that it implements.
Look at the .idl file:
coclass CXEnumVARIANT
{
[default] interface IEnumVARIANT;
}
It means that class CXEnumVARIANT supports (=implements) interface IEnumVARIANT.
If you want your component to be used by VB Script it should support IDispatch interface.
If you are serious about learning COM another book that I can recommend to read is "Active Template Library" by Tom Armstrong (in addition to the 2 books that I mentioned in the article). Do not be confused by its title, it covers COM fundamentals well and very easy to read and understand.
Good luck,
Igor.
|
|
|
|
 |
|
 |
Hi Igor,
Thanks for the help, i believe i'm not too bad with COM, its just 1 concept i'm not too sure ahout.
Coclasses, if i understand well, will allows someone to Create a new instance of a specific object type, unless coclass specifies "noncreatable" in its attributes. In your case, it would probably be possible to do in VB:
Dim e as new CXEnumVARIANT
Don't be too shabby on the syntax but let's suppose in my .idl file i have:
[....]
interface Domain: IDispatch
{
[proget ... ] HRESULT Name([in] BSTR *Name)
}
[object,uuid[..],dual,oleautomation...]
interface DomainEnumerator : IEnumVariant
{
};
interface Domains : IDispatch
{
[...] HRESULT Count(....);
[...] HRESULT get__NewEnum(..., DomainEnumerator **rvEnum);
}
library mylib
{
interface Domain;
interface Domains;
interface DomainEnumerator;
...
}
This would then make all those interfaces available for scripting (dispinterface) and would not allow me to instantiate directly any of these items.
My scenario involves only creating an "application" type of object from which any other object would be acquired, either directly (Domains) or indirectly (Domain access by DomainEnumerator of Domains). So for me, i seem to only need 1 coclass and the rest should only be interface. Am i making sense ?
BarkByte
|
|
|
|
 |
|
 |
Creating a new object is one thing but I'm talking about idl file.
Here is what MSDN says:
The coclass statement provides a listing of the supported interfaces for a component object.
[
coclass-attribute-list
]
coclass classname
{
[
interface-attributes
]
[interface | dispinterface] interfacename
{
. . .
}
}
Remarks:
The Microsoft® Component Object Model defines a class as an implementation that allows QueryInterface between a set of interfaces.
Example Code:
[
uuid(1e196b20-1f3c-1069-996b-00dd010fe676),
version(1.0),
helpstring("A class"),
helpcontext(2481), appobject
]
coclass myapp
{
[source] interface IMydocfuncs : IUnknown;
dispinterface DMydocfuncs;
};
[
uuid(12345678-1234-1234-1234-123456789ABC)
]
coclass mycoclass
{
[restricted] interface iface1;
interface iface2;
}
So in your idl file you have to have a coclass statement that tells COM what interface(s) your class supports (implements), otherwise QueryInterface() will not work.
Igor.
|
|
|
|
 |
|
 |
I think you are wrong about one thing, althought not 100% sure of what i'm about to say.
Having a coclass will allow to use CoCreateInstance to create an object of that type. And i believe that a library without a coclass makes no sense, but i dont believe that one requires a coclass for each interface type it supports.
Well, your explanations combined with my tests show that i can access my "app" object and have it return to me a Domain object (that i created as a private member of my coclass). I could then query my Domain.Name property without problem. Well, i'm not 100% sure i did things correctly regarding refcount etc .. but seeing the Domain.Name property display in VBA(Excel) Output window sure got me happy. So basicly, for any object i didnt define a coclass for but that supports IDispatch, if i create a class with the said interface, in C++, like when i call my coclass object's Login() function, internally i create a CDomain object which supports my Domain interface, then i can return that object's Domain interface pointer to any code querying about it (assuming the threading model is defined the right way).
Ok, it's probably confusing a little, but my point is, from what i understand, that one needs a coclass only if there is a requirement to use a function like CoCreateInstance() in C++ or Set obj = new CoClassObject in VBA or Set obj = CreateObject("mylib.mycoclass") in VBScript .. etc. Otherwise, if creating instances of an object is not required at all then one doesnt need to have a coclass, except for other possibilities which i dont know yet.
DarkByte
|
|
|
|
 |
|
 |
Probably, you are correct, because when you call CoCreateInstance() or CreateObject() internally it calls QueryInterface() and for these calls to succeed you need to define a coclass in the idl file.
Igor.
|
|
|
|
 |
|
 |
when i test your dll.It shows a memory leakage. [W] MLK: Memory leak of 72 bytes from 1 block allocated in ATL::CComObject<CXItem>::CreateInstance(CComObject<CXItem>::ATL * *) [ShoppingCart.dll]
why it showing like this.
|
|
|
|
 |
|
 |
My fault - I did not release com objects properly. Take a look at a previous post, it explains it.
Thanks a lot for your testing.
With regards,
Igor.
|
|
|
|
 |
|
 |
If you put a destructor in CXItem, eg. ~CXItem() { ATLTRACE("In ~CXItem\n") } You find that it isn't called, When the CXCollection list is destroyed only a bunch of pointers are removed. So the com objects are still present. I created a smart(ish) pointer and made the list of items hold them instead. This is put in near the bottom of XItem.h I then commented out all Item.AddRef(), and Item.Release()in the other files, as the smart pointers handle that. Ideally the pointers used should be CAdapt< CComPtr<CXItem> > objects which Microsoft have made for the purpose, however I found difficulties in getting these to work. Extract from XItem.h // ISupportsErrorInfo STDMETHOD(InterfaceSupportsErrorInfo)(REFIID riid); // IXItem public: }; // --- NEW STuff here... // Class to handle pointers to com objects, and make them smarter... // Increment and decrement the reference counts, as the item gets used. template<typename T> class ItemPtr { T * m_pItem; public: ~ItemPtr() { if(m_pItem) m_pItem->Release(); m_pItem = NULL; } ItemPtr() : m_pItem(NULL) {} ItemPtr(T *pItem) : m_pItem(pItem) { if(m_pItem) m_pItem ->AddRef();} ItemPtr(const ItemPtr & y2) : m_pItem(y2.m_pItem) { if(m_pItem) m_pItem->AddRef(); } ItemPtr& operator =(T * pItem) { if(m_pItem) m_pItem->Release(); m_pItem=pItem; if(m_pItem) m_pItem->AddRef(); } operator T *(){ return m_pItem; } T * operator ->(){ return m_pItem; } // Attach to a T * without increasing reference count. void Attach(T *pItem){ if(m_pItem) m_pItem->Release(); m_pItem=pItem;} // Detatch from a T * without decreasing reference count. void Detach(T *pItem){ m_pItem=NULL;} friend int operator ==(const ItemPtr & y1, const ItemPtr &y2); }; template<typename T> int operator ==(const ItemPtr<T> &y1, const ItemPtr<T> &y2) { return y1.m_pItem == y2.m_pItem; }
typedef list<ItemPtr<CXItem> > ItemList; // linked list of CXItem*, with smart pointer. //typedef list < CAdapt<CComPtr< CXItem> > > ItemList;
Regards Roger
|
|
|
|
 |
|
 |
I guess I did not spent enough time testing this code. You are right, the destructor does not get called.
But I am curious to know why CAdapt does not work. BTW, it could make a useful article.
Thanks for your remarks,
Igor.
|
|
|
|
 |
|
 |
Ok, it finds the type library now, but now its giving me another error... no code changes aside from the fix for the type library problem.
XCollection.cpp(19) : error C2039: 'InlineIsEqualGUID' : is not a member of '`global namespace''
XEnumVariant.cpp
I've been looking in newsgroups and I found some articles dealing with ambiguous overloaded function errors, but it seems that this is not the same problem.
|
|
|
|
 |
|
 |
Nevermind, I figured it out... I had to add ::ATL:: to the calls and then it worked.
Thanks for the help!
|
|
|
|
 |
|
 |
I downloaded this code and when I try to build it, I get the following error (i did not change any code, I just unzipped the file and tried to build it):
C:\MyProjects\AllProjects\examples\ShoppingCart\ShoppingCart.rc (127): error RC2135 : file not found: .\Debug\ShoppingCart.tlb
Error executing rc.exe.
|
|
|
|
 |
|
 |
In the ShoppingCart.rc file find the following line
1 TYPELIB ".\Debug\ShoppingCart.tlb"
and change it to
1 TYPELIB ".\ShoppingCart.tlb"
|
|
|
|
 |
|
 |
... and it provides much better collection classes than the VB Collection.
I vote pro drink
|
|
|
|
 |