C++/WinRT ile COM bileşenleri yazma

C++/WinRT, Windows Çalışma Zamanı sınıfları yazmanıza yardımcı olduğu gibi klasik Bileşen Nesne Modeli (COM) bileşenleri (veya ortak sınıflar) yazmanıza da yardımcı olabilir. Bu konu size nasıl yapılacağını gösterir.

C++/WinRT'nin COM arabirimlerine göre varsayılan olarak nasıl davrandığı

C++/WinRT'nin winrt::implements şablonu, çalışma zamanı sınıflarınızın ve etkinleştirme fabrikalarınızın doğrudan veya dolaylı olarak türetildiği temeldir.

Varsayılan olarak winrt::implements sessizce klasik COM arabirimlerini yoksayar. Klasik COM arabirimleri için tüm QueryInterface (QI) çağrıları E_NOINTERFACE ile başarısız olur. Varsayılan olarak , winrt::implements yalnızca C++/WinRT arabirimlerini destekler.

  • winrt::IUnknown bir C++/WinRT arabirimi olduğundan winrt::implementswinrt::IUnknown tabanlı arabirimleri destekler.
  • winrt::implements varsayılan olarak ::IUnknown'un kendisini desteklemez.

Bir dakika içinde, varsayılan olarak desteklenmeyen örneklerin üstesinden nasıl geleceğinizi göreceksiniz. Ancak ilk olarak, varsayılan olarak ne olduğunu gösteren bir kod örneği verilmiştir.

// Sample.idl
namespace MyProject 
{
    runtimeclass Sample
    {
        Sample();
        void DoWork();
    }
}

// Sample.h
#include "pch.h"
#include <shobjidl.h> // Needed only for this file.

namespace winrt::MyProject::implementation
{
    struct Sample : implements<Sample, IInitializeWithWindow>
    {
        IFACEMETHOD(Initialize)(HWND hwnd);
        void DoWork();
    }
}

Örnek sınıfını kullanmak için istemci kodu da aşağıda verilmiştır.

// Client.cpp
Sample sample; // Construct a Sample object via its projection.

// This next line doesn't compile yet.
sample.as<IInitializeWithWindow>()->Initialize(hwnd); 

Klasik COM desteğini etkinleştirme

İyi haber şu ki, winrt::implements'ın klasik COM arabirimlerini desteklemesi için gereken tek şey, herhangi bir C++/WinRT üst bilgisi eklemeden önce unknwn.h üst bilgi dosyasını eklemektir.

Bunu açıkça ya da ole2.h gibi başka bir başlık dosyasını ekleyerek dolaylı olarak yapabilirsiniz. Önerilen yöntemlerden biri, wil\cppwinrt.h parçası olan üst bilgi dosyasını eklemektir. wil\cppwinrt.h başlık dosyası, unknwn.h dosyasının winrt/base.h dosyasından önce eklenmesini sağlamakla kalmaz, ayrıca C++/WinRT ile WIL'in birbirlerinin istisnalarını ve hata kodlarını anlayabilmesi için gerekli düzenlemeleri de yapar.

Ardından, klasik COM arabirimleri için as<> kullanabilirsiniz ve yukarıdaki örnekteki kod derlenir.

Note

Yukarıdaki örnekte, istemcide klasik COM desteğini etkinleştirdikten sonra bile (sınıfını kullanan kod), sunucuda klasik COM desteğini de etkinleştirmediyseniz (sınıfı uygulayan kod), <> için QI başarısız olduğundan istemcide olduğu gibi çağrısı kilitlenir.

Yerel (yansıtılmayan) bir sınıf

Yerel sınıf, aynı derleme biriminde (uygulama veya diğer ikili) hem uygulanan hem de kullanılan sınıftır; Ve bu yüzden bunun bir projeksiyonu yoktur.

Burada yalnızca klasik COM arabirimlerini uygulayan bir yerel sınıf örneği verilmiştir.

struct LocalObject :
    winrt::implements<LocalObject, IInitializeWithWindow>
{
    ...
};

Bu örneği uygularsanız ancak klasik COM desteğini etkinleştirmezseniz aşağıdaki kod başarısız olur.

winrt::make<LocalObject>(); // error: ‘first_interface’: is not a member of ‘winrt::impl::interface_list<>’

IInitializeWithWindow, COM arabirimi olarak tanınmadığından C++/WinRT bunu yoksayar. LocalObject örneğinde, COM arabirimlerini yoksaymanın sonucu, LocalObject'in hiç arabirimi olmadığı anlamına gelir. Ancak her COM sınıfı en az bir arabirim uygulamalıdır.

COM bileşeninin basit bir örneği

C++/WinRT kullanılarak yazılmış basit bir COM bileşeni örneği aşağıda verilmiştır. Bu bir mini uygulamanın tam listesidir, bu nedenle kodu yeni bir pch.h projesine main.cpp yapıştırırsanız kodu deneyebilirsiniz.

// pch.h
#pragma once
#include <unknwn.h>
#include <winrt/Windows.Foundation.h>

// main.cpp : Defines the entry point for the console application.
#include "pch.h"

struct __declspec(uuid("ddc36e02-18ac-47c4-ae17-d420eece2281")) IMyComInterface : ::IUnknown
{
    virtual HRESULT __stdcall Call() = 0;
};

using namespace winrt;
using namespace Windows::Foundation;

int main()
{
    winrt::init_apartment();

    struct MyCoclass : winrt::implements<MyCoclass, IPersist, IStringable, IMyComInterface>
    {
        HRESULT __stdcall Call() noexcept override
        {
            return S_OK;
        }

        HRESULT __stdcall GetClassID(CLSID* id) noexcept override
        {
            *id = IID_IPersist; // Doesn't matter what we return, for this example.
            return S_OK;
        }

        winrt::hstring ToString()
        {
            return L"MyCoclass as a string";
        }
    };

    auto mycoclass_instance{ winrt::make<MyCoclass>() };
    CLSID id{};
    winrt::check_hresult(mycoclass_instance->GetClassID(&id));
    winrt::check_hresult(mycoclass_instance.as<IMyComInterface>()->Call());
}

Ayrıca bkz . C++/WinRT ile COM bileşenlerini kullanma.

Daha gerçekçi ve ilginç bir örnek

Bu konunun geri kalanında, temel bir ortak sınıf (COM bileşeni veya COM sınıfı) ve sınıf fabrikası uygulamak için C++/WinRT kullanan en düşük konsol uygulaması projesi oluşturma adımları açıklanmaktadır. Örnek uygulama, üzerinde bir geri çağırma düğmesi bulunan bir toast bildiriminin nasıl gösterileceğini gösterir ve coclass (INotificationActivationCallback COM arabirimini uygular) kullanıcının toast bildirimindeki bu düğmeye tıklaması durumunda uygulamanın başlatılmasını ve geri çağrı almasını sağlar.

Toast bildirimi özelliği hakkında daha ayrıntılı bilgiyi Yerel toast bildirimi gönderme bölümünde bulabilirsiniz. Ancak belgelerin bu bölümündeki kod örneklerinden hiçbiri C++/WinRT kullanmadığından bu konuda gösterilen kodu tercih etmenizi öneririz.

Windows Konsol Uygulaması projesi oluşturma (ToastAndCallback)

Microsoft Visual Studio'da yeni bir proje oluşturarak başlayın. bir Windows Konsol Uygulaması (C++/WinRT) projesi oluşturun ve buna ToastAndCallback adını verin.

pch.h öğesini açın ve C++/WinRT başlıkları için tüm include ifadelerinden önce #include <unknwn.h> ekleyin. İşte sonuç; pch.h öğenizin içeriğini bu listeyle değiştirebilirsiniz.

// pch.h
#pragma once
#include <unknwn.h>
#include <winrt/Windows.Foundation.h>

main.cpp dosyasını açın ve proje şablonunun oluşturduğu using yönergelerini kaldırın. Onların yerine aşağıdaki kodu ekleyin (bize ihtiyacımız olan kitaplıkları, üst bilgileri ve tür adlarını verir). İşte sonuç; main.cpp öğenizin içeriğini bu listeleme ile değiştirebilirsiniz (ayrıca aşağıdaki listelemede main içindeki kodu da kaldırdık, çünkü bu işlevi daha sonra değiştireceğiz).

// main.cpp : Defines the entry point for the console application.

#include "pch.h"

#pragma comment(lib, "advapi32")
#pragma comment(lib, "ole32")
#pragma comment(lib, "shell32")

#include <iomanip>
#include <iostream>
#include <notificationactivationcallback.h>
#include <propkey.h>
#include <propvarutil.h>
#include <shlobj.h>
#include <winrt/Windows.UI.Notifications.h>
#include <winrt/Windows.Data.Xml.Dom.h>

using namespace winrt;
using namespace Windows::Data::Xml::Dom;
using namespace Windows::UI::Notifications;

int main() { }

Proje henüz derlenmez; Kod eklemeyi tamamladıktan sonra derlemeniz ve çalıştırmanız istenir.

Coclass'ı ve sınıf fabrikasını uygulayın

C++/WinRT'de, winrt::implements temel yapısından türetilerek ortak sınıfları ve sınıf fabrikalarını uygularsınız. Yukarıda gösterilen üç using yönergesinin hemen ardından (main öğesinden önce), anlık bildirim COM etkinleştirici bileşeninizi gerçekleştirmek için bu kodu yapıştırın.

static constexpr GUID callback_guid // BAF2FA85-E121-4CC9-A942-CE335B6F917F
{
    0xBAF2FA85, 0xE121, 0x4CC9, {0xA9, 0x42, 0xCE, 0x33, 0x5B, 0x6F, 0x91, 0x7F}
};

std::wstring const this_app_name{ L"ToastAndCallback" };

struct callback : winrt::implements<callback, INotificationActivationCallback>
{
    HRESULT __stdcall Activate(
        LPCWSTR app,
        LPCWSTR args,
        [[maybe_unused]] NOTIFICATION_USER_INPUT_DATA const* data,
        [[maybe_unused]] ULONG count) noexcept final
    {
        try
        {
            std::wcout << this_app_name << L" has been called back from a notification." << std::endl;
            std::wcout << L"Value of the 'app' parameter is '" << app << L"'." << std::endl;
            std::wcout << L"Value of the 'args' parameter is '" << args << L"'." << std::endl;
            return S_OK;
        }
        catch (...)
        {
            return winrt::to_hresult();
        }
    }
};

struct callback_factory : implements<callback_factory, IClassFactory>
{
    HRESULT __stdcall CreateInstance(
        IUnknown* outer,
        GUID const& iid,
        void** result) noexcept final
    {
        *result = nullptr;

        if (outer)
        {
            return CLASS_E_NOAGGREGATION;
        }

        return make<callback>()->QueryInterface(iid, result);
    }

    HRESULT __stdcall LockServer(BOOL) noexcept final
    {
        return S_OK;
    }
};

Yukarıdaki coclass’ın uygulanması, Author APIs with C++/WinRT bölümünde gösterilenle aynı deseni izler. Bu nedenle, COM arabirimlerinin yanı sıra Windows Çalışma Zamanı arabirimleri uygulamak için de aynı tekniği kullanabilirsiniz. COM bileşenleri ve Windows Çalışma Zamanı sınıfları arabirimler aracılığıyla özelliklerini kullanıma sunar. Her COM arabirimi sonuçta IUnknown arabirim arabiriminden türetilir. Windows Çalışma Zamanı, COM'u temel alır; farklardan biri de Windows Çalışma Zamanı arabirimlerinin sonuçta IInspectable arabiriminden türemesidir (ve IInspectable, IUnknown'dan türetilir).

Yukarıdaki kodda yer alan coclass içinde, kullanıcının bir toast bildirimindeki geri çağrı düğmesine tıkladığında çağrılan işlev olan INotificationActivationCallback::Activate yöntemini uygularız. Ancak bu işlevin çağrılabilmesi için önce ortak sınıfın bir örneğinin oluşturulması gerekir ve bu , IClassFactory::CreateInstance işlevinin işidir.

Az önce uyguladığımız ortak sınıf, bildirimler için COM etkinleştiricisi olarak bilinir ve yukarıda gördüğünüz tanımlayıcı (callback_guid türünde) biçiminde sınıf kimliğine (CLSID) sahiptir. Bu tanımlayıcıyı daha sonra Başlat menüsü kısayolu ve Windows Kayıt Defteri girdisi biçiminde kullanacağız. COM etkinleştiricisinin CLSID’si ve ilişkili COM sunucusunun yolu (yani burada oluşturduğumuz yürütülebilir dosyanın yolu), toast bildiriminin geri çağrı düğmesine tıklandığında, bildirime İşlem Merkezi’nde tıklanmış olsun ya da olmasın, hangi sınıfın örneğinin oluşturulacağını belirleyen mekanizmadır.

COM yöntemlerini uygulamak için en iyi yöntemler

Hata işleme ve kaynak yönetimi teknikleri el ele gidebilir. Özel durumları kullanmak hata kodlarından daha kullanışlı ve pratiktir. Ve resource-acquisition-is-initialization (RAII) yaklaşımını kullanırsanız, hata kodlarını açıkça kontrol etmekten ve ardından kaynakları açıkça serbest bırakmaktan kaçınabilirsiniz. Bu tür açık denetimler kodunuzu gerekenden daha karmaşık hale getirir ve hatalara gizlenecek birçok yer sağlar. Bunun yerine RAII ve throw/catch özel durumları kullanın. Bu şekilde, kaynak ayırmalarınız özel durum açısından güvenlidir ve kodunuz basittir.

Ancak, özel durumların COM yöntem uygulamalarınızın dışına taşmasına izin vermemelisiniz. COM yöntemlerinizde tanımlayıcıyı noexcept kullanarak bunu sağlayabilirsiniz. Özel durumların, yönteminizden çıkmadan önce ele aldığınız sürece, yönteminizin çağrı grafiğinin herhangi bir yerinde fırlatılması sorun değildir. noexcept kullanıyorsanız, ancak yöntemin dışına bir özel durumun çıkmasına izin verirseniz, uygulamanız sonlanır.

Yardımcı türleri ve işlevleri ekleme

Bu adımda, kodun geri kalanının kullandığı bazı yardımcı türleri ve işlevleri ekleyeceğiz. Bu nedenle, hemen önüne mainaşağıdakileri ekleyin.

struct prop_variant : PROPVARIANT
{
    prop_variant() noexcept : PROPVARIANT{}
    {
    }

    ~prop_variant() noexcept
    {
        clear();
    }

    void clear() noexcept
    {
        WINRT_VERIFY_(S_OK, ::PropVariantClear(this));
    }
};

struct registry_traits
{
    using type = HKEY;

    static void close(type value) noexcept
    {
        WINRT_VERIFY_(ERROR_SUCCESS, ::RegCloseKey(value));
    }

    static constexpr type invalid() noexcept
    {
        return nullptr;
    }
};

using registry_key = winrt::handle_type<registry_traits>;

std::wstring get_module_path()
{
    std::wstring path(100, L'?');
    uint32_t path_size{};
    DWORD actual_size{};

    do
    {
        path_size = static_cast<uint32_t>(path.size());
        actual_size = ::GetModuleFileName(nullptr, path.data(), path_size);

        if (actual_size + 1 > path_size)
        {
            path.resize(path_size * 2, L'?');
        }
    } while (actual_size + 1 > path_size);

    path.resize(actual_size);
    return path;
}

std::wstring get_shortcut_path()
{
    std::wstring format{ LR"(%ProgramData%\Microsoft\Windows\Start Menu\Programs\)" };
    format += (this_app_name + L".lnk");

    auto required{ ::ExpandEnvironmentStrings(format.c_str(), nullptr, 0) };
    std::wstring path(required - 1, L'?');
    ::ExpandEnvironmentStrings(format.c_str(), path.data(), required);
    return path;
}

Kalan işlevleri ve wmain giriş noktası işlevini uygulama

main fonksiyonunuzu silin ve yerine, coclass’ınızı kaydetmeye yönelik kodu içeren ve ardından uygulamanıza geri çağrı yapabilen bir toast bildirimi gönderen bu kod listesini yapıştırın.

void register_callback()
{
    DWORD registration{};

    winrt::check_hresult(::CoRegisterClassObject(
        callback_guid,
        make<callback_factory>().get(),
        CLSCTX_LOCAL_SERVER,
        REGCLS_SINGLEUSE,
        &registration));
}

void create_shortcut()
{
    auto link{ winrt::create_instance<IShellLink>(CLSID_ShellLink) };
    std::wstring module_path{ get_module_path() };
    winrt::check_hresult(link->SetPath(module_path.c_str()));

    auto store = link.as<IPropertyStore>();
    prop_variant value;
    winrt::check_hresult(::InitPropVariantFromString(this_app_name.c_str(), &value));
    winrt::check_hresult(store->SetValue(PKEY_AppUserModel_ID, value));
    value.clear();
    winrt::check_hresult(::InitPropVariantFromCLSID(callback_guid, &value));
    winrt::check_hresult(store->SetValue(PKEY_AppUserModel_ToastActivatorCLSID, value));

    auto file{ store.as<IPersistFile>() };
    std::wstring shortcut_path{ get_shortcut_path() };
    winrt::check_hresult(file->Save(shortcut_path.c_str(), TRUE));

    std::wcout << L"In " << shortcut_path << L", created a shortcut to " << module_path << std::endl;
}

void update_registry()
{
    std::wstring key_path{ LR"(SOFTWARE\Classes\CLSID\{????????-????-????-????-????????????})" };
    ::StringFromGUID2(callback_guid, key_path.data() + 23, 39);
    key_path += LR"(\LocalServer32)";
    registry_key key;

    winrt::check_win32(::RegCreateKeyEx(
        HKEY_CURRENT_USER,
        key_path.c_str(),
        0,
        nullptr,
        0,
        KEY_WRITE,
        nullptr,
        key.put(),
        nullptr));
    ::RegDeleteValue(key.get(), nullptr);

    std::wstring path{ get_module_path() };

    winrt::check_win32(::RegSetValueEx(
        key.get(),
        nullptr,
        0,
        REG_SZ,
        reinterpret_cast<BYTE const*>(path.c_str()),
        static_cast<uint32_t>((path.size() + 1) * sizeof(wchar_t))));

    std::wcout << L"In " << key_path << L", registered local server at " << path << std::endl;
}

void create_toast()
{
    XmlDocument xml;

    std::wstring toastPayload
    {
        LR"(
<toast>
  <visual>
    <binding template='ToastGeneric'>
      <text>)"
    };
    toastPayload += this_app_name;
    toastPayload += LR"(
      </text>
    </binding>
  </visual>
  <actions>
    <action content='Call back )";
    toastPayload += this_app_name;
    toastPayload += LR"(
' arguments='the_args' activationKind='Foreground' />
  </actions>
</toast>)";
    xml.LoadXml(toastPayload);

    ToastNotification toast{ xml };
    ToastNotifier notifier{ ToastNotificationManager::CreateToastNotifier(this_app_name) };
    notifier.Show(toast);
    ::Sleep(50); // Give the callback chance to display.
}

void LaunchedNormally(HANDLE, INPUT_RECORD &, DWORD &);
void LaunchedFromNotification(HANDLE, INPUT_RECORD &, DWORD &);

int wmain(int argc, wchar_t * argv[], wchar_t * /* envp */[])
{
    winrt::init_apartment();

    register_callback();

    HANDLE consoleHandle{ ::GetStdHandle(STD_INPUT_HANDLE) };
    INPUT_RECORD buffer{};
    DWORD events{};
    ::FlushConsoleInputBuffer(consoleHandle);

    if (argc == 1)
    {
        LaunchedNormally(consoleHandle, buffer, events);
    }
    else if (argc == 2 && wcscmp(argv[1], L"-Embedding") == 0)
    {
        LaunchedFromNotification(consoleHandle, buffer, events);
    }
}

void LaunchedNormally(HANDLE consoleHandle, INPUT_RECORD & buffer, DWORD & events)
{
    try
    {
        bool runningAsAdmin{ ::IsUserAnAdmin() == TRUE };
        std::wcout << this_app_name << L" is running" << (runningAsAdmin ? L" (administrator)." : L" (NOT as administrator).") << std::endl;

        if (runningAsAdmin)
        {
            create_shortcut();
            update_registry();
        }

        std::wcout << std::endl << L"Press 'T' to display a toast notification (press any other key to exit)." << std::endl;

        ::ReadConsoleInput(consoleHandle, &buffer, 1, &events);
        if (towupper(buffer.Event.KeyEvent.uChar.UnicodeChar) == L'T')
        {
            create_toast();
        }
    }
    catch (winrt::hresult_error const& e)
    {
        std::wcout << L"Error: " << e.message().c_str() << L" (" << std::hex << std::showbase << std::setw(8) << static_cast<uint32_t>(e.code()) << L")" << std::endl;
    }
}

void LaunchedFromNotification(HANDLE consoleHandle, INPUT_RECORD & buffer, DWORD & events)
{
    ::Sleep(50); // Give the callback chance to display its message.
    std::wcout << std::endl << L"Press any key to exit." << std::endl;
    ::ReadConsoleInput(consoleHandle, &buffer, 1, &events);
}

Örnek uygulamayı test etme

Uygulamayı derleyin ve ardından kayıt işleminin ve diğer kurulum kodlarının çalışması için uygulamayı en az bir kez yönetici olarak çalıştırın. Bunun bir yolu, Visual Studio yönetici olarak çalıştırmak ve ardından uygulamayı Visual Studio çalıştırmaktır. Atlama listesini görüntülemek için görev çubuğundaki Visual Studio sağ tıklayın, atlama listesinde Visual Studio sağ tıklayın ve ardından Yönetici olarak çalıştır'a tıklayın. İstemi kabul edin ve projeyi açın. Uygulamayı çalıştırdığınızda, uygulamanın yönetici olarak çalışıp çalışmadığını belirten bir ileti görüntülenir. Değilse, kayıt ve diğer kurulum çalışmaz. Uygulamanın düzgün çalışması için bu kaydın ve diğer kurulumun en az bir kez çalışması gerekir.

Uygulamayı yönetici olarak çalıştırıp çalıştırmadığınıza bakılmaksızın, bir bildirimin görüntülenmesi için 'T' tuşuna basın. Ardından, açılan toast bildiriminden ya da Eylem Merkezi'nden doğrudan ToastAndCallback'i geri çağır düğmesine tıklayabilirsiniz; uygulamanız başlatılır, coclass örneği oluşturulur ve INotificationActivationCallback::Activate yöntemi yürütülür.

İşlemdeki COM sunucusu

Yukarıdaki ToastAndCallback örnek uygulaması, yerel (veya işlem dışı) bir COM sunucusu olarak çalışır. Bu, coclass'ın CLSID'sini kaydetmek için kullandığınız LocalServer32 Windows Kayıt Defteri anahtarıyla gösterilir. Yerel COM sunucusu, ortak sınıflarını yürütülebilir bir ikili dosyada (bir .exe) barındırıyor.

Alternatif olarak (ve muhtemelen daha olası), ortak sınıflarınızı bir dinamik bağlantı kitaplığında (a .dll) barındırmayı seçebilirsiniz. DLL biçimindeki bir COM sunucusu, işlem içi COM sunucusu olarak bilinir ve InprocServer32 Windows Kayıt Defteri anahtarı kullanılarak kaydedilen CLSID'ler tarafından belirtilir.

Microsoft Visual Studio'de yeni bir proje oluşturarak işlem içi COM sunucusu oluşturma görevine başlayabilirsiniz. Visual C++>Windows Desktop>Dynamic-Link Kitaplığı (DLL) projesi oluşturun.

Yeni projeye C++/WinRT desteği eklemek için, C++/WinRT desteği eklemek için Windows Masaüstü uygulama projesini değiştirme başlığında açıklanan adımları izleyin.

Coclass, sınıf fabrikası ve işlem içi sunucu dışa aktarmalarını uygulayın

öğesini açın dllmain.cppve buna aşağıda gösterilen kod listesini ekleyin.

C++/WinRT Windows Çalışma Zamanı sınıflarını uygulayan bir DLL'niz zaten varsa, aşağıda gösterilen DllCanUnloadNow işlevine zaten sahip olursunuz. Bu DLL'ye ortak sınıflar eklemek istiyorsanız DllGetClassObject işlevini ekleyebilirsiniz.

Uyumlu kalmasını istediğiniz mevcut Windows Çalışma Zamanı C++ Şablon Kitaplığı (WRL) kodu yoksa, gösterilen koddan WRL bölümlerini kaldırabilirsiniz.

// dllmain.cpp

struct MyCoclass : winrt::implements<MyCoclass, IPersist>
{
    HRESULT STDMETHODCALLTYPE GetClassID(CLSID* id) noexcept override
    {
        *id = IID_IPersist; // Doesn't matter what we return, for this example.
        return S_OK;
    }
};

struct __declspec(uuid("85d6672d-0606-4389-a50a-356ce7bded09"))
    MyCoclassFactory : winrt::implements<MyCoclassFactory, IClassFactory>
{
    HRESULT STDMETHODCALLTYPE CreateInstance(IUnknown *pUnkOuter, REFIID riid, void **ppvObject) noexcept override
    {
        try
        {
            return winrt::make<MyCoclass>()->QueryInterface(riid, ppvObject);
        }
        catch (...)
        {
            return winrt::to_hresult();
        }
    }

    HRESULT STDMETHODCALLTYPE LockServer(BOOL fLock) noexcept override
    {
        // ...
        return S_OK;
    }

    // ...
};

HRESULT __stdcall DllCanUnloadNow()
{
#ifdef _WRL_MODULE_H_
    if (!::Microsoft::WRL::Module<::Microsoft::WRL::InProc>::GetModule().Terminate())
    {
        return S_FALSE;
    }
#endif

    if (winrt::get_module_lock())
    {
        return S_FALSE;
    }

    winrt::clear_factory_cache();
    return S_OK;
}

HRESULT __stdcall DllGetClassObject(GUID const& clsid, GUID const& iid, void** result)
{
    try
    {
        *result = nullptr;

        if (clsid == __uuidof(MyCoclassFactory))
        {
            return winrt::make<MyCoclassFactory>()->QueryInterface(iid, result);
        }

#ifdef _WRL_MODULE_H_
        return ::Microsoft::WRL::Module<::Microsoft::WRL::InProc>::GetModule().GetClassObject(clsid, iid, result);
#else
        return winrt::hresult_class_not_available().to_abi();
#endif
    }
    catch (...)
    {
        return winrt::to_hresult();
    }
}

Zayıf referans desteği

Ayrıca, C++/WinRT'deki zayıf başvurular konusuna bakın.

C++/WinRT (özellikle winrt::implements temel yapı şablonu), türünüz IInspectable (veya IInspectable'dan türeyen herhangi bir arabirimi) uyguluyorsa, sizin için IWeakReferenceSource uygular.

Bunun nedeni IWeakReferenceSource ve IWeakReference'ın Windows Çalışma Zamanı türleri için tasarlanmış olmasıdır. Dolayısıyla, gerçekleştiriminize yalnızca winrt::Windows::Foundation::IInspectable (veya IInspectable öğesinden türeyen bir arabirim) ekleyerek coclass'ınız için zayıf başvuru desteğini etkinleştirebilirsiniz.

struct MyCoclass : winrt::implements<MyCoclass, IMyComInterface, winrt::Windows::Foundation::IInspectable>
{
    //  ...
};

Başka bir arabirimden türetilen bir COM arabirimi uygulama

Arabirim türetimi, klasik COM'un bir özelliğidir (ve bu özellik bilinçli olarak Windows Çalışma Zamanı'da yer almaz). Aşağıda arabirim türetmesinin nasıl göründüğüne bir örnek verilmiştir.

IFileSystemBindData2 : public IFileSystemBindData { /* ... */  };

Örneğin, hem IFileSystemBindData hem de IFileSystemBindData2 uygulaması gereken bir sınıf yazıyorsanız, bunu belirtmenin ilk adımı yalnızca türetilmiş arabirimi uyguladığınızı bildirmektir, şöyle.

// pch.h
#pragma once
#include <Shobjidl.h>
...

// main.cpp
...
struct MyFileSystemBindData :
    implements<MyFileSystemBindData,
    IFileSystemBindData2>
{
    // IFileSystemBindData
    IFACEMETHOD(SetFindData)(const WIN32_FIND_DATAW* pfd) override { /* ... */ return S_OK; };
    IFACEMETHOD(GetFindData)(WIN32_FIND_DATAW* pfd) override { /* ... */ return S_OK; };

    // IFileSystemBindData2
    IFACEMETHOD(SetFileID)(LARGE_INTEGER liFileID) override { /* ... */ return S_OK; };
    IFACEMETHOD(GetFileID)(LARGE_INTEGER* pliFileID) override { /* ... */ return S_OK; };
    IFACEMETHOD(SetJunctionCLSID)(REFCLSID clsid) override { /* ... */ return S_OK; };
    IFACEMETHOD(GetJunctionCLSID)(CLSID* pclsid) override { /* ... */ return S_OK; };
};
...
int main()
...

Sonraki adım, MyFileSystemBindData örneğine karşı IID_IFileSystemBindData (temel arabirim) için QueryInterface'in (doğrudan veya dolaylı) çağrıldığında başarılı olduğundan emin olmaktır. Bunu, winrt::is_guid_of işlev şablonu için bir özelleştirme sağlayarak yaparsınız.

winrt::is_guid_of değişkendir ve bu nedenle arabirimlerin listesini sağlayabilirsiniz. IFileSystemBindData2 için yapılan bir denetimin IFileSystemBindData için bir testi de içermesini sağlayacak özelleştirmeyi şöyle belirtebilirsiniz.

// pch.h
...
namespace winrt
{
    template<>
    inline bool is_guid_of<IFileSystemBindData2>(guid const& id) noexcept
    {
        return is_guid_of<IFileSystemBindData2, IFileSystemBindData>(id);
    }
}

// main.cpp
...
int main()
{
    ...
    auto mfsbd{ winrt::make<MyFileSystemBindData>() };
    auto a{ mfsbd.as<IFileSystemBindData2>() }; // Would succeed even without the **is_guid_of** specialization.
    auto b{ mfsbd.as<IFileSystemBindData>() }; // Needs the **is_guid_of** specialization in order to succeed.
}

winrt::is_guid_of uzmanlığı projedeki tüm dosyalarda aynı olmalıdır ve arabirimin winrt::implements veya winrt::d elegate şablonu tarafından kullanıldığı noktada görünür olmalıdır. Genellikle bunu ortak bir üst bilgi dosyasına koyarsınız.

Önemli API'ler