要了解自己Direct2D我正在关注MSDN中的this example。
但我有一个问题。调用D2D1_SIZE_F rtSize = m_pRenderTarget->GetSize();
allways返回0,0的大小,并且在调试器中导致DrawLine
调用异常。如果我省略了GetSize()调用并用有效值填充D2D1_SIZE_F结构,它就可以工作。
初始化渲染目标的相关代码是:
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
);
我已经通过调试器验证了有效值的大小。
调用GetSize的绘图代码部分:
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
);
}
所以我的问题是为什么GetSize()会返回0,0并在以后导致AV?
BTW:我正在使用:
Windows 7旗舰版64位
Code :: Blocks IDE
TDM-GCC-64 gcc编译器v4.8.1
我正在以Unicode模式#define UNICODE
进行编译
如果我编译为32位或64位,则会出现问题(是的,我对64位模式进行了一些小调整,以确保我在WndProc中有一个指向应用程序对象的有效指针)
答案 0 :(得分:2)
为什么GetSize()会返回0,0并在以后导致AV?
因为GCC / MinGW-W64生成的GetSize调用与d2d1.dll
中实现的调用约定不匹配。 GetSize的返回类型D2D_SIZE_F
是一个结构。根据{{3}},有两种方法可以从函数中返回结构:
用户定义的类型可以通过全局函数和静态成员函数的值返回。要通过RAX中的值返回,用户定义的类型必须具有1,2,4,8,16,32或64位的长度;没有用户定义的构造函数,析构函数或复制赋值运算符;没有私有或受保护的非静态数据成员;没有引用类型的非静态数据成员;没有基类;没有虚拟功能;没有数据成员也不符合这些要求。 (这实际上是C ++ 03 POD类型的定义。由于C ++ 11标准中的定义已经改变,我们不建议使用std :: is_pod进行此测试。)否则,调用者承担责任分配内存并将返回值的指针作为第一个参数传递。
当GCC / MinGW-W64编译文章中的示例代码时,调用者只为GetSize调用设置一个参数(在rcx
中),并期望在{{1}中返回该值}:
rax
在Visual Studio生成的代码中,调用者在调用GetSize之前将# 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)
设置为指向堆栈上的某个位置:
rdx
在GCC / MinGW-W64上,# 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])
中的值不是有效地址,因此当GetSize的实现尝试将返回值存储在内存中时,会发生访问冲突。
rdx
是一个64位POD结构(只是两个浮点数的结构),所以在我看来,GCC在D2D_SIZE_F
寄存器中返回是正确的。我不知道是什么让Visual Studio使用了返回指针,我也担心如何使GCC在兼容性方面做同样的事情。
答案 1 :(得分:1)
我认为这实际上与nine-year-old bug in gcc以及调用约定的MS文档不清晰或不正确有关。
根据该错误报告,如果返回结构无法装入寄存器,则其指针将位于RDX(第二个arg)中,被调用对象将位于RCX(第一个arg)中。 gcc以另一种方式进行操作,在RCX(第一个参数)中使用返回指针,在RDX(第二个参数)中调用对象。
目前尚不清楚100%哪种方法对每个文档都是正确的:Return Values documentation for C++文档说要使返回值指针成为第一个参数。另外,Calling Conventions documentation for Debugging说this
指针是作为隐式第一个参数传递的。
很明显,gcc和MSVC对于这两个规则的应用顺序不同。在经过有限测试的情况下,Clang似乎同意MSVC,但是我还无法完全遵循该逻辑。 Clang确实将这种情况视为“ thiscall”,而in that case excludes the RCX register则视为hidden return object pointers。我还没有弄清楚如何将“ this”指针真正放入RCX中,这可能并不重要。
回到此问题,它不按值返回结构。使用较小的Compiler Explorer test,MSVC唯一在RAX中使用隐藏的返回值而不是按值返回的时间是当它是成员调用,和时它是一个对象。 Clang同意,您可以在Clang IR中清楚地看到,它把对象指针放在第一位,然后将hidden-return-struct-pointer放在第二位:
call void @"?GetPixelSize@ID2D1RenderTarget@@QEBA?AUD2D_SIZE_U@@XZ"(%class.ID2D1RenderTarget* %4, %struct.D2D_SIZE_U* sret %2), !dbg !31
我怀疑这与gcc错误有关,原因是我猜测潜在的问题是将“返回值指针”和“此指针”移入参数列表的处理顺序。
gcc(我猜想吗?)首先处理被调用的对象,并将其作为新的第一个参数推送。然后,它独立地查看返回对象,然后按值返回,或者将其作为新的第一个参数推送,最终使被调用对象第二个。
Clang正在以另一种方式处理。它首先处理返回对象,但已经知道这是此调用,这就是避免上面提到的ECX的方法。如果它已经处理了被调用的对象指针,则ECX将已经被分配。但是,在确定返回值是按值还是隐藏对象指针时,它显然已经知道它正在处理此指针,因为那会有所不同。
并且知道了这一点,并且从上面看到的CCIfSRet
向后搜寻,我发现了Clang specifically marks that for non-POD return values, or Instance methods, the return value is indirect and not by-value。这段代码未打if the return value is not a structure,这就是为什么(在编译器资源管理器中看到的)uint64_t不会在这里变成间接返回的原因。
这也是我看到explicitly sets that the 'return structure pointer' comes after the 'called-object' pointer的唯一地方。我猜每隔一个ABI会将它们置于与gcc相同的顺序。
(我无法在Compiler Explorer上检查gcc,因为似乎没有提供支持Win32 ABI的版本,例如mingw64或tdm版本)
在同一地方可以确保两个隐藏参数的顺序正确,即避免了从一开始就使我开始寻宝的gcc错误。
现在我知道代码在 位置,git blame
向我显示,虽然a known thing about the x64 ABI in llvm 3.5 in 2014是a bunch of other cases were fixed in llvm 9 in 2019。
当然,Clang不是MSVC。它可能是在模拟观察到的MSVC行为,但MSVC结果可能只是处理顺序的巧合,而恰好与gcc相反。
因此,尽管严格阅读ABI文档对gcc来说是正确的,但与MSVC(ABI所有者)和Clang相比,在处理具有聚合返回值的实例方法的隐藏参数时,它有两个不匹配之处。一个已经被窃听,这个问题正在重现另一个。
workaround in mingw-w64's headers通过使隐藏的结构返回指针成为显式指针参数来起作用。这样既可以确保gcc不会尝试将其传递到寄存器中,也可以将其放置在隐藏的被调用对象参数之后。
您可以看到implementation side of the same fix in Wine,它已经在使用显式的被调用对象指针,因此为了正确排序,还需要使用显式的return-structure-pointer参数。
旁注:我没有研究过32位故障。
我对Clang进行了快速浏览(在这里我不知道是正确的,因为Compiler Explorer似乎不提供32位MSVC),并且似乎产生了相同的结果调用__stdcall
和__thiscall
,除了__stdcall
版本保存ECX,而__thiscall
版本不保存。我想这只是允许重载什么功能以及完成后必须还原什么功能的区别。
基于commit description in Clang's history,我怀疑同一个具有9年历史的错误也正在影响32位gcc。
更新:在Return values documentation个月后,我注意到记录了该限制 :
可以通过全局函数和静态成员函数的值返回用户定义的类型。
因此,除静态成员函数外,成员函数不支持按值返回值的方法,在这种情况下,根据ABI文档,gcc不正确。