Licence CC 0

Ne loupez pas le coche !

Même les outils de dinosaures sont amusants

Tout part de ce thread : Unchecky, Ninite, comment ca marche ?

Fut une époque pas si lointaine que ça, j’étais féru de comprendre comment certains logiciels fermés (comprendre : dont le code source n’est a priori pas disponible publiquement) fonctionnaient. Certains de mes articles en témoignent.

Dans ce thread, il est question de savoir comment un logiciel nommé "Unchecky" peut faire pour accéder à des fenêtres (comprendre : fenêtres Windows) appartenant à d’autres processus pour décocher des checkbox.

Pour faire simple, l’intérêt de ce genre d’utilitaire est de décocher automatiquement les cases cochées par défaut qui elles-mêmes activent des options pour installer des merdes supplémentaires dont on n’a pas besoin. Ou des options bidons genre "Faire de MSN mon moteur de recherche par défaut"1.

C’est à se demander comment un tel logiciel peut fonctionner. On peut supposer que l’équipe de développement en charge de Unchecky s’amuse à reverser les installeurs pour réussir à décocher les checkbox qui vont bien. Mais ça représenterait trop de boulot.

En fait, les API Win32 sont à la fois monstrueusement chiantes à utiliser mais surtout monstrueusement complètes. C’est ce dont je me suis aperçu à l’époque où je m’amusais à faire du reverse sur Windows2.

Je vais montrer qu’il est possible, pour un programme A qui possède une "checkbox", de la décocher à partir d’un logiciel B sans avoir recours à de la rétro-ingénierie, mais seulement à des API Win32 suffisamment bien documentées pour nous permettre de parvenir à nos faims3.


  1. Bah ouais, MSN est mort, et alors ? :D 

  2. Cela pourrait se reproduire sur demande, qui sait ? ;) 

  3. Il est quinze heures, et je n’ai toujours pas mangé à cause du temps que me prend cette tribune. 

Se remettre dans le bain

Avant que les comètes ne tombent sur les dinosaures et scandent "bon maintenant on arrête les conneries et on fait des outils pour nous faciliter la vie et gagner du temps ceci est une citation un peu trop longue vous trouvez pas", les programmeurs d’application bureau faisaient leur fenêtre à la dure, façon hommes de cro-magnon. Avec un langage que j’affectionne tout particulièrement qu’est le langage C.

Mais on peut aller se gratter pour avoir quelque chose de standard. Ici il est question de macros, de defines, d’UpperCamelCase mêlé à de la notation Hongroise1.

Il m’a fallu donc ressortir un peu de documentation pour me rappeler comment on faisait une fenêtre. Mais pour apprécier un peu la gueule d’un programme minimaliste sous Windows, je vous laisse admirer le snippet suivant :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#include <windows.h>


int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                   PWSTR pCmdLine, int nCmdShow)
{
    MessageBox(
        NULL,
        TEXT("Hello World!"),
        TEXT("Message from the legacy world."),
        MB_OK | MB_ICONINFORMATION);

    return 0;
}

Ici on fait ni plus ni moins qu’afficher une boîte de message débile via l’API MessageBox. Un petit hello world des familles à la Windows, quoi.

La documentation de l’API, si elle est complète, demeure tout de même en Anglais. Mais cela ne devrait pas vous arrêter, même si comme moi vous aimez parfois jouer avec votre caca. :)

On peut voir qu’ici il n’est pas question de int main(int argc, char* argv) mais de int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow). Si vous êtes curieux je vous renvoie à la doc : https://msdn.microsoft.com/en-us/library/windows/desktop/ff381406(v=vs.85).aspx. Pour l’heure je vais me passer d’explications qui ne sont pas dans le cadre de cette tribune.

Pour créer et gérer des fenêtres avec Windows, c’est un peu déroutant si l’on vient d’un milieu où on a l’habitude de faire du procédural et synchrone via l’interface de ligne de commandes.

Pour créer une fenêtre, on a besoin :

  • d’une classe de fenêtre : vous avez déjà programmé en objet ? Eh bien le concept y ressemble : on définit une classe qui va nous permettre de définir certaines propriétés de base pour nos fenêtres à venir (car on peut en créer plusieurs) ;
  • d’une routine de traitement de messages : Il s’agit techniquement d’une fonction, d’un callback que le système va appeler pour traiter… des messages.

Des messages ? Comme sur un répondeur ? :D

Très drôle.

En fait, les fenêtres communiquent entre elles par le biais de messages. Concrètement, un message, ça peut être :

  • La souris bouge sur la droite
  • La souris clique
  • Une touche est pressée
  • La fenêtre se créé
  • Des données ont été lues sur le réseau (oui oui)

Il y en a un paquet.

En résumé, on ne fait pas de code bloquant, excepté la boucle qui va récupérer les messages pour les transmettre à notre fenêtre. :)

Grosso-merdo, ça donne tout ça :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
#include <windows.h>

LRESULT CALLBACK MainWindowProc(HWND hwnd,
    UINT message,
    WPARAM wParam,
    LPARAM lParam);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                   PWSTR pCmdLine, int nCmdShow)
{

    const LPCTSTR szClassName = TEXT("MainWindow");
    HWND hMainWindow = INVALID_HANDLE_VALUE;

    WNDCLASS wndClsMainWindowClass = {
        .style = 0,
        .lpfnWndProc = MainWindowProc,
        .cbClsExtra = 0,
        .cbWndExtra = 0,
        .hInstance = hInstance,
        .hIcon = NULL,
        .hbrBackground = COLOR_WINDOW,
        .lpszMenuName = NULL,
        .lpszClassName = szClassName
    };

    if (RegisterClass(&wndClsMainWindowClass) == 0) {
        MessageBox(
            NULL,
            TEXT("Could not register class."),
            TEXT("Error"),
            MB_OK | MB_ICONERROR);
        ExitProcess(-1);
    }

    hMainWindow = CreateWindow(
        szClassName,
        TEXT("Toggle my Checkbox (please)"),
        WS_OVERLAPPEDWINDOW | WS_VISIBLE, // style
        CW_USEDEFAULT, // x
        CW_USEDEFAULT, // y
        CW_USEDEFAULT, // width
        CW_USEDEFAULT, // height
        NULL, // parent
        NULL, // menu
        hInstance,
        NULL,
    );

    if (hMainWindow == INVALID_HANDLE_VALUE) {
        MessageBox(
            NULL,
            TEXT("Error"),
            TEXT("Could not create window."),
            MB_OK | MB_ICONERROR
        );
        ExitProcess(-1);
    }

    ShowWindow(hMainWindow, nCmdShow);

    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0) > 0) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return 0;
}

LRESULT CALLBACK MainWindowProc(HWND hwnd,
                                UINT message,
                                WPARAM wParam,
                                LPARAM lParam) 
{
    switch (message) {
    case WM_CLOSE:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hwnd, message, wParam, lParam);
    }
    return 0;
}

Oui oui, 84 lignes pour une fenêtre, alors qu’on a besoin de 10 fois moins pour en faire avec Qt par exemple…

Il ne nous reste plus qu’à créer notre checkbox. Il faut donc créer une fenêtre enfant que nous rattacherons à notre fenêtre principale. Eh oui, une checkbox est aussi une "fenêtre" dont la classe est… "Button". Ça déroute, hein ?

Pour la créer, il faut traiter le message WM_CREATE envoyé au callback responsable de traiter les messages de notre fenêtre principale. Lorsque nous recevons ce message, il faut préciser que nous créons des fenêtres enfant, à savoir notre checkbox. Comme ceci :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#define ID_CHECKBOX_TOGGLEME 1 // Set an ID to our checkbox

LRESULT CALLBACK MainWindowProc(HWND hwnd,
                                UINT message,
                                WPARAM wParam,
                                LPARAM lParam) 
{

    BOOL checked = FALSE;

    switch (message) {
    case WM_CREATE:
        CreateWindow(
            TEXT("BUTTON"),
            TEXT("Install some uselessware on this computer, with a lot of "
                 "annoying advertisements."),
            WS_VISIBLE | WS_CHILD | BS_CHECKBOX,
            20, 20, 600, 35,
            hwnd,
            (HMENU)ID_CHECKBOX_TOGGLEME,
            ((LPCREATESTRUCT)lParam)->hInstance,
            NULL);
            CheckDlgButton(hwnd, ID_CHECKBOX_TOGGLEME, BST_CHECKED);

        break;

    /* ... */
    return DefWindowProc(hwnd, message, wParam, lParam);
}

L’instruction CheckDlgButton(hwnd, 1, BST_CHECKED); va indiquer à notre programme de cocher la checkbox par défaut. Notre objectif sera de la décocher en utilisant un programme externe.

Le code final :

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
#include <windows.h>

#define ID_CHECKBOX_TOGGLEME    1

LRESULT CALLBACK MainWindowProc(HWND hwnd,
    UINT message,
    WPARAM wParam,
    LPARAM lParam);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                   PWSTR pCmdLine, int nCmdShow)
{

    const LPCTSTR szClassName = TEXT("MainWindow");
    HWND hMainWindow = INVALID_HANDLE_VALUE;

    WNDCLASS wndClsMainWindowClass = {
        .style = 0,
        .lpfnWndProc = MainWindowProc,
        .cbClsExtra = 0,
        .cbWndExtra = 0,
        .hInstance = hInstance,
        .hIcon = NULL,
        .hbrBackground = COLOR_WINDOW,
        .lpszMenuName = NULL,
        .lpszClassName = szClassName
    };

    if (RegisterClass(&wndClsMainWindowClass) == 0) {
        MessageBox(
            NULL,
            TEXT("Could not register class."),
            TEXT("Error"),
            MB_OK | MB_ICONERROR);
        ExitProcess(-1);
    }

    hMainWindow = CreateWindow(
        szClassName,
        TEXT("Toggle my Checkbox (please)"),
        WS_OVERLAPPEDWINDOW | WS_VISIBLE, // style
        CW_USEDEFAULT, // x
        CW_USEDEFAULT, // y
        CW_USEDEFAULT, // width
        CW_USEDEFAULT, // height
        NULL, // parent
        NULL, // menu
        hInstance,
        NULL,
    );

    if (hMainWindow == INVALID_HANDLE_VALUE) {
        MessageBox(
            NULL,
            TEXT("Error"),
            TEXT("Could not create window."),
            MB_OK | MB_ICONERROR
        );
        ExitProcess(-1);
    }

    ShowWindow(hMainWindow, nCmdShow);

    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0) > 0) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return 0;
}

LRESULT CALLBACK MainWindowProc(HWND hwnd,
                                UINT message,
                                WPARAM wParam,
                                LPARAM lParam) 
{

    BOOL checked = FALSE;

    switch (message) {
    case WM_CREATE:
        CreateWindow(
            TEXT("BUTTON"),
            TEXT("Install some uselessware on this computer, with a lot of "
                 "annoying advertisements."),
            WS_VISIBLE | WS_CHILD | BS_CHECKBOX,
            20, 20, 600, 35,
            hwnd,
            (HMENU)ID_CHECKBOX_TOGGLEME,
            ((LPCREATESTRUCT)lParam)->hInstance,
            NULL);
            CheckDlgButton(hwnd, ID_CHECKBOX_TOGGLEME, BST_CHECKED);

        break;

    case WM_COMMAND:
        checked = IsDlgButtonChecked(hwnd, ID_CHECKBOX_TOGGLEME);
        if (checked) {
            CheckDlgButton(hwnd, ID_CHECKBOX_TOGGLEME, BST_UNCHECKED);
        }
        else {
            CheckDlgButton(hwnd, ID_CHECKBOX_TOGGLEME, BST_CHECKED);
        }
        break;


    case WM_CLOSE:
        PostQuitMessage(0);
        break;
    }
    return DefWindowProc(hwnd, message, wParam, lParam);
}

Et le rendu (attention les yeux !) :

Une fenêtre dinosaure. :D

  1. Monsieur et Madame GroisDansMaBaignore ont une fille. Comment s’appelle-t-elle ? 

Décocher "à distance"

On a fait notre programme cible. Maintenant on va faire un autre programme qui va décocher de lui-même cette case à cocher.

Je vous le répète : l’API Win32 est monstrueusement bien fournie. On va se servir de ces APIs :

  • FindWindow pour récupérer un descripteur sur la fenêtre cible (qui possède la case à cocher)
  • EnumChildWindows pour lister les fenêtres enfantes d’une fenêtre parente (note : dans ces fenêtres enfantes, il y aura la checkbox en question ! ;) )
  • SendMessage : pour envoyer un message à la checkbox et lui dire de se décocher. Niark niark!

Pas besoin de faire de reversing pour le coup.

Le code du décocheur :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#include <windows.h>

#define MAX_BUF     255


BOOL CALLBACK TargetWindowEnumChildProc(HWND hwnd, LPARAM lParam);

int WINAPI WinMain(
    HINSTANCE hInstance,
    HINSTANCE hPrevInstance,
    PWSTR pCmdLine,
    int nCmdShow)
{
    HWND hTargetWindow = FindWindow(
        NULL,
        TEXT("Toggle my Checkbox (please)"));

    if (hTargetWindow == NULL) {
        MessageBox(
            NULL,
            TEXT("Could not find the target window. "
                 "Maybe you should launch it?"),
            TEXT("OooOops!"),
            MB_OK | MB_ICONWARNING
        );
        ExitProcess(-1);
    }

    EnumChildWindows(
        hTargetWindow,
        TargetWindowEnumChildProc,
        0);

    return 0;
}

BOOL CALLBACK TargetWindowEnumChildProc(HWND hwnd, LPARAM lParam) {
    CHAR cClassName[MAX_BUF] = {'\0'}; // Hope this is enough...
    LONG lWindowStyle = 0;

    GetClassName(hwnd, cClassName, MAX_BUF - 1);

    if (strcmp(cClassName, "Button") == 0) {
        // It's a match! Check that it is a checkbox

        lWindowStyle = GetWindowLong(hwnd, GWL_STYLE);

        if (lWindowStyle & BS_CHECKBOX) {
            // It is a checkbox: uncheck it. :D
            SendMessage(
                hwnd,
                BM_SETCHECK,
                BST_UNCHECKED,
                0);

            MessageBox(
                NULL,
                TEXT("I have unchecked this unwanted damn checkbox!"),
                TEXT("Rock'n'roll!"),
                MB_OK | MB_ICONINFORMATION
            );
        }
    }

    return TRUE;
}

Pour la démo, c’est par là ! Et pour ceux qui veulent tester eux-mêmes et tenter quelque chose de plus poussé, retrouvez les sources ici : https://github.com/Ge0/ToggleMyCheckbox


Les API Win32, c’est démodé, mais qu’est-ce que c’est rigolo quand on veut commencer à faire des trucs marrants sous Windows. Vous n’êtes pas d’accord ? :D

12 commentaires

Cela ne pose-t-il pas des problèmes de confidentialité entre applications ?

Je pense que tu te taperas une erreur si tu essaies d’accéder à une fenêtre appartenant à un processus dont tu n’es pas le propriétaire. Je connais mal la gestion des droits sous Windows, mais si tu es détenteur des deux processus, tu peux faire beaucoup de choses. Dont récupérer les données d’une autre application. Rien qu’avec ReadProcessMemory tu peux lire la mémoire d’un autre processus (pourvu que tu aies les droits suffisants pour le faire). Sinon, pour récupérer les données d’un champ de formulaire, je pense que tu peux utiliser GetWindowText.

Salut,

Merci beaucoup pour cet intéressant billet. :)

Pour la créer, il faut traiter le message WM_CREATE envoyé au callback responsable de traiter les messages de notre fenêtre principale.

Il n’y a pas moyen d’ajouter des contenants en dehors de la fonction de traitements des évènements ? Je veux dire, si ce n’est pas le cas, cela marche comment si tu veux ajouter une fenêtre à une sous-fenêtre ? Il faut gérer l’évènement WM_CREATE de la sous-fenêtre ?

PS : c’est pas tout à fait du code dinosaure, tu triches en écrivant en C99. :-°

+1 -0

Il n’y a pas moyen d’ajouter des contenants en dehors de la fonction de traitements des évènements ? Je veux dire, si ce n’est pas le cas, cela marche comment si tu veux ajouter une fenêtre à une sous-fenêtre ? Il faut gérer l’évènement WM_CREATE de la sous-fenêtre ?

Taurre

Dans l’ordre :

  • Je ne sais pas, mais il me semble que le message WM_CREATE est fait pour ça. Un peu comme le constructeur de ta fenêtre.
  • Il faut que tu enregistres une autre classe de fenêtre dans ta fonction WinMain et que tu créées la sous-fenêtre dans le callback de ta fenêtre parente, toujours en gérant le message WM_CREATE.
  • L’événement WM_CREATE de la sous-fenêtre servira à créer les enfants de la sous-fenêtre.

PS : c’est pas tout à fait du code dinosaure, tu triches en écrivant en C99. :-°

Taurre

Gnagnagna. :P Blague ç part, j’ai jamais été très bon en ce qui est différentiation du C ANSI/C99. Tu pourrais m’indiquer ce qui est C99 dans mon code ?

  • Je ne sais pas, mais il me semble que le message WM_CREATE est fait pour ça. Un peu comme le constructeur de ta fenêtre.
Ge0

Je demande parce que GTK+ ne fonctionne pas comme cela par exemple. Il est parfaitement possible de construire toute l’arborescence de la GUI sans passer par la gestion des évènements.

  • Il faut que tu enregistres une autre classe de fenêtre dans ta fonction WinMain et que tu créées la sous-fenêtre dans le callback de ta fenêtre parente, toujours en gérant le message WM_CREATE.
  • L’événement WM_CREATE de la sous-fenêtre servira à créer les enfants de la sous-fenêtre.
Ge0

Mmm… À ce sujet, je ne réalise que maintenant, est-il normal que tu ne récupères pas le retour de la fonction CreateWindow ligne 83 ?

Gnagnagna. :P Blague à part, j’ai jamais été très bon en ce qui est différentiation du C ANSI/C99. Tu pourrais m’indiquer ce qui est C99 dans mon code ?

Ge0

L’initialisation d’une structure en nommant ses champs (comme tu le fais avec la structure wndClsMainWindowClass) a été ajoutée en C99. Jusque-là il n’était possible que de spécifier les valeurs et dans l’ordre s’il vous plaît ! ;)
Et… C’est tout. ^^

+1 -0

Je demande parce que GTK+ ne fonctionne pas comme cela par exemple. Il est parfaitement possible de construire toute l’arborescence de la GUI sans passer par la gestion des évènements.

Taurre

Oui, GTK+ est plus évolué pour le coup ! :)

Mmm… À ce sujet, je ne réalise que maintenant, est-il normal que tu ne récupères pas le retour de la fonction CreateWindow ligne 83 ?

Taurre

Je pourrais pour stocker le handle dans une variable statique. et si j’ai besoin d’accéder spécifiquement à ma checkbox par le handle. Mais pour le coup, pas besoin.

L’initialisation d’une structure en nommant ses champs (comme tu le fais avec la structure wndClsMainWindowClass) a été ajoutée en C99. Jusque-là il n’était possible que de spécifier les valeurs et dans l’ordre s’il vous plaît ! ;)

Taurre

Ah ouais… J’avais tapé ça de manière totalement automatique…

Merci beaucoup pour tes éclaircissements.

Je pourrais pour stocker le handle dans une variable statique. et si j’ai besoin d’accéder spécifiquement à ma checkbox par le handle. Mais pour le coup, pas besoin.

Mmm… Et du coup, si tu tapes le code suivant :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
CreateWindow(
        TEXT("BUTTON"),
        TEXT("Install some uselessware on this computer, with a lot of "\
        "annoying advertisements."),
        WS_VISIBLE | WS_CHILD | BS_CHECKBOX,
        20, 20, 600, 35,
        hwnd,
        (HMENU)ID_CHECKBOX_TOGGLEME,
        ((LPCREATESTRUCT)lParam)->hInstance,
        NULL);
CheckDlgButton(hMainWindow, ID_CHECKBOX_TOGGLEME, BST_CHECKED);

Juste après la création de la fenêtre principale (donc après la ligne 50) et que tu vires la gestion de l’évèvenement WM_CREATE, cela fonctionne ?

Merci beaucoup pour tes éclaircissements.

Ge0

Avec plaisir. ^^

+0 -0

Je pense que tu te taperas une erreur si tu essaies d’accéder à une fenêtre appartenant à un processus dont tu n’es pas le propriétaire

Pourtant Unchecky n’est a priori pas le détenteur des installateurs ?

Et sinon, billet très sympas ;)

+0 -0

Je pense que tu te taperas une erreur si tu essaies d’accéder à une fenêtre appartenant à un processus dont tu n’es pas le propriétaire

Pourtant Unchecky n’est a priori pas le détenteur des installateurs ?

Et sinon, billet très sympas ;)

WinXaito

Unchecky est lancé par le même utilisateur que les installateurs, en fait.

Merci pour ton mot gentil. :)

C’est encore moi !

@Taurre Effectivement, tu peux tout à fait créer les "fenêtres" enfantes de ta fenêtre principale dans WinMain. Comme ceci :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                   PWSTR pCmdLine, int nCmdShow)
{

    const LPCTSTR szClassName = TEXT("MainWindow");
    HWND hMainWindow = INVALID_HANDLE_VALUE;

    WNDCLASS wndClsMainWindowClass = {
        .style = 0,
        .lpfnWndProc = MainWindowProc,
        .cbClsExtra = 0,
        .cbWndExtra = 0,
        .hInstance = hInstance,
        .hIcon = NULL,
        .hbrBackground = COLOR_WINDOW,
        .lpszMenuName = NULL,
        .lpszClassName = szClassName
    };

    if (RegisterClass(&wndClsMainWindowClass) == 0) {
        MessageBox(
            NULL,
            TEXT("Could not register class."),
            TEXT("Error"),
            MB_OK | MB_ICONERROR);
        ExitProcess(-1);
    }

    hMainWindow = CreateWindow(
        szClassName,
        TEXT("Toggle my Checkbox (please)"),
        WS_OVERLAPPEDWINDOW | WS_VISIBLE, // style
        CW_USEDEFAULT, // x
        CW_USEDEFAULT, // y
        CW_USEDEFAULT, // width
        CW_USEDEFAULT, // height
        NULL, // parent
        NULL, // menu
        hInstance,
        NULL,
    );

    CreateWindow(
        TEXT("BUTTON"),
        TEXT("Install some uselessware on this computer, with a lot of "
            "annoying advertisements."),
        WS_VISIBLE | WS_CHILD | BS_CHECKBOX,
        20, 20, 600, 35,
        hMainWindow,
        (HMENU)ID_CHECKBOX_TOGGLEME,
        hInstance,
        NULL);

    CheckDlgButton(hMainWindow, ID_CHECKBOX_TOGGLEME, BST_CHECKED);

    if (hMainWindow == INVALID_HANDLE_VALUE) {
        MessageBox(
            NULL,
            TEXT("Error"),
            TEXT("Could not create window."),
            MB_OK | MB_ICONERROR
        );
        ExitProcess(-1);
    }

    ShowWindow(hMainWindow, nCmdShow);

    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0) > 0) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return 0;
}

Simplement, le message WM_CREATE sert à mon sens exclusivement à "créer" ta fenêtre et donc à initialiser les éléments enfants. Le constructeur de ta fenêtre, en somme. Les prémices de l’orienté objet…

Fût-ce possible de faire la même chose dans WinMain, c’est un peu comme si tu mettais tout le code de ton programme dans main. On va dire que ça ne facilite pas sa maintenance ! ^^

Bonne remarque en tout cas. Ca donne vraiment envie de s’y remettre. Dommage qu’une journée ne dure que 24 heures…

Merci pour ta réponse. :)

Simplement, le message WM_CREATE sert à mon sens exclusivement à "créer" ta fenêtre et donc à initialiser les éléments enfants. Le constructeur de ta fenêtre, en somme. Les prémices de l’orienté objet…

Ge0

Intéressant, je n’avais pas vu les choses comme cela (faut dire aussi que moi et l’OO… :-° ). Je remarque d’ailleurs qu’il ne semble pas possible de faire pareil avec GTK+ (ou alors je n’ai pas trouvé le bon signal à traiter).

Fût-ce possible de faire la même chose dans WinMain, c’est un peu comme si tu mettais tout le code de ton programme dans main. On va dire que ça ne facilite pas sa maintenance ! ^^

Ge0

C’est sûr que cela devent vite diffcilement maintenable. ^^"

Bonne remarque en tout cas. Ca donne vraiment envie de s’y remettre. Dommage qu’une journée ne dure que 24 heures…

Ge0

Je pense que le problème n’est pas tant la durée, mais plutôt le contenu obligatoire. :p

+1 -0
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte