为了了解自己的Direct2D,我正在跟踪MSDN中的这个例子。
不过,我有一个问题。调用D2D1_SIZE_F rtSize = m_pRenderTarget->GetSize();总是返回大小为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?
顺便说一句:我使用的是: Windows 7终极64位代码::块IDE TDM- gcc -64 gcc编译器v4.8.1,如果我编译为32位或64位,就会出现#define UNICODE问题(是的,我对64位模式做了一些小调整,以确保我在WndProc中有一个指向应用程序对象的有效指针)
发布于 2015-12-23 14:58:22
为什么GetSize()返回0,0并在稍后导致AV?
因为GCC/MinGW-W64生成的对GetSize的调用不符合d2d1.dll中实现的调用约定。返回类型D2D_SIZE_F of GetSize是一个结构。根据Microsoft,有两种方法可以从函数返回结构:
用户定义的类型可以通过全局函数和静态成员函数的值返回。若要在RAX中按值返回用户定义的类型,则必须具有1、2、4、8、16、32或64位的长度。它还必须没有用户定义的构造函数、析构函数或复制赋值操作符.它可以没有私有或受保护的非静态数据成员,也不能具有引用类型的非静态数据成员。它不能有基类或虚拟函数。而且,它只能有满足这些要求的数据成员。(这个定义基本上与C++03 POD类型相同。由于定义在C++11标准中已经更改,因此我们不建议在此测试中使用std::is_pod。)否则,调用方必须为返回值分配内存,并将指向该值的指针作为第一个参数传递。
当GCC/MinGW-W64编译本文中的示例代码时,调用方只为调用GetSize设置一个参数(在GetSize中),并期望在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)在Visual生成的代码中,调用方在调用rdx之前将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])在GCC/MinGW-W64上,rdx中的值不是有效地址,所以当GetSize的实现试图将返回值存储在内存中时,就会发生访问冲突。
D2D_SIZE_F是一个64位的POD结构(只是一个由两个浮点数组成的结构),所以在我看来GCC在rax寄存器中返回它是正确的。我不知道是什么使Visual使用逐指针返回,恐怕也不知道如何让GCC为了兼容性而做同样的事情。
发布于 2020-07-13 17:20:31
我认为这实际上与调用约定的gcc的九岁小虫和不明确或不正确的MS文档有关。
根据该错误报告,如果返回结构不能放入寄存器中,则其指针将位于RDX (第2 arg)中,而被调用的对象将位于RCX (第1 arg)中。gcc用RCX (第1 arg)返回指针,在RDX (第2 arg)中调用object (第2 arg)。
对于每个文档,还不完全清楚哪种方法是正确的:C++的返回值文档文档说要使返回值指针成为第一个参数。另外,调用约定文档进行调试表示this指针作为隐式第一个参数传递。
显然,gcc和MSVC对这两条规则的适用顺序存在分歧。在我有限的测试中,Clang同意MSVC,但我还不能完全遵循逻辑。Clang似乎把这种情况看作是“这个调用”,而在这种情况下,不包括RCX寄存器。则是隐藏返回对象指针。我还没有弄清楚它是如何将这个指针放到RCX中的,但它可能并不是非常重要。
回到这个问题上,它没有按值返回结构。对于小型编译器资源管理器测试,MSVC在RAX中唯一使用隐藏的返回值而不是按值返回的情况是,当它是一个成员调用时,它是一个对象。Clang同意,您可以在Clang IR中清楚地看到,它将对象指针放在第一位,而隐藏返回结构指针放在第二位:
call void @"?GetPixelSize@ID2D1RenderTarget@@QEBA?AUD2D_SIZE_U@@XZ"(%class.ID2D1RenderTarget* %4, %struct.D2D_SIZE_U* sret %2), !dbg !31我怀疑这与gcc错误有关,因为我猜测根本问题是处理将“返回值指针”和“此指针”移到参数列表中的顺序。
gcc (我猜?)先处理被调用的对象,然后将其作为新的第一个参数推送。然后它独立地查看返回对象,或者按值返回,或者将其作为新的新的第一个参数推送,使被调用的对象最终处于第二位。
Clang是在以相反的方式处理。它首先处理返回对象,但已经知道它是一个this-调用,这是它知道如何避免上述ECX的方法。如果它已经处理了被调用的对象指针,那么ECX就已经被分配了。然而,在决定返回是按值返回还是按隐藏的对象指针进行时,它显然已经知道它正在处理这个指针,因为这会带来不同的结果。
知道了这一点,并从上面看到的CCIfSRet向后搜索,我发现了Clang 实例方法时,返回值是间接的,而不是按值计算的。。这段代码没有命中如果返回值不是结构,这就是为什么(如编译器资源管理器中所示) uint64_t不会在这里被转化为间接返回。
这也是我看到“被调用对象”指针的唯一地方。我想其他的ABI都把它们按照gcc的顺序排列。
(我无法在编译器资源管理器上查看gcc,因为似乎没有支持Win32 ABI的版本,例如mingw64或tdm构建)
这是确保两个隐藏参数的正确顺序的同一个地方,即避免gcc的错误,它首先启动了我的搜索。
现在我知道代码在哪里了,git blame告诉我这是关于x64 ABI的一个众所周知的事情,即2014年的lvm3.5。,尽管是一组其他病例在2019年的1vm 9中被修复。。
当然,Clang不是MSVC。这大概是在模仿MSVC的一个观察到的行为,但是MSVC的结果可能只是处理顺序的巧合,而它恰好与gcc正好相反。
因此,虽然gcc严格阅读ABI文档是正确的,但与MSVC ( ABI所有者)和Clang相比,它有两个不匹配之处,例如,处理带有聚合返回值的隐藏参数的方法。其中一个已经被窃听,而这个问题正在复制另一个。
在mingw-w64的标题中的解决方法通过使隐藏结构-返回指针成为显式指针参数来发挥作用。这既确保gcc不会试图将其传递到寄存器中,也会将其放在隐藏的调用对象参数之后。
您可以看到葡萄酒中相同修复的实现端,它已经使用了一个显式的被调用对象指针,因此要获得正确的排序,也需要使用一个显式的返回结构指针参数。
旁注:我还没有调查32位故障。
我快速地查看了Clang (我不知道这里是否正确,因为编译器资源管理器似乎不提供32位MSVC),它似乎对__stdcall和__thiscall都产生了相同的调用,只是__stdcall版本保存了ECX,但__thiscall版本没有。我想这只是函数被允许践踏的地方,以及它完成后必须恢复的部分。
基于在Clang的历史上提交描述,我怀疑同样是9岁的虫子也在影响32位gcc。
更新:查看几个月后的返回值文档,我注意到这个限制已记录在案:
用户定义的类型可以通过全局函数和静态成员函数的值返回。
因此,除了静态成员函数之外,成员函数不支持按值返回的寄存器方法,在这种情况下,gcc在ABI文档中也不正确。
https://stackoverflow.com/questions/27888109
复制相似问题