runtime: WPF Applications crashing when loading DWrite
- .NET Core Version: 3.0.100-preview6-012012, regression from preview5, exact build that regresses unknown at this time.
- Windows version: N/A
- Does the bug reproduce also in WPF for .NET Framework 4.8?:No
Problem description: When a WPF application loads DirectWriteForwarder, the module constructor attempts to load DirectWrite and instantiate the appropriate factory for use with font rendering. In this build, the invocation of the creation function for the factory fails with an invalid cast HResult.
The offending function is in Factory,cpp on line 122 (pasted here as this source is not currently available):
__declspec(noinline) void Factory::Initialize(
FactoryType factoryType
)
{
IUnknown* factoryTemp;
DWRITECREATEFACTORY pfnDWriteCreateFactory = (DWRITECREATEFACTORY)GetDWriteCreateFactoryFunctionPointer();
HRESULT hr = (*pfnDWriteCreateFactory)(
DWriteTypeConverter::Convert(factoryType),
(REFIID)(*(_guidForIDWriteFactory->Value)),
&factoryTemp
);
ConvertHresultToException(hr, "Factory::Initialize");
_pFactory = (IDWriteFactory*)factoryTemp;
}
Actual behavior: Invalid cast exception.
Expected behavior: DirectWrite is initialized.
Minimal repro: https://github.com/rladuca/DWriteIssue
About this issue
- Original URL
- State: closed
- Created 5 years ago
- Reactions: 1
- Comments: 30 (23 by maintainers)
@rladuca @vatsan-madhavan If you need a quick fix you may want to just pass the IID directly, without involving
_guidForIDWriteFactory. The justification for that complication that I see in the reference sourceis likely irrelevant in .NET Core where security is no longer relevant.
This is a bug in the JIT compiler. The static constructor of
Factorycontains a 16 byte
cpblkthat copies a
_GUIDto *pGuidForIDWriteFactory. The JIT gets confused by the fact that the_GUIDstruct contains a singleintfield and copies only 4 bytes instead of 16.@CarolEidt FYI I’ll take look. In theory this shouldn’t be difficult to fix but I wouldn’t surprised to find that it is - we need to distinguish between an actual
cpblk(which under no circumstances can ignore holes) and acpobjwhich may do field by field copy.C# only (no WPF, no C++/CLI):
This prints something like
02551bc7-0000-0000-0000-000000000000, like the IID passed toDWriteCreateFactorythis has 4 valid bytes and the rest are all 0.Still using
cpblk, like in the C++/CLI version. The issue can be reproduced withoutcpblkbut it’s debatable if copying such a struct requires copying the entire struct or only its sole field. Obviously this is not up for debate in the case ofcpblk, it must copy all 16 bytes.Since it happens that I run into this exception myself I took a quick look out of curiosity. It would seem that
DWriteCreateFactoryis called with an invalid IID and thus the call fails withE_NOINTERFACE.The IID doesn’t seem to be completely invalid, it’s more like truncated - 4 bytes are fine and the rest are 0. That smalls like bad code generation to me. I’ll try to take a closer look.