MFC и майонез
Совсем недавно я нашел ответы на два вопроса, мучавшие меня с детства.
1. WindowProc одна для всех окон каждого [оконного класса], но обработку событий хотелось бы производить в экземплярах класса окна. Для этого надо по hWnd окна уметь определять указатель на объект, который его создал. Но несколько важных событий (WM_CREATE, WM_NCCREATE, WM_GETMINMAXINFO) приходят до того, как происходит возврат из CreateWindowEx в метод объекта и возвращение ею hWnd. Поэтому обычно this передают как последний параметр CreateWindowEx, а в WM_CREATE и WM_NCCREATE он приходит в lParam в структуре CREATESTRUCT, после чего его связывают с hWnd с помощью SetWindowLong с параметром GWL_USERDATA и радуются. Но! WM_GETMINMAXINFO приходит еще раньше, чем WM_NCCREATE, и кому его пересылать, совершенно непонятно.
Поэтому я залез в сырцы MFC и обнаружил, что хитрые мелкомягкие программеры героически обошли созданные ими грабли. Они сохраняют указатель на объект, создающий в текущем треде окно, в объекте класса _AFX_THREAD_STATE, хранящем данные в TLS, свои для каждого треда. Кроме этого они устанавливают оконный хук SetWindowsHookEx с параметром WH_CBT, которому приходит сообщение HCBT_CREATEWND с hWnd создаваемого окна в wParam (а в lParam лежит CBT_CREATEWND, хранящий указатель на CREATESTRUCT) раньше, чем любое событие достигает окна. В нем указатель через TLS извлекается, и ему сообщают хандл его окна (а обратную связь hWnd → указатель MFC хранит в своем собственном БШ-hash_map).
Таким образом, я узнал даже несколько ответов. Во-первых, можно хранить указатель в TLS и извлекать его, если вдруг в статической WindowProc GWL_USERDATA будет содержать 0. Во-вторых, можно помещать this в CREATESTRUCT и в хуке его доставать (впрочем, если программа должна работать под Win9x, без TLS или аналога все же не обойтись — CallNextHookEx в них требует HHOOK, который был возвращен перед вызовом CreateWindowEx, т.е. в объекте). В-третьих, можно поступать как MFC, тогда задействуется только TLS (в которой для каждой задачи TlsAlloc выделяет новую ячейку) и оставлять нетронутыми GWL_USERDATA и CREATESTRUCT, которые, возможно, приложение возжелает заюзать само, приведя к конфликту в первом и втором варианте.
2. С самого далекого детства я помню советский майонез «Провансаль», обладавший дивным вкусом, в отличие от почти безвкусных современных аналогов. Со временем я потерял всяческую надежду вновь ощутить этот замечательный вкус, списав все на долгое хранение некачественного советского продукта на складах в слое кровавого студня.
Пока совершенно случайно не налил современный безвкусный майонез на самую обычную яичницу. Последовавший за этим легкий гуглинг все объяснил: «небольшой расход яичного желтка, самого дорогостоящего компонента в рецептуре майонеза, привлекает внимание производителя». Стоило ли сомневаться!? Советский майонез был вкусным, потому что там было правильное количество яичного желтка, так как не было погони за сверхприбылью.
Оказывается, это довольно известный факт (тыц, тыц): советский майонез изготавливался по строгому ГОСТу, а современные производители предпочитают руководствоваться более мягким российским стандартом, либо, еще чаще, собственными ТУ, в которых яичный желток (его содержание в оригинале ~10%), служащий естественным эмульгатором, почти весь заменяется на другие, более дешевые эмульгаторы — гуаровую и ксантановую камедь. Стремление капиталистов к экономии на этом не останавливается: относительно дорогостоящее масло заменяется водой и разведенным сухим молоком, и даже копеечная горчица заменяется еще более дешевым ароматизатором.
Сколько же стоит этот бесценный порошок, из-за неземной дороговизны которого мы вынуждены есть безвкусную водянистую гадость? 150-200 российских рублей за килограмм. Занавес.
1. WindowProc одна для всех окон каждого [оконного класса], но обработку событий хотелось бы производить в экземплярах класса окна. Для этого надо по hWnd окна уметь определять указатель на объект, который его создал. Но несколько важных событий (WM_CREATE, WM_NCCREATE, WM_GETMINMAXINFO) приходят до того, как происходит возврат из CreateWindowEx в метод объекта и возвращение ею hWnd. Поэтому обычно this передают как последний параметр CreateWindowEx, а в WM_CREATE и WM_NCCREATE он приходит в lParam в структуре CREATESTRUCT, после чего его связывают с hWnd с помощью SetWindowLong с параметром GWL_USERDATA и радуются. Но! WM_GETMINMAXINFO приходит еще раньше, чем WM_NCCREATE, и кому его пересылать, совершенно непонятно.
Поэтому я залез в сырцы MFC и обнаружил, что хитрые мелкомягкие программеры героически обошли созданные ими грабли. Они сохраняют указатель на объект, создающий в текущем треде окно, в объекте класса _AFX_THREAD_STATE, хранящем данные в TLS, свои для каждого треда. Кроме этого они устанавливают оконный хук SetWindowsHookEx с параметром WH_CBT, которому приходит сообщение HCBT_CREATEWND с hWnd создаваемого окна в wParam (а в lParam лежит CBT_CREATEWND, хранящий указатель на CREATESTRUCT) раньше, чем любое событие достигает окна. В нем указатель через TLS извлекается, и ему сообщают хандл его окна (а обратную связь hWnd → указатель MFC хранит в своем собственном БШ-hash_map).
Таким образом, я узнал даже несколько ответов. Во-первых, можно хранить указатель в TLS и извлекать его, если вдруг в статической WindowProc GWL_USERDATA будет содержать 0. Во-вторых, можно помещать this в CREATESTRUCT и в хуке его доставать (впрочем, если программа должна работать под Win9x, без TLS или аналога все же не обойтись — CallNextHookEx в них требует HHOOK, который был возвращен перед вызовом CreateWindowEx, т.е. в объекте). В-третьих, можно поступать как MFC, тогда задействуется только TLS (в которой для каждой задачи TlsAlloc выделяет новую ячейку) и оставлять нетронутыми GWL_USERDATA и CREATESTRUCT, которые, возможно, приложение возжелает заюзать само, приведя к конфликту в первом и втором варианте.
2. С самого далекого детства я помню советский майонез «Провансаль», обладавший дивным вкусом, в отличие от почти безвкусных современных аналогов. Со временем я потерял всяческую надежду вновь ощутить этот замечательный вкус, списав все на долгое хранение некачественного советского продукта на складах в слое кровавого студня.
Пока совершенно случайно не налил современный безвкусный майонез на самую обычную яичницу. Последовавший за этим легкий гуглинг все объяснил: «небольшой расход яичного желтка, самого дорогостоящего компонента в рецептуре майонеза, привлекает внимание производителя». Стоило ли сомневаться!? Советский майонез был вкусным, потому что там было правильное количество яичного желтка, так как не было погони за сверхприбылью.
Оказывается, это довольно известный факт (тыц, тыц): советский майонез изготавливался по строгому ГОСТу, а современные производители предпочитают руководствоваться более мягким российским стандартом, либо, еще чаще, собственными ТУ, в которых яичный желток (его содержание в оригинале ~10%), служащий естественным эмульгатором, почти весь заменяется на другие, более дешевые эмульгаторы — гуаровую и ксантановую камедь. Стремление капиталистов к экономии на этом не останавливается: относительно дорогостоящее масло заменяется водой и разведенным сухим молоком, и даже копеечная горчица заменяется еще более дешевым ароматизатором.
Сколько же стоит этот бесценный порошок, из-за неземной дороговизны которого мы вынуждены есть безвкусную водянистую гадость? 150-200 российских рублей за килограмм. Занавес.