Bagikan melalui


TN038: Implementasi MFC/OLE IUnknown

Catatan

Catatan teknis berikut belum diperbarui sejak pertama kali disertakan dalam dokumentasi online. Akibatnya, beberapa prosedur dan topik mungkin kedaluarsa atau salah. Untuk informasi terbaru, disarankan agar Anda mencari topik yang menarik dalam indeks dokumentasi online.

Inti dari OLE 2 adalah "Model Objek Komponen OLE", atau COM. COM mendefinisikan standar tentang bagaimana objek yang bekerja sama berkomunikasi satu sama lain. Ini termasuk detail tampilan "objek", termasuk bagaimana metode dikirim pada objek. COM juga mendefinisikan kelas dasar, dari mana semua kelas yang kompatibel com berasal. Kelas dasar ini adalah IUnknown. Meskipun antarmuka IUnknown disebut sebagai kelas C++, COM tidak spesifik untuk satu bahasa — dapat diimplementasikan dalam C, PASCAL, atau bahasa lain yang dapat mendukung tata letak biner objek COM.

OLE mengacu pada semua kelas yang berasal dari IUnknown sebagai "antarmuka." Ini adalah perbedaan penting, karena "antarmuka" seperti IUnknown tidak membawa implementasi. Ini hanya mendefinisikan protokol tempat objek berkomunikasi, bukan spesifik dari apa yang dilakukan implementasi tersebut. Ini wajar untuk sistem yang memungkinkan fleksibilitas maksimum. Ini adalah tugas MFC untuk menerapkan perilaku default untuk program MFC/C++.

Untuk memahami implementasi MFC dari IUnknown , Anda harus terlebih dahulu memahami apa antarmuka ini. Versi IUnknown yang disederhanakan didefinisikan di bawah ini:

class IUnknown
{
public:
    virtual HRESULT QueryInterface(REFIID iid, void** ppvObj) = 0;
    virtual ULONG AddRef() = 0;
    virtual ULONG Release() = 0;
};

Catatan

Detail konvensi panggilan tertentu yang diperlukan, seperti __stdcall dibiarkan untuk ilustrasi ini.

Fungsi anggota AddRef dan Release mengontrol manajemen memori objek. COM menggunakan skema penghitungan referensi untuk melacak objek. Objek tidak pernah dirujuk langsung seperti yang Anda lakukan di C++. Sebaliknya, objek COM selalu dirujuk melalui penunjuk. Untuk melepaskan objek ketika pemilik selesai menggunakannya, anggota Rilis objek dipanggil (dibandingkan dengan menggunakan penghapusan operator, seperti yang akan dilakukan untuk objek C++ tradisional). Mekanisme penghitungan referensi memungkinkan beberapa referensi ke satu objek untuk dikelola. Implementasi AddRef dan Release mempertahankan jumlah referensi pada objek — objek tidak dihapus sampai jumlah referensinya mencapai nol.

AddRef dan Release cukup mudah dari sudut siaga implementasi. Berikut adalah implementasi sepele:

ULONG CMyObj::AddRef()
{
    return ++m_dwRef;
}

ULONG CMyObj::Release()
{
    if (--m_dwRef == 0)
    {
        delete this;
        return 0;
    }
    return m_dwRef;
}

Fungsi anggota QueryInterface sedikit lebih menarik. Tidak terlalu menarik untuk memiliki objek yang hanya fungsi anggotanya adalah AddRef dan Release - akan lebih baik untuk memberi tahu objek untuk melakukan lebih banyak hal daripada yang disediakan IUnknown. Di sinilah QueryInterface berguna. Ini memungkinkan Anda untuk mendapatkan "antarmuka" yang berbeda pada objek yang sama. Antarmuka ini biasanya berasal dari IUnknown dan menambahkan fungsionalitas tambahan dengan menambahkan fungsi anggota baru. Antarmuka COM tidak pernah memiliki variabel anggota yang dideklarasikan dalam antarmuka, dan semua fungsi anggota dinyatakan sebagai murni-virtual. Contohnya,

class IPrintInterface : public IUnknown
{
public:
    virtual void PrintObject() = 0;
};

Untuk mendapatkan IPrintInterface jika Anda hanya memiliki IUnknown, panggil QueryInterface menggunakan IID dari IPrintInterface. Adalah IID angka 128-bit yang secara unik mengidentifikasi antarmuka. Ada IID untuk setiap antarmuka yang Anda atau OLE tentukan. Jika pUnk adalah penunjuk ke objek IUnknown , kode untuk mengambil IPrintInterface darinya mungkin:

IPrintInterface* pPrint = NULL;
if (pUnk->QueryInterface(IID_IPrintInterface, (void**)&pPrint) == NOERROR)
{
    pPrint->PrintObject();
    pPrint->Release();
    // release pointer obtained via QueryInterface
}

Itu tampaknya cukup mudah, tetapi bagaimana Anda akan menerapkan objek yang mendukung antarmuka IPrintInterface dan IUnknown Dalam hal ini sederhana karena IPrintInterface berasal langsung dari IUnknown - dengan menerapkan IPrintInterface, IUnknown secara otomatis didukung. Contohnya:

class CPrintObj : public CPrintInterface
{
    virtual HRESULT QueryInterface(REFIID iid, void** ppvObj);
    virtual ULONG AddRef();
    virtual ULONG Release();
    virtual void PrintObject();
};

Implementasi AddRef dan Release akan sama persis dengan yang diimplementasikan di atas. CPrintObj::QueryInterface akan terlihat seperti ini:

HRESULT CPrintObj::QueryInterface(REFIID iid, void FAR* FAR* ppvObj)
{
    if (iid == IID_IUnknown || iid == IID_IPrintInterface)
    {
        *ppvObj = this;
        AddRef();
        return NOERROR;
    }
    return E_NOINTERFACE;
}

Seperti yang Anda lihat, jika pengidentifikasi antarmuka (IID) dikenali, pointer dikembalikan ke objek Anda; jika tidak, kesalahan terjadi. Perhatikan juga bahwa QueryInterface yang berhasil menghasilkan AddRef tersirat. Tentu saja, Anda juga harus menerapkan CEditObj::P rint. Itu sederhana karena IPrintInterface langsung berasal dari antarmuka IUnknown . Namun, jika Anda ingin mendukung dua antarmuka yang berbeda, keduanya berasal dari IUnknown, pertimbangkan hal berikut:

class IEditInterface : public IUnkown
{
public:
    virtual void EditObject() = 0;
};

Meskipun ada sejumlah cara berbeda untuk mengimplementasikan kelas yang mendukung IEditInterface dan IPrintInterface, termasuk menggunakan beberapa warisan C++, catatan ini akan berkonsentrasi pada penggunaan kelas berlapis untuk mengimplementasikan fungsionalitas ini.

class CEditPrintObj
{
public:
    CEditPrintObj();

    HRESULT QueryInterface(REFIID iid, void**);
    ULONG AddRef();
    ULONG Release();
    DWORD m_dwRef;

    class CPrintObj : public IPrintInterface
    {
    public:
        CEditPrintObj* m_pParent;
        virtual HRESULT QueryInterface(REFIID iid, void** ppvObj);
        virtual ULONG AddRef();
        virtual ULONG Release();
    } m_printObj;

    class CEditObj : public IEditInterface
    {
    public:
        CEditPrintObj* m_pParent;
        virtual ULONG QueryInterface(REFIID iid, void** ppvObj);
        virtual ULONG AddRef();
        virtual ULONG Release();
    } m_editObj;
};

Seluruh implementasi disertakan di bawah ini:

CEditPrintObj::CEditPrintObj()
{
    m_editObj.m_pParent = this;
    m_printObj.m_pParent = this;
}

ULONG CEditPrintObj::AddRef()
{
    return ++m_dwRef;
}

CEditPrintObj::Release()
{
    if (--m_dwRef == 0)
    {
        delete this;
        return 0;
    }
    return m_dwRef;
}

HRESULT CEditPrintObj::QueryInterface(REFIID iid, void** ppvObj)
{
    if (iid == IID_IUnknown || iid == IID_IPrintInterface)
    {
        *ppvObj = &m_printObj;
        AddRef();
        return NOERROR;
    }
    else if (iid == IID_IEditInterface)
    {
        *ppvObj = &m_editObj;
        AddRef();
        return NOERROR;
    }
    return E_NOINTERFACE;
}

ULONG CEditPrintObj::CEditObj::AddRef()
{
    return m_pParent->AddRef();
}

ULONG CEditPrintObj::CEditObj::Release()
{
    return m_pParent->Release();
}

HRESULT CEditPrintObj::CEditObj::QueryInterface(REFIID iid, void** ppvObj)
{
    return m_pParent->QueryInterface(iid, ppvObj);
}

ULONG CEditPrintObj::CPrintObj::AddRef()
{
    return m_pParent->AddRef();
}

ULONG CEditPrintObj::CPrintObj::Release()
{
    return m_pParent->Release();
}

HRESULT CEditPrintObj::CPrintObj::QueryInterface(REFIID iid, void** ppvObj)
{
    return m_pParent->QueryInterface(iid, ppvObj);
}

Perhatikan bahwa sebagian besar implementasi IUnknown ditempatkan ke dalam kelas CEditPrintObj daripada menduplikasi kode di CEditPrintObj::CEditObj dan CEditPrintObj::CPrintObj. Ini mengurangi jumlah kode dan menghindari bug. Poin utama di sini adalah bahwa dari antarmuka IUnknown dimungkinkan untuk memanggil QueryInterface untuk mengambil antarmuka apa pun yang mungkin didukung objek, dan dari masing-masing antarmuka tersebut dimungkinkan untuk melakukan hal yang sama. Ini berarti bahwa semua fungsi QueryInterface yang tersedia dari setiap antarmuka harus berulah dengan cara yang sama persis. Agar objek yang disematkan ini memanggil implementasi di "objek luar", back-pointer digunakan (m_pParent). Penunjuk m_pParent diinisialisasi selama konstruktor CEditPrintObj. Kemudian Anda akan mengimplementasikan CEditPrintObj::CPrintObj::P rintObject dan CEditPrintObj::CEditObj::EditObject juga. Cukup sedikit kode ditambahkan untuk menambahkan satu fitur — kemampuan untuk mengedit objek. Untungnya, cukup jarang antarmuka hanya memiliki satu fungsi anggota (meskipun itu terjadi) dan dalam hal ini, EditObject dan PrintObject biasanya akan digabungkan menjadi satu antarmuka.

Itu banyak penjelasan dan banyak kode untuk skenario sederhana seperti itu. Kelas MFC/OLE menyediakan alternatif yang lebih sederhana. Implementasi MFC menggunakan teknik yang mirip dengan cara pesan Windows dibungkus dengan Peta Pesan. Fasilitas ini disebut Antarmuka Peta dan dibahas di bagian berikutnya.

Peta Antarmuka MFC

MFC/OLE mencakup implementasi "Antarmuka Peta" yang mirip dengan "Peta Pesan" MFC dan "Peta Pengiriman" dalam konsep dan eksekusi. Fitur inti Peta Antarmuka MFC adalah sebagai berikut:

Selain itu, peta antarmuka mendukung fitur lanjutan berikut:

  • Dukungan untuk membuat objek COM yang dapat diagregasi

  • Dukungan untuk menggunakan objek agregat dalam implementasi objek COM

  • Implementasinya dapat dikaitkan dan dapat diperluas

Untuk informasi selengkapnya tentang agregasi, lihat topik Agregasi .

Dukungan peta antarmuka MFC berakar di CCmdTarget kelas . CCmdTarget Jumlah referensi "has-a" serta semua fungsi anggota yang terkait dengan implementasi IUnknown (jumlah referensi misalnya ada di CCmdTarget). Untuk membuat kelas yang mendukung OLE COM, Anda mendapatkan kelas dari CCmdTarget dan menggunakan berbagai makro serta fungsi CCmdTarget anggota untuk mengimplementasikan antarmuka yang diinginkan. Implementasi MFC menggunakan kelas berlapis untuk menentukan setiap implementasi antarmuka seperti contoh di atas. Ini dipermudah dengan implementasi standar IUnknown serta sejumlah makro yang menghilangkan beberapa kode berulang.

Dasar-Dasar Peta Antarmuka

Untuk mengimplementasikan kelas menggunakan peta antarmuka MFC

  1. Mendapatkan kelas baik secara langsung maupun tidak langsung dari CCmdTarget.

  2. DECLARE_INTERFACE_MAP Gunakan fungsi dalam definisi kelas turunan.

  3. Untuk setiap antarmuka yang ingin Anda dukung, gunakan makro BEGIN_INTERFACE_PART dan END_INTERFACE_PART dalam definisi kelas.

  4. Dalam file implementasi, gunakan makro BEGIN_INTERFACE_MAP dan END_INTERFACE_MAP untuk menentukan peta antarmuka kelas.

  5. Untuk setiap IID yang didukung, gunakan makro INTERFACE_PART antara makro BEGIN_INTERFACE_MAP dan END_INTERFACE_MAP untuk memetakan IID tersebut ke "bagian" tertentu dari kelas Anda.

  6. Terapkan setiap kelas berlapis yang mewakili antarmuka yang Anda dukung.

  7. Gunakan makro METHOD_PROLOGUE untuk mengakses induk, CCmdTargetobjek turunan.

  8. AddRef, Release, dan QueryInterface dapat mendelegasikan ke CCmdTarget implementasi fungsi-fungsi ini (ExternalAddRef, ExternalRelease, dan ExternalQueryInterface).

Contoh CPrintEditObj di atas dapat diimplementasikan sebagai berikut:

class CPrintEditObj : public CCmdTarget
{
public:
    // member data and member functions for CPrintEditObj go here

// Interface Maps
protected:
    DECLARE_INTERFACE_MAP()

    BEGIN_INTERFACE_PART(EditObj, IEditInterface)
        STDMETHOD_(void, EditObject)();
    END_INTERFACE_PART(EditObj)

    BEGIN_INTERFACE_PART(PrintObj, IPrintInterface)
        STDMETHOD_(void, PrintObject)();
    END_INTERFACE_PART(PrintObj)
};

Deklarasi di atas membuat kelas yang berasal dari CCmdTarget. Makro DECLARE_INTERFACE_MAP memberi tahu kerangka kerja bahwa kelas ini akan memiliki peta antarmuka kustom. Selain itu, makro BEGIN_INTERFACE_PART dan END_INTERFACE_PART mendefinisikan kelas berlapis, dalam hal ini dengan nama CEditObj dan CPrintObj (X hanya digunakan untuk membedakan kelas berlapis dari kelas global yang dimulai dengan "C" dan kelas antarmuka yang dimulai dengan "I"). Dua anggota berlapis dari kelas ini dibuat: masing-masing m_CEditObj, dan m_CPrintObj. Makro secara otomatis mendeklarasikan fungsi AddRef, Release, dan QueryInterface ; oleh karena itu Anda hanya mendeklarasikan fungsi khusus untuk antarmuka ini: EditObject dan PrintObject (makro OLE STDMETHOD digunakan sehingga _stdcall dan kata kunci virtual disediakan sesuai untuk platform target).

Untuk mengimplementasikan peta antarmuka untuk kelas ini:

BEGIN_INTERFACE_MAP(CPrintEditObj, CCmdTarget)
    INTERFACE_PART(CPrintEditObj, IID_IPrintInterface, PrintObj)
    INTERFACE_PART(CPrintEditObj, IID_IEditInterface, EditObj)
END_INTERFACE_MAP()

Ini menghubungkan IID IID_IPrintInterface dengan m_CPrintObj dan IID_IEditInterface dengan m_CEditObj masing-masing. Implementasi CCmdTarget QueryInterface (CCmdTarget::ExternalQueryInterface) menggunakan peta ini untuk mengembalikan pointer ke m_CPrintObj dan m_CEditObj saat diminta. Tidak perlu menyertakan entri untuk IID_IUnknown; kerangka kerja akan menggunakan antarmuka pertama di peta (dalam hal ini, m_CPrintObj) ketika IID_IUnknown diminta.

Meskipun makro BEGIN_INTERFACE_PART secara otomatis mendeklarasikan fungsi AddRef, Release, dan QueryInterface untuk Anda, Anda masih perlu mengimplementasikannya:

ULONG FAR EXPORT CEditPrintObj::XEditObj::AddRef()
{
    METHOD_PROLOGUE(CEditPrintObj, EditObj)
    return pThis->ExternalAddRef();
}

ULONG FAR EXPORT CEditPrintObj::XEditObj::Release()
{
    METHOD_PROLOGUE(CEditPrintObj, EditObj)
    return pThis->ExternalRelease();
}

HRESULT FAR EXPORT CEditPrintObj::XEditObj::QueryInterface(
    REFIID iid,
    void FAR* FAR* ppvObj)
{
    METHOD_PROLOGUE(CEditPrintObj, EditObj)
    return (HRESULT)pThis->ExternalQueryInterface(&iid, ppvObj);
}

void FAR EXPORT CEditPrintObj::XEditObj::EditObject()
{
    METHOD_PROLOGUE(CEditPrintObj, EditObj)
    // code to "Edit" the object, whatever that means...
}

Implementasi untuk CEditPrintObj::CPrintObj, akan mirip dengan definisi di atas untuk CEditPrintObj::CEditObj. Meskipun akan mungkin untuk membuat makro yang dapat digunakan untuk secara otomatis menghasilkan fungsi-fungsi ini (tetapi sebelumnya dalam pengembangan MFC/OLE ini terjadi), menjadi sulit untuk mengatur titik henti ketika makro menghasilkan lebih dari satu baris kode. Untuk alasan ini, kode ini diperluas secara manual.

Dengan menggunakan implementasi kerangka kerja peta pesan, ada sejumlah hal yang tidak perlu dilakukan:

  • Menerapkan QueryInterface

  • Menerapkan AddRef dan Rilis

  • Deklarasikan salah satu metode bawaan ini pada kedua antarmuka Anda

Selain itu, kerangka kerja menggunakan peta pesan secara internal. Ini memungkinkan Anda untuk memperoleh dari kelas kerangka kerja, katakanlah COleServerDoc, yang sudah mendukung antarmuka tertentu dan menyediakan penggantian atau penambahan ke antarmuka yang disediakan oleh kerangka kerja. Anda dapat melakukan ini karena kerangka kerja sepenuhnya mendukung pewarisan peta antarmuka dari kelas dasar. Itulah alasan mengapa BEGIN_INTERFACE_MAP mengambil sebagai parameter kedua nama kelas dasar.

Catatan

Umumnya tidak mungkin untuk menggunakan kembali implementasi implementasi bawaan MFC dari antarmuka OLE hanya dengan mewarisi spesialisasi yang disematkan dari antarmuka tersebut dari versi MFC. Ini tidak dimungkinkan karena penggunaan makro METHOD_PROLOGUE untuk mendapatkan akses ke objek yang berisi CCmdTarget-turunan menyiratkan offset tetap objek yang disematkan dari CCmdTargetobjek -turunan. Ini berarti, misalnya, Anda tidak dapat memperoleh XMyAdviseSink yang disematkan dari implementasi MFC di COleClientItem::XAdviseSink, karena XAdviseSink bergantung pada offset tertentu dari bagian COleClientItem atas objek.

Catatan

Namun, Anda dapat mendelegasikan ke implementasi MFC untuk semua fungsi yang Anda inginkan untuk perilaku default MFC. Ini dilakukan dalam implementasi IOleInPlaceFrame MFC (XOleInPlaceFrame) di COleFrameHook kelas (mendelegasikan ke m_xOleInPlaceUIWindow untuk banyak fungsi). Desain ini dipilih untuk mengurangi ukuran runtime objek yang mengimplementasikan banyak antarmuka; ini menghilangkan kebutuhan akan back-pointer (seperti cara m_pParent digunakan di bagian sebelumnya).

Peta Agregasi dan Antarmuka

Selain mendukung objek COM yang berdiri sendiri, MFC juga mendukung agregasi. Agregasi itu sendiri terlalu kompleks topik untuk dibahas di sini; lihat topik Agregasi untuk informasi selengkapnya tentang agregasi. Catatan ini hanya akan menjelaskan dukungan untuk agregasi yang dibangun ke dalam kerangka kerja dan peta antarmuka.

Ada dua cara untuk menggunakan agregasi: (1) menggunakan objek COM yang mendukung agregasi, dan (2) menerapkan objek yang dapat dikumpulkan oleh objek lain. Kemampuan ini dapat disebut sebagai "menggunakan objek agregat" dan "membuat objek dapat diagregasi". MFC mendukung keduanya.

Menggunakan Objek Agregat

Untuk menggunakan objek agregat, perlu ada beberapa cara untuk mengikat agregat ke dalam mekanisme QueryInterface. Dengan kata lain, objek agregat harus berperilaku seolah-olah itu adalah bagian asli dari objek Anda. Jadi bagaimana ikatan ini ke mekanisme peta antarmuka MFC Selain makro INTERFACE_PART, di mana objek berlapis dipetakan ke IID, Anda juga dapat mendeklarasikan objek agregat sebagai bagian dari kelas turunan Anda CCmdTarget . Untuk melakukannya, makro INTERFACE_AGGREGATE digunakan. Ini memungkinkan Anda menentukan variabel anggota (yang harus menjadi penunjuk ke kelas IUnknown atau turunan), yang akan diintegrasikan ke dalam mekanisme peta antarmuka. Jika penunjuk tidak NULL ketika CCmdTarget::ExternalQueryInterface dipanggil, kerangka kerja akan secara otomatis memanggil fungsi anggota QueryInterface objek agregat, jika IID yang diminta bukan salah satu asli IIDyang didukung oleh objek itu CCmdTarget sendiri.

Untuk menggunakan makro INTERFACE_AGGREGATE

  1. Deklarasikan variabel anggota (an IUnknown*) yang akan berisi pointer ke objek agregat.

  2. Sertakan makro INTERFACE_AGGREGATE di peta antarmuka Anda, yang mengacu pada variabel anggota berdasarkan nama.

  3. Pada titik tertentu (biasanya selama CCmdTarget::OnCreateAggregates), inisialisasi variabel anggota ke sesuatu selain NULL.

Contohnya:

class CAggrExample : public CCmdTarget
{
public:
    CAggrExample();

protected:
    LPUNKNOWN m_lpAggrInner;
    virtual BOOL OnCreateAggregates();

    DECLARE_INTERFACE_MAP()
    // "native" interface part macros may be used here
};

CAggrExample::CAggrExample()
{
    m_lpAggrInner = NULL;
}

BOOL CAggrExample::OnCreateAggregates()
{
    // wire up aggregate with correct controlling unknown
    m_lpAggrInner = CoCreateInstance(CLSID_Example,
        GetControllingUnknown(), CLSCTX_INPROC_SERVER,
        IID_IUnknown, (LPVOID*)&m_lpAggrInner);

    if (m_lpAggrInner == NULL)
        return FALSE;
    // optionally, create other aggregate objects here
    return TRUE;
}

BEGIN_INTERFACE_MAP(CAggrExample, CCmdTarget)
    // native "INTERFACE_PART" entries go here
    INTERFACE_AGGREGATE(CAggrExample, m_lpAggrInner)
END_INTERFACE_MAP()

Variabel m_lpAggrInner diinisialisasi dalam konstruktor ke NULL. Kerangka kerja mengabaikan variabel anggota NULL dalam implementasi default QueryInterface. OnCreateAggregates adalah tempat yang baik untuk benar-benar membuat objek agregat Anda. Anda harus menyebutnya secara eksplisit jika Anda membuat objek di luar implementasi MFC .COleObjectFactory Alasan untuk membuat agregat serta CCmdTarget::OnCreateAggregates penggunaan CCmdTarget::GetControllingUnknown akan menjadi jelas saat membuat objek yang dapat diagregasi dibahas.

Teknik ini akan memberi objek Anda semua antarmuka yang didukung objek agregat ditambah antarmuka aslinya. Jika Anda hanya menginginkan subset antarmuka yang didukung agregat, Anda dapat mengambil alih CCmdTarget::GetInterfaceHook. Ini memungkinkan Anda memiliki kemampuan kait tingkat rendah, mirip dengan QueryInterface. Biasanya, Anda ingin semua antarmuka yang didukung agregat.

Membuat Agregat Implementasi Objek

Agar objek dapat diagregasi, implementasi AddRef, Release, dan QueryInterface harus mendelegasikan ke "mengontrol tidak diketahui." Dengan kata lain, agar menjadi bagian dari objek, ia harus mendelegasikan AddRef, Release, dan QueryInterface ke objek yang berbeda, juga berasal dari IUnknown. "Pengontrol tidak diketahui" ini disediakan untuk objek ketika dibuat, yaitu, disediakan untuk implementasi COleObjectFactory. Menerapkan ini membawa sejumlah kecil overhead, dan dalam beberapa kasus tidak diinginkan, sehingga MFC membuat ini opsional. Untuk mengaktifkan objek agar dapat diagregasi, Anda memanggil CCmdTarget::EnableAggregation dari konstruktor objek.

Jika objek juga menggunakan agregat, Anda juga harus memastikan untuk meneruskan "pengontrol yang tidak diketahui" yang benar ke objek agregat. Biasanya pointer IUnknown ini diteruskan ke objek saat agregat dibuat. Misalnya, parameter pUnkOuter adalah "mengontrol tidak diketahui" untuk objek yang dibuat dengan CoCreateInstance. Penunjuk "mengontrol tidak diketahui" yang benar dapat diambil dengan memanggil CCmdTarget::GetControllingUnknown. Namun, nilai yang dikembalikan dari fungsi tersebut tidak valid selama konstruktor. Untuk alasan ini, disarankan agar Anda membuat agregat Anda hanya dalam penimpaan CCmdTarget::OnCreateAggregates, di mana nilai pengembalian dari GetControllingUnknown dapat diandalkan, bahkan jika dibuat dari COleObjectFactory implementasi.

Penting juga bahwa objek memanipulasi jumlah referensi yang benar saat menambahkan atau merilis jumlah referensi buatan. Untuk memastikan hal ini terjadi, selalu panggil ExternalAddRef dan ExternalRelease bukan InternalRelease dan InternalAddRef. Jarang memanggil InternalRelease atau InternalAddRef di kelas yang mendukung agregasi.

Materi Referensi

Penggunaan lanjutan OLE, seperti menentukan antarmuka Anda sendiri atau mengambil alih implementasi kerangka kerja antarmuka OLE memerlukan penggunaan mekanisme peta antarmuka yang mendasar.

Bagian ini membahas setiap makro dan API yang digunakan untuk mengimplementasikan fitur lanjutan ini.

CCmdTarget::EnableAggregation — Deskripsi Fungsi

void EnableAggregation();

Keterangan

Panggil fungsi ini di konstruktor kelas turunan jika Anda ingin mendukung agregasi OLE untuk objek jenis ini. Ini menyiapkan implementasi IUnknown khusus yang diperlukan untuk objek yang dapat diagregasi.

CCmdTarget::ExternalQueryInterface — Deskripsi Fungsi

DWORD ExternalQueryInterface(
    const void FAR* lpIID,
    LPVOIDFAR* ppvObj
);

Parameter

lpIID
Penunjuk jauh ke IID (argumen pertama ke QueryInterface)

ppvObj
Penunjuk ke IUnknown* (argumen kedua ke QueryInterface)

Keterangan

Panggil fungsi ini dalam implementasi IUnknown Anda untuk setiap antarmuka yang diterapkan kelas Anda. Fungsi ini menyediakan implementasi queryInterface berbasis data standar berdasarkan peta antarmuka objek Anda. Penting untuk melemparkan nilai pengembalian ke HRESULT. Jika objek diagregasi, fungsi ini akan memanggil "mengontrol IUnknown" alih-alih menggunakan peta antarmuka lokal.

CCmdTarget::ExternalAddRef — Deskripsi Fungsi

DWORD ExternalAddRef();

Keterangan

Panggil fungsi ini dalam implementasi IUnknown::AddRef Anda untuk setiap antarmuka yang diterapkan kelas Anda. Nilai yang dikembalikan adalah jumlah referensi baru pada objek CCmdTarget. Jika objek diagregasi, fungsi ini akan memanggil "mengontrol IUnknown" alih-alih memanipulasi jumlah referensi lokal.

CCmdTarget::ExternalRelease — Deskripsi Fungsi

DWORD ExternalRelease();

Keterangan

Panggil fungsi ini dalam implementasi IUnknown::Release Anda untuk setiap antarmuka yang diterapkan kelas Anda. Nilai pengembalian menunjukkan jumlah referensi baru pada objek. Jika objek diagregasi, fungsi ini akan memanggil "mengontrol IUnknown" alih-alih memanipulasi jumlah referensi lokal.

DECLARE_INTERFACE_MAP — Deskripsi Makro

DECLARE_INTERFACE_MAP

Keterangan

Gunakan makro ini di kelas apa pun yang berasal dari CCmdTarget yang akan memiliki peta antarmuka. Digunakan dengan cara yang sama seperti DECLARE_MESSAGE_MAP. Pemanggilan makro ini harus ditempatkan dalam definisi kelas, biasanya di header (. H) file. Kelas dengan DECLARE_INTERFACE_MAP harus menentukan peta antarmuka dalam file implementasi (. CPP) dengan makro BEGIN_INTERFACE_MAP dan END_INTERFACE_MAP.

BEGIN_INTERFACE_PART dan END_INTERFACE_PART — Deskripsi Makro

BEGIN_INTERFACE_PART(localClass, iface);
END_INTERFACE_PART(localClass)

Parameter

localClass
Nama kelas yang mengimplementasikan antarmuka

iface
Nama antarmuka yang diterapkan kelas ini

Keterangan

Untuk setiap antarmuka yang akan diterapkan kelas Anda, Anda harus memiliki pasangan BEGIN_INTERFACE_PART dan END_INTERFACE_PART. Makro ini mendefinisikan kelas lokal yang berasal dari antarmuka OLE yang Anda tentukan serta variabel anggota yang disematkan dari kelas tersebut. Anggota AddRef, Rilis, dan QueryInterface dideklarasikan secara otomatis. Anda harus menyertakan deklarasi untuk fungsi anggota lain yang merupakan bagian dari antarmuka yang diimplementasikan (deklarasi tersebut ditempatkan antara makro BEGIN_INTERFACE_PART dan END_INTERFACE_PART).

Argumen iface adalah antarmuka OLE yang ingin Anda terapkan, seperti IAdviseSink, atau (atau IPersistStorage antarmuka kustom Anda sendiri).

Argumen localClass adalah nama kelas lokal yang akan ditentukan. 'X' akan secara otomatis ditambahkan ke nama. Konvensi penamaan ini digunakan untuk menghindari tabrakan dengan kelas global dengan nama yang sama. Selain itu, nama anggota yang disematkan, sama dengan nama localClass kecuali diawali oleh 'm_x'.

Contohnya:

BEGIN_INTERFACE_PART(MyAdviseSink, IAdviseSink)
    STDMETHOD_(void, OnDataChange)(LPFORMATETC, LPSTGMEDIUM);
    STDMETHOD_(void, OnViewChange)(DWORD, LONG);
    STDMETHOD_(void, OnRename)(LPMONIKER);
    STDMETHOD_(void, OnSave)();
    STDMETHOD_(void, OnClose)();
END_INTERFACE_PART(MyAdviseSink)

akan menentukan kelas lokal yang disebut XMyAdviseSink yang berasal dari IAdviseSink, dan anggota kelas di mana ia dinyatakan disebut m_xMyAdviseSink.Note:

Catatan

Baris yang dimulai dengan STDMETHOD_ pada dasarnya disalin dari OLE2. H dan sedikit dimodifikasi. Menyalinnya dari OLE2. H dapat mengurangi kesalahan yang sulit diatasi.

BEGIN_INTERFACE_MAP dan END_INTERFACE_MAP — Deskripsi Makro

BEGIN_INTERFACE_MAP(theClass, baseClass)
END_INTERFACE_MAP

Parameter

theClass
Kelas tempat peta antarmuka akan didefinisikan

baseClass
Kelas dari mana theClass berasal.

Keterangan

Makro BEGIN_INTERFACE_MAP dan END_INTERFACE_MAP digunakan dalam file implementasi untuk benar-benar menentukan peta antarmuka. Untuk setiap antarmuka yang diimplementasikan ada satu atau beberapa pemanggilan makro INTERFACE_PART. Untuk setiap agregat yang digunakan kelas, ada satu pemanggilan makro INTERFACE_AGGREGATE.

INTERFACE_PART — Deskripsi Makro

INTERFACE_PART(theClass, iid, localClass)

Parameter

theClass
Nama kelas yang berisi peta antarmuka.

iid
Yang IID akan dipetakan ke kelas yang disematkan.

localClass
Nama kelas lokal (kurang dari 'X').

Keterangan

Makro ini digunakan antara makro BEGIN_INTERFACE_MAP dan makro END_INTERFACE_MAP untuk setiap antarmuka yang akan didukung objek Anda. Ini memungkinkan Anda untuk memetakan IID ke anggota kelas yang ditunjukkan olehclass dan localClass. 'm_x' akan ditambahkan ke localClass secara otomatis. Perhatikan bahwa lebih dari satu IID mungkin dikaitkan dengan satu anggota. Ini sangat berguna ketika Anda hanya mengimplementasikan antarmuka "paling turunan" dan ingin menyediakan semua antarmuka perantara juga. Contoh yang baik dari ini adalah IOleInPlaceFrameWindow antarmuka. Hierarkinya terlihat seperti ini:

IUnknown
    IOleWindow
        IOleUIWindow
            IOleInPlaceFrameWindow

Jika objek mengimplementasikan IOleInPlaceFrameWindow, klien dapat QueryInterface pada salah satu antarmuka ini: IOleUIWindow, , IOleWindowatau IUnknown, selain antarmuka IOleInPlaceFrameWindow "paling turunan" (yang benar-benar Anda terapkan). Untuk menangani ini, Anda dapat menggunakan lebih dari satu makro INTERFACE_PART untuk memetakan masing-masing dan setiap antarmuka dasar ke IOleInPlaceFrameWindow antarmuka:

dalam file definisi kelas:

BEGIN_INTERFACE_PART(CMyFrameWindow, IOleInPlaceFrameWindow)

dalam file implementasi kelas:

BEGIN_INTERFACE_MAP(CMyWnd, CFrameWnd)
    INTERFACE_PART(CMyWnd, IID_IOleWindow, MyFrameWindow)
    INTERFACE_PART(CMyWnd, IID_IOleUIWindow, MyFrameWindow)
    INTERFACE_PART(CMyWnd, IID_IOleInPlaceFrameWindow, MyFrameWindow)
END_INTERFACE_MAP

Kerangka kerja mengurus IUnknown karena selalu diperlukan.

INTERFACE_PART — Deskripsi Makro

INTERFACE_AGGREGATE(theClass, theAggr)

Parameter

theClass
Nama kelas yang berisi peta antarmuka,

theAggr
Nama variabel anggota yang akan diagregasi.

Keterangan

Makro ini digunakan untuk memberi tahu kerangka kerja bahwa kelas menggunakan objek agregat. Ini harus muncul antara makro BEGIN_INTERFACE_PART dan END_INTERFACE_PART. Objek agregat adalah objek terpisah, berasal dari IUnknown. Dengan menggunakan makro agregat dan INTERFACE_AGGREGATE, Anda dapat membuat semua antarmuka yang didukung agregat tampaknya didukung langsung oleh objek. Argumen TheAggr hanyalah nama variabel anggota kelas Anda yang berasal dari IUnknown (baik secara langsung atau tidak langsung). Semua makro INTERFACE_AGGREGATE harus mengikuti makro INTERFACE_PART saat ditempatkan di peta antarmuka.

Baca juga

Catatan Teknis menurut Angka
Catatan Teknis menurut Kategori