views:

138

answers:

4

I just read this question and this question, and since then I have been trying to call SHGetSetSettings in Delphi. This is a function of shell32.dll, but is not defined in ShlObj.pas, so we need to write our own definition.

First we need to translate the SHELLSTATE structure. Now I have only limited experience in C, but I suppose that ": 1" means that the member of the structure is a single bit, that is, that eight of them can be packed together in a byte. I also suppose that DWORD = UINT = 32-bit unsigned integers and that LONG = int are 32-bit signed integers. But then we have a problem: The entire structure will then occupy 228 bits, or 28.5 bytes, which is ... rather impossible, at least in Delphi, where sizeof(SomeRecord) has to be an integer.

Nevertheless, I tried to solve it by adding four dummy bits at the end. 232 bits = 29 bytes, which is nice.

Hence I tried

PShellState = ^TShellState;
TShellState = packed record
  Data1: cardinal;
  Data2: cardinal;
  Data3: cardinal;
  Data4: cardinal;
  Data5: cardinal;
  Data6: cardinal;
  Data7: cardinal;
  Data8: byte; // Actually a nibble would be sufficient
end;

and then I declared (for later convenience)

const
  fShowAllObjects = 1;
  fShowExtensions = 2;
  fNoConfirmRecycle = 4;
  fShowSysFiles = 8;
  fShowCompColor = 16;
  fDoubleClickInWebView = 32;
  fDesktopHTML = 64;
  fWin95Classic = 128;
  fDontPrettyPath = 256;
  fShowAttribCol = 512;
  fMapNetDrvButton = 1024;
  fShowInfoTip = 2048;
  fHideIcons = 4096;
  fWebView = 8192;
  fFilter = 16384;
  fShowSuperHidden = 32768;
  fNoNetCrawling = 65536;

Now I felt ready to define

interface
  procedure SHGetSetSettings(var ShellState: TShellState; Mask: cardinal; DoSet: boolean); stdcall;

implementation
  procedure SHGetSetSettings; external shell32 name 'SHGetSetSettings';

But before I tried the code, I noticed something very strange. I found that the constants I declared were already declared here: SSF Constants. Notice that SSF_HIDEICONS = 0x00004000 = 16384 ≠ fHideIcons = 4096. If the SSF_ constants really are masks used together with SHELLSTATE, then it makes no sense to define SSF_HIDEICONS as 2^14 when it is the 13th bit (and its mask should be 2^12) in the structure. Hence, it seems, the two MSDN reference pages contradict eachother.

Could someone please bring some clarity into all this?

+3  A: 

My reading of the help here is that the SSF_ constants are specified for the mask when retrieving data. There's no reason they have to map to the bits in the ShellState structure.

If they did fShowSysFiles would map to 8 (0x04), and we know from the help that SSF_SHOWSYSFILES is 0x20. There's no direct mapping.

Bob Moore
Yes, of course they do not *have* to map to the bits of the strucuture. But it seemed so reasonable at the time!
Andreas Rejbrand
But what about the other issue: Is it possible for a C structure not to have an integral number of bytes as its size?
Andreas Rejbrand
No. Looking at the declaration, the bit fields are specified as being within a BOOL, so the underlying type is int. I make the size of that struct 36 bytes, at a glance. Of course, struct packing can alter the size, but since everything here (int, UINT, long, DWORD) is 4bytes on a 32 bit OS, I think the estimate is _reasonably_ safe. Of course some language lawyer will leap in here and prove me wrong :-). Watch out if you're working with a 64 bit compiler though... that value will change.
Bob Moore
@Bob Moore: So, my interpretation of the structure would be correct if I just add a few dummy bits to "complete the BOOLS" when necessary?
Andreas Rejbrand
Yep. I don't use Delphi that often, but I believe a packed record will only ever be an integral number of bytes anyway. You just have to ensure that you use enough space to start the dwWin95Unused on a four byte boundary.
Bob Moore
A: 

Afaik bitfields in C are a subtype of integer. There are ways to pack it, but also in C, after a bunch of single bit fields, there will be padding to the next byte boundery (and probably even to the next integer boundery). Moreover C's sizeof doesn't support halves either.

So probably it is 1+6+1 times sizeof(integer)= 32bytes.

Marco van de Voort
+1  A: 

Here is TShellState definition in Delphi 2010:

type
  tagSHELLSTATEW = record 
    Data: DWORD;
    Data2: UINT;
{   fShowAllObjects: BOOL:1;
    fShowExtensions: BOOL:1;
    fNoConfirmRecycle: BOOL:1;
    fShowSysFiles: BOOL:1;
    fShowCompColor: BOOL:1;
    fDoubleClickInWebView: BOOL:1;
    fDesktopHTML: BOOL:1;
    fWin95Classic: BOOL:1;
    fDontPrettyPath: BOOL:1;
    fShowAttribCol: BOOL:1;
    fMapNetDrvBtn: BOOL:1;
    fShowInfoTip: BOOL:1;
    fHideIcons: BOOL:1;
    fWebView: BOOL:1;
    fFilter: BOOL:1;
    fShowSuperHidden: BOOL:1;
    fNoNetCrawling: BOOL:1;}

    //dwWin95Unused: DWORD;// Win95 only - no longer supported pszHiddenFileExts
    //uWin95Unused: UINT; // Win95 only - no longer supported cbHiddenFileExts

    // Note: Not a typo!  This is a persisted structure so we cannot use LPARAM
    lParamSort: Integer;
    iSortDirection: Integer;
    version: UINT;

    // new for win2k. need notUsed var to calc the right size of ie4 struct
    // FIELD_OFFSET does not work on bit fields
    uNotUsed: UINT;// feel free to rename and use
{   fSepProcess: BOOL:1;

    // new for Whistler.
    fStartPanelOn: BOOL:1;
    fShowStartPage: BOOL:1;

    // new for Windows Vista
    fAutoCheckSelect: BOOL:1;
    fIconsOnly: BOOL:1;
    fShowTypeOverlay: BOOL:1;

    // If you need a new flag, steal a bit from from fSpareFlags.
    // Also, keep SHELLFLAGSTATE and SHGetSettings in sync when adding new flags.
    fSpareFlags: UINT:11;
}

  end;
  {$EXTERNALSYM tagSHELLSTATEW}
  SHELLSTATEA = tagSHELLSTATEW;
  {$EXTERNALSYM SHELLSTATEA}
  SHELLSTATEW = tagSHELLSTATEW;
  {$EXTERNALSYM SHELLSTATEW}
  SHELLSTATE = SHELLSTATEW;
  {$EXTERNALSYM SHELLSTATE}
  TShellState = SHELLSTATE;
  PShellState = ^TShellState;

const
  SHELLSTATEVERSION_IE4 = 9; 
  {$EXTERNALSYM SHELLSTATEVERSION_IE4}
  SHELLSTATEVERSION_WIN2K = 10; 
  {$EXTERNALSYM SHELLSTATEVERSION_WIN2K}

Not very helpful, unfortunately.

Alexander
Well, it is actually *very* helpful. Among other things, this says that all BOOL:1's occupies eight bytes together. Kind of odd, though, because 17 bits can fit in three bytes, so one cardinal would have been enought. Btw: I assume that a UINT is a bit unsigned integer, that is, a cardinal, as DWORD is. But why are two different "aliases" used? Hm... I seem to get 24 bytes only...
Andreas Rejbrand
+2  A: 

The D2010 declaration of SHELLSTATE in ShlObj.pas is unfortunately incorrect, but the first group of bits (17) is correctly matched with Data: DWORD; (Yours is indeed OK). There could be 18 or 19 of them all the same. Then we should get 2 more DWORD/UINT for the 2 Win95unused, not only Data2.

Too bad because the SSF flags and SHGetSetSettings declarations are already done in there and correct.

The correct declaration according to MSDN should be IMO:

  tagSHELLSTATEW = record
    Data: DWORD;
{   fShowAllObjects: BOOL:1;
    fShowExtensions: BOOL:1;
    fNoConfirmRecycle: BOOL:1;
    fShowSysFiles: BOOL:1;
    fShowCompColor: BOOL:1;
    fDoubleClickInWebView: BOOL:1;
    fDesktopHTML: BOOL:1;
    fWin95Classic: BOOL:1;
    fDontPrettyPath: BOOL:1;
    fShowAttribCol: BOOL:1;
    fMapNetDrvBtn: BOOL:1;
    fShowInfoTip: BOOL:1;
    fHideIcons: BOOL:1;
    fWebView: BOOL:1;
    fFilter: BOOL:1;
    fShowSuperHidden: BOOL:1;
    fNoNetCrawling: BOOL:1;}

    dwWin95Unused: DWORD;// Win95 only - no longer supported pszHiddenFileExts
    uWin95Unused: UINT; // Win95 only - no longer supported cbHiddenFileExts

    // Note: Not a typo!  This is a persisted structure so we cannot use LPARAM
    lParamSort: Integer;
    iSortDirection: Integer;
    version: UINT;

    // new for win2k. need notUsed var to calc the right size of ie4 struct
    // FIELD_OFFSET does not work on bit fields
    uNotUsed: UINT;// feel free to rename and use}

    Data2: DWORD;
{   fSepProcess: BOOL:1;

    // new for Whistler.
    fStartPanelOn: BOOL:1;
    fShowStartPage: BOOL:1;

    // new for Windows Vista
    fAutoCheckSelect: BOOL:1;
    fIconsOnly: BOOL:1;
    fShowTypeOverlay: BOOL:1;

    // If you need a new flag, steal a bit from from fSpareFlags.
    // Also, keep SHELLFLAGSTATE and SHGetSettings in sync when adding new flags.
    fSpareFlag: UINT:13;}
  end;

You can verify that this allows to get the sorting properties correctly with:

var
  lpss: tagSHELLSTATEW;
begin
  ZeroMemory(@lpss, SizeOf(lpss));
  SHGetSetSettings(lpss, SSF_SORTCOLUMNS, False);
François
Yes, now we got the right size of it.
Andreas Rejbrand