通过 C++/WinRT 创作 COM 组件Author COM components with C++/WinRT

C++/WinRT 可以帮助你创作经典组件对象模型 (COM) 组件(或组件类),就像它可以帮助你创作 Windows 运行时类一样。C++/WinRT can help you to author classic Component Object Model (COM) components (or coclasses), just as it helps you to author Windows Runtime classes. 本主题演示如何执行该操作。This topic shows you how.

默认情况下,C++/WinRT 在 COM 接口方面的表现如何How C++/WinRT behaves, by default, with respect to COM interfaces

C++/WinRT 的 winrt::implements 模板是直接或间接派生运行时类和激活工厂的基础。C++/WinRT's winrt::implements template is the base from which your runtime classes and activation factories directly or indirectly derive.

默认情况下,winrt::implements 仅支持基于 IInspectable 的接口,并且会以无提示方式忽略经典的 COM 接口。By default, winrt::implements supports only IInspectable-based interfaces, and it silently ignores classic COM interfaces. 因此,通过 QueryInterface (QI) 调用经典 COM 接口会失败,并出现 E_NOINTERFACE 错误。Any QueryInterface (QI) calls for classic COM interfaces will consequently fail with E_NOINTERFACE.

我们稍后会讲述如何克服这种情况,现在让我们先来看看一个代码示例,了解默认情况下会发生什么。In a moment you'll see how to overcome that situation, but first here's a code example to illustrate what happens by default.

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

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

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

下面是使用 Sample 类的客户端代码。And here's client code to consume the Sample class.

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

// This next line crashes, because the QI for IInitializeWithWindow fails.
sample.as<IInitializeWithWindow>()->Initialize(hwnd); 

好在要让 winrt::implements 支持经典 COM 接口,只需在包括任何 C++/WinRT 头文件之前包括 unknwn.h 即可。The good news is that all it takes to cause winrt::implements to support classic COM interfaces is to include unknwn.h before you include any C++/WinRT headers.

可以显式这样做,也可以间接这样做,只需包括一些其他的头文件(例如 ole2.h)即可。You could do that explicitly, or indirectly by including some other header file such as ole2.h. 一个建议的方法是包括 wil\cppwinrt.h 头文件,该文件是 Windows 实现库 (WIL) 的一部分。One recommended method is to include the wil\cppwinrt.h header file, which is part of the Windows Implementation Libraries (WIL). wil\cppwinrt.h 头文件不仅可确保在 unknwn.h 之前包括 winrt/base.h,而且可以让 C++/WinRT 和 WIL 了解彼此的异常和错误代码。The wil\cppwinrt.h header file not only makes sure that unknwn.h is included before winrt/base.h, it also sets things up so that C++/WinRT and WIL understand each other's exceptions and error codes.

COM 组件的简单示例A simple example of a COM component

下面是使用 C++/WinRT 编写的 COM 组件的简单示例。Here's a simple example of a COM component written using C++/WinRT. 这是一个微型应用程序的完整代码清单,因此如果将其粘贴到新 pch.hWindows 控制台应用程序 (C++/WinRT)main.cpp 项目的 中,则可对其进行试用。This is a full listing of a mini-application, so you can try the code out if you paste it into the pch.h and main.cpp of a new Windows Console Application (C++/WinRT) project.

// 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());
}

另请参阅通过 C++/WinRT 使用 COM 组件Also see Consume COM components with C++/WinRT.

一个更现实且有趣的示例A more realistic and interesting example

此主题的其余部分演练如何创建使用 C++/WinRT 实现基本组件类(COM 组件或 COM 类)和类工厂的最小控制台应用程序项目。The remainder of this topic walks through creating a minimal console application project that uses C++/WinRT to implement a basic coclass (COM component, or COM class) and class factory. 示例应用程序演示如何提供具有一个回调按钮的 toast 通知,组件类(实现 INotificationActivationCallback COM 接口)使应用程序可以启动并在用户单击 toast 上的该按钮时进行回调。The example application shows how to deliver a toast notification with a callback button on it, and the coclass (which implements the INotificationActivationCallback COM interface) allows the application to be launched and called back when the user clicks that button on the toast.

有关 toast 通知功能区域的更多背景信息可以在发送本地 toast 通知中找到。More background about the toast notification feature area can be found at Send a local toast notification. 不过文档该部分中没有代码示例使用 C++/WinRT,因此建议首选本主题中演示的代码。None of the code examples in that section of the documentation use C++/WinRT, though, so we recommend that you prefer the code shown in this topic.

创建 Windows 控制台应用程序项目 (ToastAndCallback)Create a Windows Console Application project (ToastAndCallback)

首先在 Microsoft Visual Studio 中创建新项目。Begin by creating a new project in Microsoft Visual Studio. 创建“Windows 控制台应用程序(C++/WinRT)” 项目,然后将它命名为 ToastAndCallback 。Create a Windows Console Application (C++/WinRT) project, and name it ToastAndCallback.

打开 pch.h,在用于任何 C++/WinRT 标头的 include 语句之前添加 #include <unknwn.h>Open pch.h, and add #include <unknwn.h> before the includes for any C++/WinRT headers. 下面是结果;可以将你的 pch.h 的内容替换为此清单。Here's the result; you can replace the contents of your pch.h with this listing.

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

打开 main.cpp,删除项目模板生成 using 指令。Open main.cpp, and remove the using-directives that the project template generates. 在其位置插入以下代码(该代码提供我们所需的库、标头和类型名称)。In their place, insert the following code (which gives us the libs, headers, and type names that we need). 下面是结果;可以将你的 main.cpp 的内容替换为此清单(我们还会在下面的清单中删除 main 中的代码,因为我们会在以后替换该函数)。Here's the result; you can replace the contents of your main.cpp with this listing (we've also removed the code from main in the listing below, because we'll be replacing that function later).

// 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() { }

项目尚未生成;完成添加代码之后,系统会提示生成并运行。The project won't build yet; after we've finished adding code, you'll be prompted to build and run.

实现组件类和类工厂Implement the coclass and class factory

在 C++/WinRT 中,通过从 winrt::implements 基结构派生来实现组件类和类工厂。In C++/WinRT, you implement coclasses, and class factories, by deriving from the winrt::implements base struct. 紧接着上面所示的三个 using 指令之后(并且在 main 之前),粘贴此代码以实现 toast 通知 COM 激活器组件。Immediately after the three using-directives shown above (and before main), paste this code to implement your toast notification COM activator component.

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;
    }
};

上面的组件类实现遵循在使用 C++/WinRT 创作 API 中演示的相同模式。The implementation of the coclass above follows the same pattern that's demonstrated in Author APIs with C++/WinRT. 因此,可以使用相同技术实现 COM 接口以及 Windows 运行时接口。So, you can use the same technique to implement COM interfaces as well as Windows Runtime interfaces. COM 组件和 Windows 运行时类会通过接口公开其功能。COM components and Windows Runtime classes expose their features via interfaces. 每个 COM 接口最终派生自 IUnknown 接口 接口。Every COM interface ultimately derives from the IUnknown interface interface. Windows 运行时基于 COM — 一个区别是 Windows 运行时接口最终派生自 IInspectable 接口 (并且 IInspectable 派生自 IUnknown )。The Windows Runtime is based on COM—one distinction being that Windows Runtime interfaces ultimately derive from the IInspectable interface (and IInspectable derives from IUnknown).

在上面代码中的组件类中,我们实现 INotificationActivationCallback::Activate 方法,这是在用户单击 toast 通知上的回调按钮时调用的函数。In the coclass in the code above, we implement the INotificationActivationCallback::Activate method, which is the function that's called when the user clicks the callback button on a toast notification. 但是在可以调用该函数之前,需要创建组件类的实例,这是 IClassFactory::CreateInstance 函数的工作。But before that function can be called, an instance of the coclass needs to be created, and that's the job of the IClassFactory::CreateInstance function.

我们刚刚实现的组件类称为通知的 COM 激活器 ,其类 id (CLSID) 的形式为如上所示的 callback_guid 标识符(类型为 GUID )。The coclass that we just implemented is known as the COM activator for notifications, and it has its class id (CLSID) in the form of the callback_guid identifier (of type GUID) that you see above. 我们会在以后采用“开始”菜单快捷方式和 Windows 注册表项的形式来使用该标识符。We'll be using that identifier later, in the form of a Start menu shortcut and a Windows Registry entry. COM 激活器 CLSID 以及指向其关联 COM 服务器的路径(这是在此处生成的可执行文件的路径)是一种机制,toast 通知该机制可知道在单击其回调按钮时要创建哪个类的实例(无论是否在操作中心内单击通知)。The COM activator CLSID, and the path to its associated COM server (which is the path to the executable that we're building here) is the mechanism by which a toast notification knows what class to create an instance of when its callback button is clicked (whether the notification is clicked in Action Center or not).

实现 COM 方法的最佳做法Best practices for implementing COM methods

用于错误处理和资源管理的方法可以一起进行。Techniques for error handling and for resource management can go hand-in-hand. 使用异常比错误代码更加方便且实用。It's more convenient and practical to use exceptions than error codes. 如果采用资源获取即初始化 (RAII) 惯用做法,则可以避免显式检查错误代码,然后显式释放资源。And if you employ the resource-acquisition-is-initialization (RAII) idiom, then you can avoid explicitly checking for error codes and then explicitly releasing resources. 这类显式检查使代码比实际所需更复杂,并使 bug 有大量位置可以隐藏。Such explicit checks make your code more convoluted than necessary, and it gives bugs plenty of places to hide. 相反,使用 RAII,并引发/捕获异常。Instead, use RAII, and throw/catch exceptions. 这样,资源分配是异常安全的,并且代码十分简单。That way, your resource allocations are exception-safe, and your code is simple.

但是,不得允许异常转义 COM 方法实现。However, you mustn't allow exceptions to escape your COM method implementations. 可以通过对 COM 方法使用 noexcept 说明符来确保这一点。You can ensure that by using the noexcept specifier on your COM methods. 可以在方法调用关系图中的任意位置引发异常,只要在方法退出之前处理它们。It's ok for exceptions to be thrown anywhere in the call graph of your method, as long as you handle them before your method exits. 如果使用 noexcept,但随后允许异常转义方法,则应用程序会终止。If you use noexcept, but you then allow an exception to escape your method, then your application will terminate.

添加帮助程序类型和函数Add helper types and functions

在此步骤中,我们会添加一些帮助程序类型和函数,代码的其余部分会使用它们。In this step, we'll add some helper types and functions that the rest of the code makes use of. 就在 main 之前添加以下内容。So, immediately before main, add the following.

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;
}

实现其余函数和 wmain 入口点函数Implement the remaining functions, and the wmain entry point function

删除 main 函数,在其原位置粘贴此代码清单,其中包括用于注册组件类,然后提供能够回调应用程序的 toast 的代码。Delete your main function, and in its place paste this code listing, which includes code to register your coclass, and then to deliver a toast capable of calling back your application.

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);
}

如何测试示例应用程序How to test the example application

生成应用程序,然后至少以管理员身份运行一次,以便使注册(和其他设置)代码运行。Build the application, and then run it at least once as an administrator to cause the registration, and other setup, code to run. 执行此操作的一种方法是以管理员身份运行 Visual Studio,然后从 Visual Studio 运行应用。One way to do that is to run Visual Studio as an administrator, and then run the app from Visual Studio. 在任务栏中右键单击 Visual Studio 以显示跳转列表,在跳转列表右键单击 Visual Studio,然后单击“以管理员身份运行” 。Right-click Visual Studio in the taskbar to display the jump list, right-click Visual Studio on the jump list, and then click Run as administrator. 同意提示,然后打开项目。Agree to the prompt, and then open the project. 运行应用程序时,会显示一条消息,指出是否在以管理员身份运行应用程序。When you run the application, a message is displayed indicating whether or not the application is running as an administrator. 如果不是,则注册和其他设置不会运行。If it isn't, then the registration and other setup won't run. 该注册和其他设置必须至少运行一次,才能使应用程序正常工作。That registration and other setup has to run at least once in order for the application to work correctly.

无论是否在以管理员身份运行应用程序,按“T”使 toast 显示。Whether or not you're running the application as an administrator, press 'T' to cause a toast to be displayed. 然后可以直接从弹出的 toast 通知或是从操作中心单击“回调 ToastAndCallback” 按钮,这样会启动应用程序,实例化组件类并执行 INotificationActivationCallback::Activate 方法。You can then click the Call back ToastAndCallback button either directly from the toast notification that pops up, or from the Action Center, and your application will be launched, the coclass instantiated, and the INotificationActivationCallback::Activate method executed.

进程内 COM 服务器In-process COM server

上面的 ToastAndCallback 示例应用可充当本地(或进程外)COM 服务器。The ToastAndCallback example app above functions as a local (or out-of-process) COM server. 这由 LocalServer32 Windows 注册表项进行指示,该项用于注册其组件类的 CLSID。This is indicated by the LocalServer32 Windows Registry key that you use to register the CLSID of its coclass. 本地 COM 服务器在可执行二进制文件 (.exe) 中承载其组件类。A local COM server hosts its coclass(es) inside an executable binary (an .exe).

或者(并且可能更有可能),可以选择在动态链接库 (.dll) 中承载组件类。Alternatively (and arguably more likely), you can choose to host your coclass(es) inside a dynamic-link library (a .dll). 采用 DLL 形式的 COM 服务器称为进程内 COM 服务器,由使用 InprocServer32 Windows 注册表项注册 CLSID 进行指示。A COM server in the form of a DLL is known as an in-process COM server, and it's indicated by CLSIDs being registered by using the InprocServer32 Windows Registry key.

可以通过在 Microsoft Visual Studio 中创建新项目,来开始创建进程内 COM 服务器的任务。You can begin the task of creating an in-process COM server by creating a new project in Microsoft Visual Studio. 创建“Visual C++” > “Windows 桌面” > “动态链接库(DLL)” 项目。Create a Visual C++ > Windows Desktop > Dynamic-Link Library (DLL) project.

若要向新项目添加 C++/WinRT 支持,请执行修改 Windows 桌面应用程序项目以添加 C++/WinRT 支持中所述的步骤。To add C++/WinRT support to the new project, follow the steps described in Modify a Windows Desktop application project to add C++/WinRT support.

实现组件类、类工厂和进程内服务器导出Implement the coclass, class factory, and in-proc server exports

打开 dllmain.cpp,并向其添加如下所示的代码清单。Open dllmain.cpp, and add to it the code listing shown below.

如果已具有一个实现 C++WinRT Windows 运行时类的 DLL,则表示已具有如下所示的 DllCanUnloadNow 函数。If you already have a DLL that implements C++/WinRT Windows Runtime classes, then you'll already have the DllCanUnloadNow function shown below. 如果要将组件类添加到该 DLL,则可以添加 DllGetClassObject 函数。If you want to add coclasses to that DLL, then you can add the DllGetClassObject function.

如果没有要保持与之兼容的现有 Windows 运行时 C++ 模板库 (WRL) 代码,则可以从显示的代码中删除 WRL 部分。If don't have existing Windows Runtime C++ Template Library (WRL) code that you want to stay compatible with, then you can remove the WRL parts from the code shown.

// 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();
    }
}

对弱引用的支持Support for weak references

另请参阅 C++/WinRT 中的弱引用Also see Weak references in C++/WinRT.

如果类型实现 IInspectable (或任何派生自 IInspectable 的接口),则 C++/WinRT(具体而言, winrt::implements基结构模板)会实现IWeakReferenceSourceC++/WinRT (specifically, the winrt::implements base struct template) implements IWeakReferenceSource for you if your type implements IInspectable (or any interface that derives from IInspectable).

这是因为 IWeakReferenceSource 和 IWeakReference 旨在用于 Windows 运行时类型。This is because IWeakReferenceSource and IWeakReference are designed for Windows Runtime types. 因此,只需通过向实现添加 winrt::Windows::Foundation::IInspectable (或派生自 IInspectable 的接口),即可为组件类启用弱引用支持。So, you can turn on weak reference support for your coclass simply by adding winrt::Windows::Foundation::IInspectable (or an interface that derives from IInspectable) to your implementation.

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

重要的 APIImportant APIs