RenderTarget-> GetSize不起作用

时间:2015-01-11 14:22:37

标签: c++ gcc windows-7-x64 direct2d

要了解自己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中有一个指向应用程序对象的有效指针)

2 个答案:

答案 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 Debuggingthis指针是作为隐式第一个参数传递的。

很明显,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 2014a 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不正确。