首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >RenderTarget->GetSize不工作

RenderTarget->GetSize不工作
EN

Stack Overflow用户
提问于 2015-01-11 14:22:37
回答 2查看 1.2K关注 0票数 1

为了了解自己的Direct2D,我正在跟踪MSDN中的这个例子

不过,我有一个问题。调用D2D1_SIZE_F rtSize = m_pRenderTarget->GetSize();总是返回大小为0,0的调用,并在调试器中对DrawLine调用造成异常。如果我省略了GetSize()调用并使用有效值填充D2D1_SIZE_F结构,它就能工作。

初始化呈现目标的相关代码是:

代码语言:javascript
复制
    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的部分:

代码语言:javascript
复制
    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中有一个指向应用程序对象的有效指针)

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 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中返回该值。

代码语言:javascript
复制
# 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设置为指向堆栈上的某个位置:

代码语言:javascript
复制
# 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为了兼容性而做同样的事情。

票数 2
EN

Stack Overflow用户

发布于 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中清楚地看到,它将对象指针放在第一位,而隐藏返回结构指针放在第二位:

代码语言:javascript
复制
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文档中也不正确。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/27888109

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档