问题
To learn myself Direct2D i'm following this example from the MSDN.
I have however one issue. The call D2D1_SIZE_F rtSize = m_pRenderTarget->GetSize();
allways returns a size of 0,0 and in the debugger causes an exception on the DrawLine
call. If I leave out the GetSize() call and fill in the D2D1_SIZE_F structure with valid values it works.
The relevant code for initializing the render target is:
RECT rc;
GetClientRect(m_hwnd, &rc);
D2D1_SIZE_U size = D2D1::SizeU(
rc.right - rc.left,
rc.bottom - rc.top
);
// Create a Direct2D render target.
hr = m_pDirect2dFactory->CreateHwndRenderTarget(
D2D1::RenderTargetProperties(),
D2D1::HwndRenderTargetProperties(m_hwnd, size),
&m_pRenderTarget
);
I have verified with the debugger that valid values are past in size.
The part of the drawing code where GetSize is called:
m_pRenderTarget->BeginDraw();
m_pRenderTarget->SetTransform(D2D1::Matrix3x2F::Identity());
m_pRenderTarget->Clear(D2D1::ColorF(D2D1::ColorF::White));
D2D1_SIZE_F rtSize = m_pRenderTarget->GetSize();
// Draw a grid background.
int width = static_cast<int>(rtSize.width);
int height = static_cast<int>(rtSize.height);
for (int x = 0; x < width; x += 10)
{
m_pRenderTarget->DrawLine(
D2D1::Point2F(static_cast<FLOAT>(x), 0.0f),
D2D1::Point2F(static_cast<FLOAT>(x), rtSize.height),
m_pLightSlateGrayBrush,
0.5f
);
}
So my question is why does GetSize() return 0,0 and causes an AV later on?
BTW: I'm using:
Windows 7 Ultimate 64-bits
Code::Blocks IDE
TDM-GCC-64 gcc compiler v4.8.1
I'm compiling in Unicode mode #define UNICODE
Problem occurs regardles if I compile to 32-bits or 64-bits (yes I made a few minor adjustments for 64-bits mode to make sure I had a valid pointer to the application object in WndProc)
回答1:
Why does GetSize() return 0,0 and causes an AV later on?
Because the call to GetSize generated by GCC/MinGW-W64 doesn't match the calling convention of the implementation in d2d1.dll
. The return type D2D_SIZE_F
of GetSize is a struct. According to Microsoft Docs there are two ways to return a struct from a function:
User-defined types can be returned by value from global functions and static member functions. To return a user-defined type by value in RAX, it must have a length of 1, 2, 4, 8, 16, 32, or 64 bits. It must also have no user-defined constructor, destructor, or copy assignment operator. It can have no private or protected non-static data members, and no non-static data members of reference type. It can't have base classes or virtual functions. And, it can only have data members that also meet these requirements. (This definition is essentially the same as a C++03 POD type. Because the definition has changed in the C++11 standard, we don't recommend using std::is_pod for this test.) Otherwise, the caller must allocate memory for the return value and pass a pointer to it as the first argument.
When GCC/MinGW-W64 compiles the sample code from the article, the caller only sets up one argument (in rcx
) for the call to GetSize, and expects the value to be returned in rax
:
# AT&T syntax (destination operand last)
mov 0x10(%rbx),%rcx # rcx <- pointer to IRenderContext
mov (%rcx),%rax # rax <- pointer to virtual function table
callq *0x1a8(%rax) # virtual function call (expect result in rax)
In the code generated by Visual Studio, the caller sets up rdx
to point to a location on the stack before calling GetSize:
# Intel syntax (destination operand first)
mov rax,qword ptr [rsp+168h] # rax <- pointer to IRenderContext
mov rax,qword ptr [rax] # rax <- pointer to virtual function table
lea rdx,[rsp+68h] # rdx <- address of return value (hidden argument)
mov rcx,qword ptr [rsp+168h] # rcx <- this pointer (hidden argument)
call qword ptr [rax+1A8h] # virtual function call (expect result at [rdx])
On GCC/MinGW-W64 the value in rdx
is not a valid address, so when the implementation of GetSize attempts to store the return value in memory there, an access violation occurs.
D2D_SIZE_F
is a 64-bit POD struct (just a struct of two floats), so it seems to me that GCC is correct to return it in the rax
register. I don't know what it is that makes Visual Studio use return-by-pointer, nor, I'm afraid, how to make GCC do the same for compatibility.
回答2:
I think this is actually related to a nine-year-old bug in gcc and unclear or incorrect MS Documentation of the calling convention.
According to that bug report, if the return structure cannot be fit into a register, its pointer will be in RDX (2nd arg), and the called object will be in RCX (1st arg). gcc does it the other way 'round, with return pointer in RCX (1st arg) and called object in RDX (2nd arg).
It's not 100% clear which way is correct per the docs: The Return Values documentation for C++ documentation says to make the return value pointer the first argument. Separately, the Calling Conventions documentation for Debugging says that the this
pointer is passed as an implicit first parameter.
Clearly gcc and MSVC disagree about the order of those two rules being applied. It appears in my limited tested that Clang agrees with MSVC, but I haven't been able to follow the logic fully yet. Clang does seem to treat this situation as a 'thiscall', and in that case excludes the RCX register for hidden return object pointers. I haven't worked out how it actually puts the 'this' pointer into RCX though, it's probably not super-important.
Back to this problem, where it's not returning the structure by value. With a small Compiler Explorer test, the only time that MSVC uses a hidden return value instead of return-by-value in RAX is when it's a member call, and it's an object. Clang agrees, and you can see really clearly in the Clang IR that it put the object pointer first, and the hidden-return-struct-pointer second:
call void @"?GetPixelSize@ID2D1RenderTarget@@QEBA?AUD2D_SIZE_U@@XZ"(%class.ID2D1RenderTarget* %4, %struct.D2D_SIZE_U* sret %2), !dbg !31
The reason I suspect this is related to the gcc bug, is that I am guessing that the underlying issue is the ordering of dealing with moving the "return value pointer" and "this pointer" into the argument list.
gcc (I guess?) processes the called object first, and pushes it as the new first argument. Then it independently looks at the return object, and either returns-by-value, or pushes it as the new-new first argument, leaving the called object ultimately second.
Clang is processing the other way 'round. It processes the return object first, but already knows it's a this-call, which is how it knows to avoid ECX above. If it had already processed the called object pointer, ECX would have been allocated already. However, when deciding if the return is by-value or by-hidden-object-pointer, it clearly already knows that it is dealing with a this-pointer, because that makes the difference.
And knowing that, and hunting backwards from the CCIfSRet
seen above, I found that Clang specifically marks that for non-POD return values, or Instance methods, the return value is indirect and not by-value. This code is not hit if the return value is not a structure, which is why (as seen in Compiler Explorer) a uint64_t doesn't get turned into an indirect return here.
This is also the only place I saw that explicitly sets that the 'return structure pointer' comes after the 'called-object' pointer. I guess every other ABI puts them in the same order as gcc.
(I couldn't check gcc on Compiler Explorer, as there doesn't seem to be a version offered which supports the Win32 ABI, e.g., mingw64 or tdm builds)
This is the same place that ensures the correct order of the two hidden parameters, i.e. avoids the gcc bug that started me on this hunt in the first place.
And now that I know where the code is, git blame
shows me that this was a known thing about the x64 ABI in llvm 3.5 in 2014 although a bunch of other cases were fixed in llvm 9 in 2019.
Of course, Clang is not MSVC. It is presumably emulating an observed behaviour of MSVC, but it's possible that the MSVC outcome is just a coincidence of order-of-processing, and it happens to be the opposite of gcc.
So while gcc is correct by a strict reading of the ABI docs, it has two mismatches compared to both MSVC (the ABI owner) and Clang around handling hidden arguments for instance methods with aggregate return values. One has been bugged, and this problem is reproducing the other.
The workaround in mingw-w64's headers functions by making the hidden structure-return-pointer an explicit pointer parameter. This both ensures gcc doesn't try to pass it in a register, and also puts it after the hidden called-object parameter.
You can see the implementation side of the same fix in Wine, which was already using an explicit called-object pointer, and so to get the ordering correct, needs to use an explicit return-structure-pointer parameter too.
Side-note: I haven't looked into the 32-bit failure.
I had a quick look with Clang (which I don't know to be correct here, as Compiler Explorer doesn't appear to offer 32-bit MSVC) and it seems to produce the same call for both __stdcall
and __thiscall
, except that the __stdcall
version saves ECX, but the __thiscall
version does not. I guess that's just a difference in what the function is allowed to stomp, and what it must restore when it's done.
Based on the commit description in Clang's history I suspect the same nine-year-old-bug is affecting 32-bit gcc too.
Update: Looking at the Return values documentation months later, I noticed that this limitation is documented:
User-defined types can be returned by value from global functions and static member functions.
So the return-by-value-in-register approach is not supported for member functions, except static member functions, and gcc is not correct per the ABI docs in this case.
来源:https://stackoverflow.com/questions/27888109/rendertarget-getsize-not-working