{"id":105961,"date":"2021-11-24T07:00:00","date_gmt":"2021-11-24T15:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=105961"},"modified":"2021-11-24T06:52:55","modified_gmt":"2021-11-24T14:52:55","slug":"20211124-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20211124-00\/?p=105961","title":{"rendered":"Appending additional payload to a <CODE>PROPSHEETPAGE<\/CODE> structure"},"content":{"rendered":"<p>A not-well-known feature of the common controls property sheet is that you can append your own custom data to the end of the <code>PROPSHEETPAGE<\/code> structure, and the system will carry it around for you.<\/p>\n<p>The traditional way of setting up a <code>PROPSHEETPAGE<\/code> is to use the <code>lParam<\/code> member to point to a structure containing additional data that is used by the property sheet:<\/p>\n<pre>struct WidgetNameData\r\n{\r\n    HWIDGET widget;\r\n    bool uppercaseOnly;\r\n    int renameCount;\r\n};\r\n\r\nvoid ShowWidgetProperties(HWIDGET widget, HWND hwndOwner)\r\n{\r\n    WidgetNameData nameData;\r\n    nameData.widget = widget;\r\n    nameData.uppercaseOnly = IsPolicyEnabled(Policy::UppercaseNames);\r\n    nameData.renameCount = 0;\r\n\r\n    PROPSHEETPAGE pages[1] = {};\r\n\r\n    pages[0].dwSize = sizeof(pages[0]);\r\n    pages[0].hInstance = g_hinstThisDll;\r\n    pages[0].pszTemplate = MAKEINTRESOURCE(IDD_WIDGETNAMEPROP);\r\n    pages[0].pfnDlgProc = WidgetNameDlgProc;\r\n    pages[0].lParam = (LPARAM)&amp;nameData;\r\n\r\n    PROPSHEETHEADER psh = { sizeof(psh) };\r\n    psh.dwFlags = PSH_WIZARD | PSH_PROPSHEETPAGE;\r\n    psh.hInstance = g_hinstThisDll;\r\n    psh.hwndParent = hwndOwner;\r\n    psh.pszCaption = MAKEINTRESOURCE(IDS_WIDGETPROPTITLE);\r\n    psh.nPages = ARRAYSIZE(pages);\r\n    psh.ppsp = pages;\r\n    PropertySheet(&amp;psh);\r\n}\r\n<\/pre>\n<p>For simplicity, this property sheet has only one page. This page needs a <code>WidgetData<\/code> worth of extra state, so we allocate that state (on the stack, in this case) and put a pointer to int in the <code>PROPSHEETPAGE<\/code>&#8216;s <code>lParam<\/code> for the dialog procedure to fish out:<\/p>\n<pre>INT_PTR CALLBACK WidgetNameDlgProc(\r\n    HWND hdlg, UINT uMsg, WPARAM wParam, LPARAM lParam)\r\n{\r\n    auto pageData = (WidgetNameData*)GetWindowLongPtr(\r\n        hdlg, DWLP_USER);\r\n    switch (uMsg)\r\n    {\r\n    case WM_INITDIALOG:\r\n        {\r\n            auto page = (PROPSHEETPAGE*)lParam;\r\n            pageData = (WidgetNameData*)page-&gt;lParam;\r\n            SetWindowLongPtr(hdlg, DWLP_USER, (LONG_PTR)pageData);\r\n\r\n            ... initialize the page ...\r\n            return TRUE;\r\n        }\r\n\r\n    ... other message handlers ...\r\n    }\r\n    return FALSE;\r\n}\r\n<\/pre>\n<p>For a property sheet dialog procedure, the <code>lParam<\/code> of the <code>WM_<wbr \/>INIT\u00adDIALOG<\/code> points to a <code>PROPSHEETPAGE<\/code> structure, and you can pull out the <code>lParam<\/code> to access your private data.<\/p>\n<p>Now, this gets kind of complicated if the property sheet page was created via <code>Create\u00adProp\u00adSheet\u00adPage<\/code>, say, because it is a plug-in that is added dynamically into an existing widget property sheet.<\/p>\n<pre>HPROPSHEETPAGE CreateWidgetNamePage(HWIDGET widget)\r\n{\r\n    PROPSHEETPAGE page = {};\r\n\r\n    page.dwSize = sizeof(page);\r\n    page.hInstance = g_hinstThisDll;\r\n    page.pszTemplate = MAKEINTRESOURCE(IDD_WIDGETNAMEPROP);\r\n    page.pfnDlgProc = WidgetNameDlgProc;\r\n    page.lParam = (LPARAM)&amp;(what goes here?);\r\n\r\n    return CreatePropertySheetPage(&amp;page);\r\n}\r\n<\/pre>\n<p>You can&#8217;t use a stack-allocated <code>Widget\u00adName\u00adData<\/code> because that will disappear once the <code>Create\u00adWidget\u00adName\u00adPage<\/code> function returns. You probably have to create a separate heap allocation for it, and pass a pointer to the heap allocation as the <code>lParam<\/code>, but now you also need to add a property sheet callback so you can remember to free the data when you get a <code>PSPCB_<wbr \/>RELEASE<\/code> callback.<\/p>\n<p><b>Exercise<\/b>: Why is <code>WM_<wbr \/>DESTROY<\/code> the wrong place to free the data? (Answer below.)<\/p>\n<p>To avoid this extra hassle, you can use this one weird trick: Append your private data to the <code>PROPSHEETPAGE<\/code>, and the system will carry it around for you.<\/p>\n<pre>struct WidgetNameData <span style=\"color: blue;\">: PROPSHEETPAGE<\/span>\r\n{\r\n    HWIDGET widget;\r\n    bool uppercaseOnly;\r\n    int renameCount;\r\n};\r\n\r\nHPROPSHEETPAGE CreateWidgetNamePage(HWIDGET widget)\r\n{\r\n    <span style=\"color: blue;\">WidgetNameData<\/span> page = {};\r\n\r\n    page.dwSize = sizeof(page);\r\n    page.hInstance = g_hinstThisDll;\r\n    page.pszTemplate = MAKEINTRESOURCE(IDD_WIDGETNAMEPROP);\r\n    page.pfnDlgProc = WidgetNameDlgProc;\r\n\r\n    <span style=\"color: blue;\">\/\/ store the extra data in the extended page\r\n    page.widget = widget;\r\n    page.uppercaseOnly = IsWidgetPolicyEnabled(WidgetPolicy::UppercaseNames);\r\n    page.renameCount = 0;<\/span>\r\n\r\n    return CreatePropertySheetPage(&amp;page);\r\n}\r\n<\/pre>\n<p>We append the data to the <code>PROPSHEETPAGE<\/code> by deriving from <code>PROPSHEETPAGE<\/code> and adding our extra data to it. The trick is that by setting the <code>page.dwSize<\/code> to the size of the entire larger structure, we tell the property sheet manager that our private data is part of the page. When the property sheet manager creates a page, it copies all of the bytes described by the <code>dwSize<\/code> member into its private storage (referenced by the returned <code>HPROPSHEETPAGE<\/code>), and by increasing the value of <code>page.dwSize<\/code>, we get our data copied there too.<\/p>\n<p>Recall that the <code>lParam<\/code> parameter to the <code>WM_<wbr \/>INIT\u00adDIALOG<\/code> message is a pointer to a <code>PROPSHEETPAGE<\/code>. In our case, it&#8217;s a pointer to our custom <code>PROPSHEETPAGE<\/code> structure, and we can downcast to the specific type to access the extra data.<\/p>\n<pre>INT_PTR CALLBACK WidgetNameDlgProc(\r\n    HWND hdlg, UINT uMsg, WPARAM wParam, LPARAM lParam)\r\n{\r\n    auto pageData = (WidgetNameData*)GetWindowLongPtr(\r\n        hdlg, DWLP_USER);\r\n    switch (uMsg)\r\n    {\r\n    case WM_INITDIALOG:\r\n        {\r\n            <span style=\"color: blue;\">\/\/ the lParam points to our extended page\r\n            pageData = (WidgetNameData*)lParam;<\/span>\r\n            SetWindowLongPtr(hdlg, DWLP_USER, (LONG_PTR)pageData);\r\n\r\n            ... initialize the page ...\r\n            return TRUE;\r\n        }\r\n\r\n    ... other message handlers ...\r\n    }\r\n    return FALSE;\r\n}\r\n<\/pre>\n<p>In pictures: The traditional way creates a separate allocation that the <code>PROPSHEETPAGE<\/code>&#8216;s <code>lParam<\/code> points to. When the system copies the <code>PROPSHEETPAGE<\/code> into private storage, the <code>lParam<\/code> is copied with it.<\/p>\n<p><!-- \"margin-left: 1px\" to work around devblogs.microsoft.com default style sheet --><\/p>\n<table style=\"border-collapse: collapse; margin-left: 1px;\" border=\"0\" cellspacing=\"0\" cellpadding=\"0\">\n<tbody>\n<tr>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td style=\"padding: 3px;\" colspan=\"2\" nowrap=\"nowrap\">passed to <code>Create-<\/code><br \/>\n<code>Prop\u00adSheet\u00adPage<\/code><\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td style=\"padding: 3px;\" colspan=\"2\" nowrap=\"nowrap\">copy passed to<br \/>\n<code>WM_INITDIALOG<\/code><\/td>\n<\/tr>\n<tr>\n<td style=\"border: solid 1px black; border-right: none; vertical-align: middle; width: 1em;\" rowspan=\"3\"><span style=\"writing-mode: vertical-lr; -ms-writing-mode: tb-lr; transform: rotate(180deg);\"><code>dwSize<\/code><\/span><\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: solid 1px black; border-right: none; vertical-align: middle; width: 1em;\" rowspan=\"3\"><span style=\"writing-mode: vertical-lr; -ms-writing-mode: tb-lr; transform: rotate(180deg);\"><code>PROPSHEETPAGE<\/code><\/span><\/td>\n<td style=\"border: solid 1px black; border-bottom: none; padding: 3px 3px 0 3px;\"><code>dwSize<\/code><br \/>\n<code>dwFlags<\/code><br \/>\n\u22ee<\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: solid 1px black; border-right: none; vertical-align: middle; width: 1em;\" rowspan=\"3\"><span style=\"writing-mode: vertical-lr; -ms-writing-mode: tb-lr; transform: rotate(180deg);\"><code>PROPSHEETPAGE<\/code><\/span><\/td>\n<td style=\"border: solid 1px black; border-bottom: none; padding: 3px 3px 0 3px;\"><code>dwSize<\/code><br \/>\n<code>dwFlags<\/code><br \/>\n\u22ee<\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: solid 1px black; border-left: none; vertical-align: middle; width: 1em;\" rowspan=\"3\"><span style=\"writing-mode: vertical-lr; -ms-writing-mode: tb-lr; transform: rotate(180deg);\"><code>dwSize<\/code><\/span><\/td>\n<\/tr>\n<tr>\n<td>&nbsp;<\/td>\n<td style=\"border: 1px black; border-style: none solid; padding: 0 3px;\"><code>lParam<\/code><\/td>\n<td>\u2192<\/td>\n<td style=\"border: solid 1px black; border-bottom: none; padding: 3px 3px 0 3px;\"><code>widget<\/code><\/td>\n<td>\u2190<\/td>\n<td style=\"border: 1px black; border-style: none solid; padding: 0 3px;\"><code>lParam<\/code><\/td>\n<\/tr>\n<tr>\n<td>&nbsp;<\/td>\n<td style=\"border: solid 1px black; border-top: none; padding: 0 3px 3px 3px;\">\u22ee<\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: 1px black; border-style: none solid; padding: 0 3px;\"><code>uppercaseOnly<\/code><\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: solid 1px black; border-top: none; padding: 0 3px 3px 3px;\">\u22ee<\/td>\n<\/tr>\n<tr>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: solid 1px black; border-top: none; padding: 0 3px 3px 3px;\"><code>renameCount<\/code><\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>The new way makes the whole thing a giant <code>Widget\u00adName\u00adData<\/code> with a <code>PROPSHEETPAGE<\/code> at the top and bonus data at the bottom.<\/p>\n<table style=\"border-collapse: collapse; margin-left: 1px;\" border=\"0\" cellspacing=\"0\" cellpadding=\"0\">\n<tbody>\n<tr>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td style=\"padding: 3px;\" colspan=\"2\" nowrap=\"nowrap\">passed to <code>Create-<\/code><br \/>\n<code>Prop\u00adSheet\u00adPage<\/code><\/td>\n<td style=\"width: 2em;\">\u00a0<\/td>\n<td style=\"padding: 3px;\" colspan=\"2\" nowrap=\"nowrap\">copy passed to<br \/>\n<code>WM_INITDIALOG<\/code><\/td>\n<\/tr>\n<tr>\n<td style=\"border: solid 1px black; border-right: none; vertical-align: middle; width: 1em;\" rowspan=\"2\"><span style=\"writing-mode: vertical-lr; -ms-writing-mode: tb-lr; transform: rotate(180deg);\"><code>dwSize<\/code><\/span><\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: solid 1px black; border-right: none; vertical-align: middle; width: 1em;\"><span style=\"writing-mode: vertical-lr; -ms-writing-mode: tb-lr; transform: rotate(180deg);\"><code>PROPSHEETPAGE<\/code><\/span><\/td>\n<td style=\"border: solid 1px black; padding: 3px;\"><code>dwSize<\/code><br \/>\n<code>dwFlags<\/code><br \/>\n\u22ee<br \/>\n<code>lParam<\/code><br \/>\n\u22ee<\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: solid 1px black; border-right: none; vertical-align: middle; width: 1em;\"><span style=\"writing-mode: vertical-lr; -ms-writing-mode: tb-lr; transform: rotate(180deg);\"><code>PROPSHEETPAGE<\/code><\/span><\/td>\n<td style=\"border: solid 1px black; padding: 3px;\"><code>dwSize<\/code><br \/>\n<code>dwFlags<\/code><br \/>\n\u22ee<br \/>\n<code>lParam<\/code><br \/>\n\u22ee<\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: solid 1px black; border-left: none; vertical-align: middle; width: 1em;\" rowspan=\"2\"><span style=\"writing-mode: vertical-lr; -ms-writing-mode: tb-lr; transform: rotate(180deg);\"><code>dwSize<\/code><\/span><\/td>\n<\/tr>\n<tr>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: solid 1px black; padding: 3px;\"><code>widget<\/code><br \/>\n<code>uppercaseOnly<\/code><br \/>\n<code>renameCount<\/code><\/td>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: solid 1px black; padding: 3px;\"><code>widget<\/code><br \/>\n<code>uppercaseOnly<\/code><br \/>\n<code>renameCount<\/code><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>The catch here is that the bonus data is copied to the internal storage via <code>memcpy<\/code>, so it must be something like a POD type which can be safely copied byte-by-byte.<\/p>\n<p>If you didn&#8217;t know about this trick, you would wonder why the <code>lParam<\/code> of the <code>WM_<wbr \/>INIT\u00adDIALOG<\/code> message points to the full <code>PROPSHEETPAGE<\/code>. After all, without this trick, the only thing you could do with the <code>PROPSHEETPAGE<\/code> pointer was access the <code>lParam<\/code>; all the other fields are explicitly documented as off-limits\u00b9 during the handling of the <code>WM_<wbr \/>INIT\u00adDIALOG<\/code> message. Why bother giving you a pointer to a structure where you&#8217;re allowed to access only one member? Why not just pass that one member?<\/p>\n<p>And now you know why: Because you actually can access more than just the <code>lParam<\/code>. If you hung extra data off the end of the <code>PROPSHEETPAGE<\/code>, then that data is there for you too.<\/p>\n<p>Extending the <code>PROPSHEETPAGE<\/code> structure means that each <code>PROPSHEETPAGE<\/code> can be a different size, which makes it tricky to pass an array of them, which is something you need to do if you&#8217;re using the <code>PSH_<wbr \/>PROP\u00adSHEET\u00adPAGE<\/code> flag. We&#8217;ll look at that problem next time.<\/p>\n<p><b>Bonus chatter<\/b>: This is an expansion of <a title=\"You can extend the PROPSHEETPAGE structure with your own bonus data\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20110318-00\/?p=11183\"> a previous discussion of the same topic<\/a>. In that earlier topic, I said that the array technique requires all of the elements to be the same size. But next time, I&#8217;ll show how to create an array of heterogeneous types.<\/p>\n<p><b>Bonus bonus chatter<\/b>: This is similar to, but not the same as, the trick of <a title=\"The OVERLAPPED associated with asynchronous I\/O is passed by address, and you can take advantage of that\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20101217-00\/?p=11983\"> adding extra information to the end of the <code>OVERLAPPED<\/code> structure<\/a>. In the case of overlapped I\/O, the same <code>OVERLAPPED<\/code> pointer is used; there is no copying. You can therefore put arbitrary complex data after the <code>OVERLAPPED<\/code>; they don&#8217;t have to be POD types. Of course, you have to be careful to destruct them properly when the I\/O is complete.<\/p>\n<p>In the case of a <code>PROPSHEETPAGE<\/code>, the memory is copied, so the data needs to be <code>memcpy<\/code>-safe. You could still use it to hold non-POD types, though, by treating it as uninitialized memory that the system conveniently preallocates for you. You&#8217;ll have to placement-construct the objects in their copied location, and manually destruct them when the property sheet page is destroyed.<\/p>\n<p><b>Answer to exercise<\/b>: If the property sheet page is never created (because the user never clicks on the tab for the page), then the dialog is never created, and therefore is never destroyed either. In that case, you don&#8217;t get any <code>WM_<wbr \/>DESTROY<\/code> message, and the memory ends up leaked.<\/p>\n<p>\u00b9 The documentation cheats a bit and says that you cannot modify anything except for the <code>lParam<\/code>, when what it&#8217;s really saying is that you cannot modify any <i>system-defined<\/i> things except for the <code>lParam<\/code>. Your private things are yours, and you can modify them at will.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Taking it along for the ride.<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-105961","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Taking it along for the ride.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/105961","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=105961"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/105961\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=105961"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=105961"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=105961"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}