Search code examples
windowsmfcgdi

Why does GetLogPen fail with a custom pen?


The calls to GetLogPen() and GetExtLogPen() both fail. They return zero...

CBrush Brush;
Brush.CreateSolidBrush( COLOR_MINORSELECTION );
Brush.GetLogBrush( &lBrush );
DWORD Style[] = { 3, 1 };
CPen CustomPen;
CustomPen.CreatePen( PS_USERSTYLE, 1, &lBrush, 2, Style );
CPen *pOldPen = pDC->SelectObject( &CustomPen );
LOGPEN LogPen;
if( CustomPen->GetLogPen( &LogPen ) == 0 )
{
    EXTLOGPEN ExtLogPen;
    if( CustomPen->GetExtLogPen( &ExtLogPen ) == 0 )
        return;
}

The failure appears to be because of the PS_USERSTYLE being used for the pen style. If I do this with a PS_SOLID pen, I get the LogPen structure filled in as I expect.

Any thoughts?


Solution

  • This is a bug in the CPen::GetExtLogPen implementation:

    int CPen::GetExtLogPen(EXTLOGPEN* pLogPen) {
        return ::GetObject(m_hObject, sizeof(EXTLOGPEN), pLogPen);
    }
    

    The implementation ignores the trailing variable-sized array of DWORDs in the EXTLOGPEN structure. This structure is defined as:

    typedef struct tagEXTLOGPEN {
        DWORD     elpPenStyle;
        DWORD     elpWidth;
        UINT      elpBrushStyle;
        COLORREF  elpColor;
        ULONG_PTR elpHatch;
        DWORD     elpNumEntries;
        DWORD     elpStyleEntry[1];
    } EXTLOGPEN, *PEXTLOGPEN;
    

    The call to CPen::GetExtLogPen succeeds, if the elpStyleEntry array is at most one element long. This is true for all pens except for those pens that have the PS_USERSTYLE pen style. When using the PS_USERSTYLE pen style, the elpStyleEntry entry will have at least 2 entries.

    The workaround in the case of pens with the PS_USERSTYLE pen style is to use the Windows API GetObject call, and circumvent the MFC implementation:

    // Query for required buffer size
    int sizeRequired = ::GetObject(CustomPen.m_hObject, 0, nullptr);
    // Allocate buffer (may not be properly aligned)
    std::vector<byte> buffer(sizeRequired);
    // Retrieve the entire EXTLOGPEN structure
    int ret = ::GetObject(CustomPen.m_hObject, static_cast<int>(buffer.size()), buffer.data());
    assert(ret == static_cast<int>(buffer.size());
    // Cast to const ref for convenient access
    const EXTLOGPEN& elp = *reinterpret_cast<const EXTLOGPEN*>(buffer.data());
    

    Unfortunately, the solution posted by Mark Ransom doesn't solve the issue, because the CPen::GetExtLogPen passes sizeof(EXTLOGPEN) to the GetObject call, not the true size of its argument.


    Note: A bug report has been submitted to Microsoft Connect.