选择加入统一构造和直接实现访问(下)
在我们的示例中,MyClass.h 可能如下所示(不管是否使用 -opt[imize])。
// MyClass.h
#pragma once
#include "MyClass.g.h"
namespace winrt::MyProject::implementation
{
struct MyClass : ClassT<MyClass>
{
MyClass() = default;
static void StaticMethod();
void Method();
};
}
namespace winrt::MyProject::factory_implementation
{
struct MyClass : ClassT<MyClass, implementation::MyClass>
{
};
}
MyClass.cpp
是整合这一切的地方。
#include "pch.h"
#include "MyClass.h"
#include "MyClass.g.cpp" // !!It's important that you add this line!!
namespace winrt::MyProject::implementation
{
void MyClass::StaticMethod()
{
}
void MyClass::Method()
{
}
}
因此,若要在现有项目中使用统一构造,需编辑每个实现的 .cpp
文件,以便在包括(和定义)实现类后 #include <Sub/Namespace/Type.g.cpp>
。 该文件提供具现未定义的函数的定义。 下面是 MyClass.g.cpp
文件中的这些定义。
namespace winrt::MyProject
{
MyClass::MyClass() :
MyClass(make<MyProject::implementation::MyClass>())
{
}
void MyClass::StaticMethod()
{
return MyProject::implementation::MyClass::StaticMethod();
}
}
这样就可以高效地直接调用实现,避免调用工厂缓存,并且满足了链接器的要求,很好地完成了具现。
最后,-opt[imize]
会更改项目的 module.g.cpp
(此文件用于实现 DLL 的 DllGetActivationFactory 和 DllCanUnloadNow 导出)的实现,通过消除 C++/WinRT 1.0 所需要的强类型耦合,大大加快了增量生成的速度。 这通常称为“类型擦除工厂”。 在没有 -opt[imize]
的情况下,为组件生成的 module.g.cpp
文件一开始就会包括所有实现类的定义,在本例中为 MyClass.h
。 然后,它会直接创建每个类的实现工厂,如下所示。
if (requal(name, L"MyProject.MyClass"))
{
return winrt::detach_abi(winrt::make<winrt::MyProject::factory_implementation::MyClass>());
}
同样,你不需要遵循所有细节。 需要注意的是,这需要为组件所实现的任何以及所有类进行完整的定义。 这可能会对内循环产生巨大影响,因为对单个实现的任何更改都会导致 module.g.cpp
重新编译。 在使用 -opt[imize]
的情况下,这不再是一个问题, 但所生成的 module.g.cpp
文件会出现两种情况。 第一种情况是,它不再包括任何实现类。 在此示例中,它根本不包括 MyClass.h
, 而只是创建实现工厂,不需要对其实现有任何的了解。
void* winrt_make_MyProject_MyClass();
if (requal(name, L"MyProject.MyClass"))
{
return winrt_make_MyProject_MyClass();
}
很明显,不需要包括其定义,只需由链接器解析 winrt_make_Component_Class 函数的定义即可。 当然,你不需考虑这一点,因为为你生成的 MyClass.g.cpp 文件(为了支持统一构造,你以前已包括了此文件)也定义该函数。 下面是为此示例生成的 MyClass.g.cpp 文件的完整内容:
void* winrt_make_MyProject_MyClass()
{
return winrt::detach_abi(winrt::make<winrt::MyProject::factory_implementation::MyClass>());
}
namespace winrt::MyProject
{
MyClass::MyClass() :
MyClass(make<MyProject::implementation::MyClass>())
{
}
void MyClass::StaticMethod()
{
return MyProject::implementation::MyClass::StaticMethod();
}
}
如你所见,winrt_make_MyProject_MyClass 函数直接创建实现的工厂。 这一切意味着,你可以随意更改任何给定实现,根本不需要重新编译 module.g.cpp。 只有在添加或删除 Windows 运行时类的时候,才会更新 module.g.cpp,此时需将其重新编译。
重写基类虚拟方法
如果基类和派生类都是应用定义类,则派生类可能出现虚拟方法问题,但虚拟方法是在祖父 Windows 运行时类中定义的。 实际上,如果从 XAML 类派生,则会出现这种情况。 本部分的其余内容是派生类中的示例的继续。
namespace winrt::MyNamespace::implementation
{
struct BasePage : BasePageT<BasePage>
{
void OnNavigatedFrom(Windows::UI::Xaml::Navigation::NavigationEventArgs const& e);
};
struct DerivedPage : DerivedPageT<DerivedPage>
{
void OnNavigatedFrom(Windows::UI::Xaml::Navigation::NavigationEventArgs const& e);
};
}
层次结构为 Windows::UI::Xaml::Controls::Page<- BasePage<- DerivedPage。 BasePage::OnNavigatedFrom 方法正确重写了 Page::OnNavigatedFrom,但 DerivedPage::OnNavigatedFrom 不重写 BasePage::OnNavigatedFrom。
在这里,DerivedPage 重用 BasePage 提供的 IPageOverrides vtable,这意味着它无法重写 IPageOverrides::OnNavigatedFrom 方法。 一个可能的解决方案是让 BasePage 本身充当一个模板类,将其实现完全置于标头文件中,但这会使得事情变得相当复杂,令人不可接受。
解决方法是在基类中将 OnNavigatedFrom 方法声明为显式虚拟方法。 这样,当 DerivedPage::IPageOverrides::OnNavigatedFrom 的 vtable 条目调用 BasePage::IPageOverrides::OnNavigatedFrom 时,生成器会调用 BasePage::OnNavigatedFrom,后者因其虚拟特性,最终会调用 DerivedPage::OnNavigatedFrom。
namespace winrt::MyNamespace::implementation
{
struct BasePage : BasePageT<BasePage>
{
// Note the `virtual` keyword here.
virtual void OnNavigatedFrom(Windows::UI::Xaml::Navigation::NavigationEventArgs const& e);
};
struct DerivedPage : DerivedPageT<DerivedPage>
{
void OnNavigatedFrom(Windows::UI::Xaml::Navigation::NavigationEventArgs const& e);
};
}
这要求类层次结构的所有成员都认可 OnNavigatedFrom 方法的返回值和参数类型。 如果它们不认可,则应使用上面的版本作为虚拟方法,并将备用内容包装好。注意,IDL 不需要声明重写的方法。 有关详细信息,请参阅实现可重写的方法。