3

For a long time I tried to use the LVM_GETITEMW message with LVIF_TEXT mask to get the text of a ListView. My program worked in 32 bit but not in 64 bit architecture.

I discovered that the problem was at the LVITEM struct. Shortly, my question is which struct is the appropriate one for 64 bit and why.

The struct I used as the LVITEMW struct had the following fields:

('mask', c_uint32),
('iItem', c_int32),
('iSubItem', c_int32),
('state', c_uint32),
('stateMask', c_uint32),
('pszText', c_uint32),
('cchTextMax', c_int32),
('iImage', c_int32),
('lParam', c_uint64),
('iIndent', c_int32),
('iGroupId', c_int32),
('cColumns', c_uint32),
('puColumns', c_uint32),
('piColFmt', c_int32),
('iGroup', c_int32)

(Written with python 2.7 ctypes, but this is just a form of writing - the language is really irrelevant).

These fields are just as documented.

After a lot of googling, I found this forum which had exactly what I needed - the 64 bit solution!

So in 64 bit the struct should have more "spaces", and should look something like this (the pointers are now 64 bit and also the stateMask is 64 bit. That's a little bit different from what the forum suggested but works too):

('mask', c_uint32),
('iItem', c_int32),
('iSubItem', c_int32),
('state', c_uint32),
('stateMask', c_uint64), <-- Now 64 bit
('pszText', c_uint64), <-- Now 64 bit which makes sense since this is a pointer
('cchTextMax', c_int32),
('iImage', c_int32),
('lParam', c_uint64),
('iIndent', c_int32),
('iGroupId', c_int32),
('cColumns', c_uint32),
('puColumns', c_uint64), <-- Now 64 bit which makes sense since this is a pointer
('piColFmt', c_int64), <-- Now 64 bit which makes sense since this is a pointer
('iGroup', c_int32)

The forum suggested having:

('mask', c_uint32),
('iItem', c_int32),
('iSubItem', c_int32),
('state', c_uint32),
('stateMask', c_uint64),
('pszText', c_uint64),
('cchTextMax', c_int32),
('iImage', c_int64), <-- Now 64 bit
('lParam', c_uint32),
('iIndent', c_int32),
('iGroupId', c_int32),
('cColumns', c_uint32),
('puColumns', c_uint32),
('piColFmt', c_int32),
('iGroup', c_int64), <-- Now 128 bit all together
('iGroup2', c_int64) <-- continuation

Which also works, at list for my need which is the text pointed by pszText.

And my questions are:

  1. Is this documented anywhere?
  2. Why should the stateMask be c_uint64 - shouldn't it always be the same size as the state?
  3. Which one is the true struct for 64 bit?

Thank you!

Ella Sharakanski
  • 2,683
  • 3
  • 27
  • 47
  • Don't platform-spam. What does this have to do with C#? – itsme86 Jan 03 '15 at 01:06
  • @itsme86 Most of the examples there are on this are written in C# and I thought people who know C# may have the answer. Do you think I better remove the tag? – Ella Sharakanski Jan 03 '15 at 01:08
  • 1
    I'd probably remove the C# tag and add win32, since it's a win32 API question IMO. – itsme86 Jan 03 '15 at 01:10
  • Sure this is documented, the Windows SDK has the LVITEM structure declaration. The converter you need to go from the C declaration to the Python declaration is however one you need to have between your ears. – Hans Passant Jan 03 '15 at 01:32
  • @HansPassant Sure the conversion is easy. So you say I should download this? http://www.microsoft.com/en-us/download/details.aspx?id=8279 No online documentation? – Ella Sharakanski Jan 03 '15 at 01:40
  • 2
    The fields are as documented on the MSDN page you found. You need to translate the Win32 ABI into Python. The detail you are missing is that the Win32 ABI uses [8-byte packing](http://blogs.msdn.com/b/oldnewthing/archive/2009/04/22/9560726.aspx). – Raymond Chen Jan 03 '15 at 02:27
  • @RaymondChen Can you explain what does that mean for this specific case? (The stateMask) – Ella Sharakanski Jan 03 '15 at 02:34
  • I think I get it: In the 32 bit version of the struct, when used on a 64 bit OS, the pszText becomes 8 bytes and it starts on the address of 20 bytes which is not 8 bytes aligned. So there is a 4 bytes padding (so can be divided by 8). Also I was wrong at the 32 bit struct - the lParam is only 8 byte for 64 bit OS. On 32 bit OS it is a "long" which is 4 byte. Back to the 64 bit OS: The lParam starts on 36 bytes so again 4 bytes padding at iImage. puColumns starts on 60 but it now should be 8 bytes so 4 bytes padding at cColumns. The struct becomes 84 bytes so 4 bytes padding at the end. Right? – Ella Sharakanski Jan 03 '15 at 03:01
  • Oh never mind I counted wrong, see the answer below. – Ella Sharakanski Jan 03 '15 at 03:26
  • 1
    @RaymondChen Another question: Why does the documentation say that puColumns and piColFmt are UINT and INT although it also says they are pointers? (pszText is a pointer too and it is an LPTSTR which is a WCHAR * or a CHAR *) – Ella Sharakanski Jan 03 '15 at 04:02
  • 2
    That's a documentation error. I'll see what I can do to have it fixed. – Raymond Chen Jan 03 '15 at 15:35

1 Answers1

5

Thanks to the comment by Raymond Chen I was able to figure out the answer!

It's about data alignment. Every pointer should be 8 bytes and should also be aligned to an address that can be divided by 8, so sometimes there should be a padding before the pointer. Also, the size of the struct should be dividable by min(max(sizeof(each field in the struct)), 8) which is 8 in our case since the size of a pointer is 8.

class LVITEMW_explicit(ctypes.Structure):
    _pack_ = 1
    _fields_ = [
        ('mask', c_uint32),         # 0
        ('iItem', c_int32),         # 4
        ('iSubItem', c_int32),      # 8
        ('state', c_uint32),        # 12
        ('stateMask', c_uint32),    # 16
        ('padding1', c_int),
        ('pszText', c_uint64),      # 20 --> 24 after padding (A pointer)
        ('cchTextMax', c_int32),    # 32
        ('iImage', c_int32),        # 36
        ('lParam', c_uint64),       # 40 (On 32 bit should be c_long which is 32 bits)
        ('iIndent', c_int32),       # 48
        ('iGroupId', c_int32),      # 52
        ('cColumns', c_uint32),     # 56
        ('padding2', c_int),
        ('puColumns', c_uint64),    # 60 --> 64 after padding (A pointer)
        ('piColFmt', c_int64),      # 72 (A pointer)
        ('iGroup', c_int32),        # 80
        ('padding3', c_int32),      # The total length was 84 before padding3 was added, which is not dividable by 8
    ]

Or as this should really be written - without the _pack_ = 1:

class LVITEMW(ctypes.Structure):
    _fields_ = [
        ('mask', c_uint32),
        ('iItem', c_int32),
        ('iSubItem', c_int32),
        ('state', c_uint32),
        ('stateMask', c_uint32),
        ('pszText', c_uint64),
        ('cchTextMax', c_int32),
        ('iImage', c_int32),
        ('lParam', c_uint64), # On 32 bit should be c_long
        ('iIndent', c_int32),
        ('iGroupId', c_int32),
        ('cColumns', c_uint32),
        ('puColumns', c_uint64),
        ('piColFmt', c_int64),
        ('iGroup', c_int32),
    ]

And indeed ctypes.sizeof(LVITEMW) returns 88, same as ctypes.sizeof(LVITEMW_explicit).

Thanks again for the helpful comments!

Ella Sharakanski
  • 2,683
  • 3
  • 27
  • 47