Implementowanie kolekcji opartej na standardowej bibliotece C++
AtL udostępnia ICollectionOnSTLImpl
interfejs umożliwiający szybkie implementowanie standardowych interfejsów kolekcji opartych na bibliotece C++ na obiektach. Aby zrozumieć, jak działa ta klasa, użyjesz prostego przykładu (poniżej), który używa tej klasy do implementowania kolekcji tylko do odczytu skierowanej do klientów usługi Automation.
Przykładowy kod pochodzi z przykładu ATLCollections.
Aby wykonać tę procedurę, wykonaj następujące czynności:
Wygeneruj nowy obiekt prosty.
Edytuj plik IDL dla wygenerowanego interfejsu.
Utwórz pięć definicji typów opisujących sposób przechowywania elementów kolekcji i sposób ich uwidaczniania klientom za pośrednictwem interfejsów COM.
Utwórz dwie definicje typów dla klas zasad kopiowania.
Utwórz definicje typów dla implementacji modułu wyliczającego i kolekcji.
Edytuj wygenerowany przez kreatora kod języka C++, aby użyć definicji typu kolekcji.
Generowanie nowego prostego obiektu
Utwórz nowy projekt, upewniając się, że pole Atrybuty w obszarze Ustawienia aplikacji zostało wyczyszczone. Użyj okna dialogowego Dodawanie klasy ATL i Kreatora dodawania prostego obiektu, aby wygenerować prosty obiekt o nazwie Words
. Upewnij się, że jest generowany podwójny interfejs o nazwie IWords
. Obiekty wygenerowanej klasy będą używane do reprezentowania kolekcji wyrazów (czyli ciągów).
Edytowanie pliku IDL
Teraz otwórz plik IDL i dodaj trzy właściwości niezbędne do przekształcenia IWords
w interfejs kolekcji tylko do odczytu, jak pokazano poniżej:
[
object,
uuid(7B3AC376-509F-4068-87BA-03B73ADC359B),
dual, // (1)
nonextensible, // (2)
pointer_default(unique)
]
interface IWords : IDispatch
{
[id(DISPID_NEWENUM), propget] // (3)
HRESULT _NewEnum([out, retval] IUnknown** ppUnk);
[id(DISPID_VALUE), propget] // (4)
HRESULT Item([in] long Index, [out, retval] BSTR* pVal); // (5)
[id(0x00000001), propget] // (6)
HRESULT Count([out, retval] long* pVal);
};
Jest to standardowy formularz interfejsu kolekcji tylko do odczytu zaprojektowanego z myślą o klientach usługi Automation. Ponumerowane komentarze w tej definicji interfejsu odpowiadają poniższym komentarzom:
Interfejsy kolekcji są zwykle podwójne, ponieważ klienci usługi Automation uzyskują
_NewEnum
dostęp do właściwości za pośrednictwem metodyIDispatch::Invoke
. Jednak klienci usługi Automation mogą uzyskiwać dostęp do pozostałych metod za pośrednictwem tabeli vtable, dlatego preferowane są dwa interfejsy.Jeśli podwójny interfejs lub dispinterface nie zostaną rozszerzone w czasie wykonywania (tj. nie udostępnisz dodatkowych metod lub właściwości za pośrednictwem
IDispatch::Invoke
metody ), należy zastosować atrybut niespodziewalny do definicji. Ten atrybut umożliwia klientom usługi Automation wykonywanie pełnej weryfikacji kodu w czasie kompilacji. W takim przypadku interfejs nie powinien być rozszerzony.Prawidłowy identyfikator DISPID jest ważny, jeśli chcesz, aby klienci usługi Automation mogli używać tej właściwości. (Należy pamiętać, że w DISPID_NEWENUM istnieje tylko jeden podkreślenie).
Możesz podać dowolną wartość jako DISPID
Item
właściwości .Item
Jednak zazwyczaj używa DISPID_VALUE, aby ustawić ją jako domyślną właściwość kolekcji. Dzięki temu klienci usługi Automation mogą odwoływać się do właściwości bez jawnego nazewnictwa.Typ danych używany dla wartości
Item
zwracanej właściwości jest typem elementu przechowywanego w kolekcji, jeśli chodzi o klientów COM. Interfejs zwraca ciągi, więc należy użyć standardowego typu ciągu COM, BSTR. Dane można przechowywać w innym formacie wewnętrznie, ponieważ wkrótce zobaczysz.Wartość używana dla identyfikatora DISPID
Count
właściwości jest całkowicie dowolna. Dla tej właściwości nie ma standardowego identyfikatora DISPID.
Tworzenie definicji typów dla magazynu i ekspozycji
Po zdefiniowaniu interfejsu kolekcji należy zdecydować, jak będą przechowywane dane i jak dane będą widoczne za pośrednictwem modułu wyliczającego.
Odpowiedzi na te pytania można podać w postaci wielu definicji typów, które można dodać w górnej części pliku nagłówka dla nowo utworzonej klasy:
// Store the data in a vector of std::strings
typedef std::vector< std::string > ContainerType;
// The collection interface exposes the data as BSTRs
typedef BSTR CollectionExposedType;
typedef IWords CollectionInterface;
// Use IEnumVARIANT as the enumerator for VB compatibility
typedef VARIANT EnumeratorExposedType;
typedef IEnumVARIANT EnumeratorInterface;
W takim przypadku dane będą przechowywane jako std::vector std ::strings. std::vector to klasa kontenera biblioteki standardowej języka C++, która zachowuje się jak tablica zarządzana. std::string jest klasą ciągów biblioteki standardowej języka C++. Te klasy ułatwiają pracę z kolekcją ciągów.
Ponieważ obsługa języka Visual Basic jest niezbędna do sukcesu tego interfejsu, moduł wyliczający zwrócony przez _NewEnum
właściwość musi obsługiwać IEnumVARIANT
interfejs. Jest to jedyny interfejs wyliczający zrozumiały dla języka Visual Basic.
Tworzenie definicji typów dla klas zasad kopiowania
Utworzone definicje typów do tej pory zawierają wszystkie informacje potrzebne do utworzenia dalszych definicji typów dla klas kopiowania, które będą używane przez moduł wyliczający i kolekcję:
// Typedef the copy classes using existing typedefs
typedef VCUE::GenericCopy<EnumeratorExposedType, ContainerType::value_type> EnumeratorCopyType;
typedef VCUE::GenericCopy<CollectionExposedType, ContainerType::value_type> CollectionCopyType;
W tym przykładzie można użyć niestandardowej klasy zdefiniowanej GenericCopy
w pliku VCUE_Copy.h i VCUE_CopyString.h z przykładu ATLCollections . Możesz użyć tej klasy w innym kodzie, ale może być konieczne zdefiniowanie dalszych specjalizacji GenericCopy
w celu obsługi typów danych używanych we własnych kolekcjach. Aby uzyskać więcej informacji, zobacz ATL Copy Policy Classes (Klasy zasad kopiowania ATL).
Tworzenie definicji typów dla wyliczenia i kolekcji
Teraz wszystkie parametry szablonu niezbędne do specjalizacji CComEnumOnSTL
klas i ICollectionOnSTLImpl
dla tej sytuacji zostały podane w postaci definicji typów. Aby uprościć korzystanie z specjalizacji, utwórz jeszcze dwie definicje typów, jak pokazano poniżej:
typedef CComEnumOnSTL< EnumeratorInterface, &__uuidof(EnumeratorInterface), EnumeratorExposedType, EnumeratorCopyType, ContainerType > EnumeratorType;
typedef ICollectionOnSTLImpl< CollectionInterface, ContainerType, CollectionExposedType, CollectionCopyType, EnumeratorType > CollectionType;
Teraz CollectionType
jest synonimem specjalizacji ICollectionOnSTLImpl
, która implementuje IWords
interfejs zdefiniowany wcześniej i udostępnia moduł wyliczający, który obsługuje IEnumVARIANT
.
Edytowanie kodu wygenerowanego przez kreatora
Teraz musisz pochodzić CWords
z implementacji interfejsu reprezentowanej przez definicję CollectionType
typu, a nie IWords
, jak pokazano poniżej:
class ATL_NO_VTABLE CWords :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CWords, &CLSID_Words>,
// 'CollectionType' replaces 'IWords' in next line
public IDispatchImpl<CollectionType, &IID_IWords, &LIBID_NVC_ATL_COMLib, /*wMajor =*/ 1, /*wMinor =*/ 0>
{
public:
DECLARE_REGISTRY_RESOURCEID(IDR_WORDS)
BEGIN_COM_MAP(CWords)
COM_INTERFACE_ENTRY(IWords)
COM_INTERFACE_ENTRY(IDispatch)
END_COM_MAP()
// Remainder of class declaration omitted.
Dodawanie kodu w celu wypełnienia kolekcji
Jedyną rzeczą, która pozostaje, jest wypełnienie wektora danymi. W tym prostym przykładzie można dodać kilka słów do kolekcji w konstruktorze dla klasy:
CWords()
{
m_coll.push_back("this");
m_coll.push_back("is");
m_coll.push_back("a");
m_coll.push_back("test");
}
Teraz możesz przetestować kod przy użyciu wybranego klienta.
Zobacz też
Kolekcje i moduły wyliczania
Przykład ATLCollections
Klasy zasad kopii ATL