Currently, TDialogRes::GetText fails for extended dialog templates (DLGTEMPLATEEX), since in this case the implementation is missing, and consequently, pointers are left uninitialised:
int TDialogRes::GetText(LPTSTR buffer, int size, TDlgResText which) const
{
LPCWSTR p, pMenu, pClass, pCaption;
if (!IsDialogEx()) {
/*...Initialise pMenu, pClass and pCaption...*/
}
//else{ Y.B. Finish ??????????????????????????????????????????????????????
// DLGTEMPLATEEX* dlgTemplateEx = GetTemplateEx();
// !BB Get info about DLGEX!
//}
switch (which) {
case drtMenuName: p = pMenu; break;
case drtClassName: p = pClass; break;
case drtCaption: p = pCaption; break;
default:
return 0;
};
/*...Use pointer p...*/
}
A series of posts about dialog templates from Raymon Chen's blog:
The evolution of dialog templates – Introduction
The evolution of dialog templates – 16-bit Classic Templates
The evolution of dialog templates – 32-bit Classic Templates
The evolution of dialog templates – 16-bit Extended Templates
The evolution of dialog templates – 32-bit Extended Templates
The evolution of dialog templates – Summary
After doing some research, the code in TDialogRes seems to be complete nonsense.
There is no such thing as RT_NEWDIALOG. I don't know where Borland got that idea.
Instead, both old and new dialog templates are loaded using RT_DIALOG , and the way to distinguish them is by checking the first four bytes of the template. See Raymond Chen's posts.
Also, the definition of DLGTEMPLATEEX was not using the correct alignment and ended being the wrong size.
Code fixed in [r8649].
Related
Commit: [r8649]
[r8650] added an example to the Classes project to test TDialogRes with both old and new dialog templates.
Related
Commit: [r8650]
@jogybl wrote:
Files are missing ("dialogrestest.(h|cpp)").
Thanks, pushed them.
@jogybl wrote:
Good work on the rewrite and the test!
The GetText function is now replaced with a GetDialogData function that returns a struct with the parsed members of the dialog template.
Hi Ognyan,
Good work on fixing this! This kind of low-level binary parsing, with potential alignment issues and pointer arithmetic corner cases, is tricky to get right, so good of you to add a test case in Classes — which seems to work nicely!
Have you had your code reviewed by AI?
I'm trying not to get involved on the trunk, so I just asked Windows Copilot (Smart) to have a look at your InitDialogData, and it complained about a few issues with alignment, type casts and pointer arithmetic in places. I didn't get into the weeds, but noted that it made a false statement at least in one place, so the review clearly wasn't fully reliable. But Copilot suggested generating some synthetic test cases that exercise the tricky corner cases, which is a good idea. The currently tested dialogs may be deceptively regular.
I tried to use AI to generate the code for this, and some of the work was done by it, but I had to make a number of corrections, including I think omitting the star in statements like
(ushort)*(pControlClass + 1).Yeah, I am not sure the code would be handling correctly the case in which controls have extra bytes. Will need more research when I get back to this.
@jogybl wrote:
Even if you use AI to generate code, having AI review the code in a fresh prompt can be useful. Maybe use a different AI for the review, and use the smartest AI you have access to (in thinking mode, so that it thinks before it speaks).
Here's the code in "InitDialogData" I couldn't unsee:
This baffled me and Copilot alike, and it took some time before I could convince myself (and Copilot) that it works.
This code does not do absolute memory address alignment, but instead aligns the offset from p, which in turn is presumed to be at a WORD aligned but DWORD unaligned offset from the base address of the dialog template. The presumption that the offset from base to p is DWORD unaligned relies on sizeof(DLGTEMPLATE) and sizeof(DLGTEMPLATEEX) both being an odd multiple of sizeof(WORD), just by coincidence.
If keeping the code as is, consider asserting these presumptions in the code:
Or simplify by checking the offset from the base instead:
Should you align the offset or the absolute memory address? If the base address is always DWORD aligned it doesn't matter, but absolute memory address alignment is more conventional and perhaps easier to understand. However, if you want to allow the template to be located at an unaligned address, then offset alignment will ensure the code still works.
Note that the Windows API (CreateDialogIndirectParam) requires that the template is DWORD aligned:
Hence my recommendation would be to just assert this requirement with a PRECONDITION and use conventional absolute memory address alignment.
Here are some other good suggestions by Copilot:
Good call. Due to the LPCWSTR type of the pointer here, the number of bytes skipped is always even (rounded down), and the last extra byte is not skipped if the count is odd (unless it is coincidentally skipped by the subsequent alignment).
Here is a code rewrite that circumvents this problem, while simplifying things, adding assertions and preventing buffer overflow:
Here is the implementation of the local helpers:
And here are the global helpers:
Last edit: Vidar Hasfjord 2 days ago
Thanks. I have applied and tested the changes in [r8746].
Related
Commit: [r8746]
@jogybl wrote:
Good to hear it passed testing! I made a minor refactoring of ConvertToString_, separating the control class lookup into a new function ConvertToControlClass_, just for better separation of concerns, but this is not that important here.
Regarding [r8747], there is an issue in your change of the test for the font fields:
Since DS_SHELLFONT contains multiple flags (DS_SETFONT | DS_FIXEDSYS), this does not do what you intend; it will evalute to true even if just DS_FIXEDSYS is set. You can change it to
(DialogData.Style & DS_SETFONT) || (DialogData.Style & DS_SHELLFONT) == DS_SHELLFONTto fix this, but it just ends up being redundant. Your original code testing only the DS_SETFONT flag is sufficient.Related
Commit: [r8746]
Commit: [r8747]
Another possible issue in [r8747] is this test:
This means that e.g. with the parsed BYTE equal to 0xFF (all bits set), the expression evaluates to false, since the BYTE is not equal to TRUE (0x01). This is usually fixed by always writing
!= FALSEinstead. But this one is very easy to forget, as I can attest to myself!Related
Commit: [r8747]