Accueil
Rechercher:
sur developpez.com sur les forums
Forums | Tutoriels | F.A.Q's | Participez | Hébergement | Contacts
Club Emploi Blogs   TV   Dév. Web PHP XML Python Autres 2D-3D-Jeux Sécurité Windows Linux PC Mac
Accueil Conception Java DotNET Visual Basic  C  C++ Delphi MS-Office SQL & SGBD Oracle  4D  Business Intelligence

Utilisez l'API Win32 de surveillance du Spooler d'impression

21/03/2003

Par DjmSoftWare
 

Ce tutorial démontre l'utilisation des API win32 relatives à la surveillance du spouler d'impression.


Avant-Propos
1. Cahier des Charges
2. Solution Proposée définition de la classe de base (main.h)
2.a. méthode GetDefaultPrinter
2.b. méthode IsValidHandle
2.c. sauvegarde de la listView
2.d. Lecture du fichier et initialisation de la listVIew
2.e. Procédure de tri en fonction de la date et l'heure
2.f. Constructeur de la Classe Principale
2.g. Destruction de la Classe Principale
2.h. Classe TPrinterNotifier
2.i. définition de la classe (PrintNotifier.h)
2.j. constructeur de la classe TPrinterNotifier
2.k. Méthode Execute
2.l. méthode PostLog
2.m. destructeur de la classe TPrinterNotifier
2.n. gestionnaire de message WM_PrinterNotifier
2.o. gestionnaire de message WM_SortedListNotifier
2.p. Classe de Tri
2.q. Méthode Execute


Avant-Propos


Attention: l'application décrite ci dessous ne fonctionne que sous les systèmes d'exploitations suivants

  • Windows NT 4
  • Windows 2000
  • Windows XP

Sources du Programme disponible ici.


1. Cahier des Charges


  • enumérer toutes les imprimantes installées sur le PC en local ou en réseau
  • Element>pour chaque imprimante afficher dans une ListView les paramètres suivants
  • Charger et Sauvegarder la ListView dans un fichier au format CSV
  • Consommer un minimum de resources Systèmes
  • Possibilité de debugging et de trace (Log File) par paramètre dans la ligne de commande

Date
Heure
JobId
ComputerName
User Name
Printer Name
Document
Page
Taille

2. Solution Proposée


Image de l'application.

pour répondre au point 5 du cahier des charges on va utiliser les API Win32.

HANDLE FindFirstPrinterChangeNotification(
HANDLE hPrinter, // handle to printer or print server to monitor for changes
DWORD fdwFlags, // flags that specify the conditions to monitor
DWORD fdwOptions, // reserved, must be zero
LPVOID pPrinterNotifyOptions // pointer to structure specifying printer information to monitor
);
BOOL FindNextPrinterChangeNotification(
HANDLE hChange, // handle to change notification object of interest
PDWORD pdwChange, // pointer to a value that indicates the condition that changed
LPVOID pPrinterNotifyOptions, // pointer to a structure that specifies a refresh flag
LPVOID *ppPrinterNotifyInfo // pointer to a pointer that receives printer information buffer
);
BOOL FindClosePrinterChangeNotification(
HANDLE hChange // handle to change notification object to close
);

Ces fonctions s'utilisent en relation avec les "Wait Function" des API Win32 ce qui nécessite l'utilisation d'une classe dédiée de type TThread
On doit tout d'abord réaliser les points suivants

  • Analyse de la ligne de commande pour déterminer si le mode debug est activé (param -debug)
  • Rechercher et stocker dans une StringList toutes les imprimantes installées
  • créer une classe de type TThread pour le monitoring

définition de la classe de base (main.h)
//---------------------------------------------------------------------------
#ifndef MainH
#define MainH
//---------------------------------------------------------------------------
#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
#include <ComCtrls.hpp>
#include <ExtCtrls.hpp>
#include <FileCtrl.hpp>
#include <stdio.h>
#include "PrintNotifier.h"
#include "SortedJobThread.h"
#define JobFileName "JobList.csv"
//---------------------------------------------------------------------------
struct TJobInfoRecord {
    AnsiString jDate;
    AnsiString jtime;
    DWORD jID;
    AnsiString jPrinter;
    AnsiString jComputer;
    AnsiString jUser;
    AnsiString jDocument;
    int jPages;
    DWORD jSize;
   
} ;

int __stdcall CustomSortProc(long Item1, long Item2, long ParamSort);

class TForm1 : public TForm {
    __published: // Composants gÉrÉs par l'EDI
    TListView *ListView1;
    void __fastcall FormDestroy(TObject *Sender);
    private :
    int FileHandle;
    bool FDebug;
    TStringList* FPrinterList; // Liste des Imprimantes
    AnsiString FDefaultPrinter; // nom de l'imprimante par défaut
    AnsiString FPrinter; // nom de l'imprimante
    HANDLE FHandle;
    TJobInfoRecord* FJobRecord; // Enregistrement d'un job
    TList* FJobListRec; // Liste des Enregistrements de Job
    TPrinterNotifier* FPrintNotifier; // Ptr sur ThreadPrinterNotifier
    TSortJobThread* FSortJobThread ; // Ptr sur TSortJobThread
    int FThreadCount; // compteur de ThreadPrinterNotifier
    TList* FThreadList; // Liste de Ptr sur ThreadPrinterNotifier
    TStringList* FJobList; // Liste de Jobs (format csv)
    void __fastcall GPrinterNotifier(TMessage &msg);
    void __fastcall GUpdateListView(TMessage &msg);
    void __fastcall FindInstalledPrinters();
    void __fastcall GetDefaultPrinter();
    bool __fastcall IsValidHandle(AnsiString &PrinterName);
    void __fastcall SaveListViewToFile();
    void __fastcall LoadListFromFile();
    void __fastcall WriteLog(TJobInfoRecord *NewRec,int n);
   
    // Déclarations de l'utilisateur
    public : // Déclarations de l'utilisateur
    __fastcall TForm1(TComponent* Owner);
    BEGIN_MESSAGE_MAP
    MESSAGE_HANDLER(WM_PrinterNotifier,TMessage,GPrinterNotifier)
    MESSAGE_HANDLER(WM_SortedListNotifier,TMessage,GUpdateListView)
    END_MESSAGE_MAP(TForm)
   
} ; //---------------------------------------------------------------------------
extern PACKAGE TForm1 *Form1;
//---------------------------------------------------------------------------
#endif

on va tout d'abord s'intéresser a rechercher toutes les imprimantes installées l'implémentation de la méthode FindInstalledPrinters() est définie de la manière suivante

// find all installed Printers and Save PrinterList in FPrinterList
{
    ; DWORD Count=0,InfoCount=0,Flags=0,Level=0;
    char ** PPBufContent;
    unsigned char *PBuffer,* Buffer;
    if (Win32Platform == VER_PLATFORM_WIN32_NT) // test si NT,XP
    {
    Flags=PRINTER_ENUM_CONNECTIONS | PRINTER_ENUM_LOCAL;
    Level=4;
    }
    else
    {
    MessageDlg("Désolé cette application ne fonctionne que sous environnement\nWindows NT ou Windows XP",
    mtError,
    TMsgDlgButtons() << mbOK, 0);
    Application->Terminate();
    }
    EnumPrinters(Flags,
    NULL,
    Level, // API Windows
    NULL, // Count retourne la taille
    0, // nÉcessaire pour le Buffer
    &Count,
    &InfoCount);
    if (Count==0) return ; // pas d'imprimante -->Exit
    Buffer= new unsigned char [Count]; // crÉation du Buffer
    PBuffer= Buffer; // PBuffer pointe sur le Buffer
    EnumPrinters(Flags,
    NULL, Level, //InfoCount contient le
    Buffer, // Nbre d'ÉlÉments de type
    Count, // PRINTER_INFO_4
    &Count, // stockÉ dansle Buffer
    &InfoCount);
    if (InfoCount)
    {
    FPrinterList->Clear(); // on efface la Liste
    for (DWORD n=0;n<InfoCount;n++)
    {
    PPBufContent=(char **)Buffer; //PPBufContent contient le contenu
    Buffer+=sizeof (PRINTER_INFO_4); // dÉplacement dans le Buffer
    FPrinterList->Add(*PPBufContent); // on additionne le rÉsultat dans
    }
    } // la StringListe
    delete []PBuffer; // destruction du Buffer
   
} ; }

on teste tout d'abord si le système d'exploitation est valide,car les fonctions de notifications ne sont implémentées que sous NT respectivement XP
on utilise ensuite l'API win32 EnumPrinters qui retourne dans la variable Count
la taille du buffer nécesaire pour stocker toutes les imprimantes trouvées dans le format PRINTER_INFO_4

typedef struct _PRINTER_INFO_4 { // pri4
{
    ;
    LPTSTR pPrinterName;
    LPTSTR pServerName;
    DWORD Attributes;
    } PRINTER_INFO_4;
   
} ;

on appelle donc cette API EnumPrinters 2 fois de suite
lors du second appel on a créé en mémoire un buffer d'unsigned char de taille égale
a la variable Count
en sortie de la fonction la variable InfoCount est initialisée
avec le nombre d'imprimantes trouvées dans le format
PRINTER_INFO_4 un résultat de 0 indique une erreur
le format du Buffer retourné par Windows est le suivant

FPrinterList->Clear(); // on efface la Liste
{
    ;
    for (DWORD n=0;n<InfoCount;n++)
    {
    PPBufContent=(char **)Buffer; //PPBufContent contient le contenu
    Buffer+=sizeof (PRINTER_INFO_4); // dÉplacement dans le Buffer
    FPrinterList->Add(*PPBufContent); // on additionne le rÉsultat dans la StringListe
    }
   
} ;


pour les premiers test il peut être intéressant de travailler avec l'imprimante par défault


2.a. méthode GetDefaultPrinter


void __fastcall TForm1::GetDefaultPrinter()
{
{
    ;
    long BuffLength=0L; // recherche de l'imprimante par dÉfault
    char DefaultPrinter [90];
    BuffLength=GetProfileString("windows",
    "device",
    NULL,
    DefaultPrinter,
    sizeof (DefaultPrinter));
    if (BuffLength)
    {
    FDefaultPrinter=DefaultPrinter;
    FDefaultPrinter=FDefaultPrinter.SubString(1,FDefaultPrinter.Pos(",")-1);
    } // RÉsultat dans FDefaultPrint
   
} ;
}

pour utiliser les fonctions de notifications on doit envoyer comme paramètre le Handle de l'imprimante


2.b. méthode IsValidHandle


bool __fastcall TForm1::IsValidHandle(AnsiString & PrinterName)
{
bool ReturnValue=false;
if (PrinterName!="")
{
ReturnValue=OpenPrinter(PrinterName.c_str(),
&FHandle,
NULL);
} // on essaye d'obtenir un Handle pour
return ReturnValue; // l'imprimante PrinterName
} // on retourne le rÉsultat


le Handle est stoké si valide dans le champ FHandle


2.c. sauvegarde de la listView


void __fastcall TForm1::SaveListViewToFile()
{
ListView1->Items->BeginUpdate();
if (FDebug)
{
SetCurrentDirectory("..");
ListView1->CustomSort(CustomSortProc,1);
}
TListItem* mListItem;
AnsiString Jobtxt;
AnsiString Separator=";";
for (int n=0;n<ListView1->Items->Count;n++)
{ // affectation de la premiÈre ligne
mListItem=ListView1->Items->Item[n]; // de la listView À mListItem
Jobtxt=mListItem->Caption; //affectation de Caption a Jobtxt
Jobtxt+=Separator; // ajout du sÉparateur
for (int i=0;i<8;i++) // itÉration dans la ligne N
{ // ÉlÉment I
Jobtxt+=mListItem->SubItems->Strings[i];//affectation de Element[i]À Jobtxt
Jobtxt+=Separator; // ajout du sÉparateur
}
FJobList->Add(Jobtxt); // stockage de l'ansiString RÉsultant
} // dans la la JobList
FJobList->SaveToFile(ExtractFilePath(ParamStr(0))+JobFileName);// sauvegarde
ListView1->Items->EndUpdate(); // fin du traitement de la ListView
}


on commence la méthode avec BeginUpdate ce qui va accélérer le traitement en empêchant l'affichage de mise a jour
de la listView dans le mode debug les fichier de trace sont mémorisé dans le directory Log le séparateur de texte
est défini par <>
on itére simplement dans la listview, on fabrique une ligne complète avec les différents éléments
de l'affichage,on stocke la ligne résultante dans la StringList et enfin on sauve le contenu de la StringList dane un fichier au format csv



2.d. Lecture du fichier et initialisation de la listVIew


void __fastcall TForm1::LoadListFromFile()
{
try
{
FJobList->LoadFromFile(ExtractFilePath(ParamStr(0))+JobFileName);
ListView1->Items->BeginUpdate(); // dÉbut du traitement de la listView
TListItem* mListItem;
AnsiString Jobtxt;
AnsiString Separator=";";
int PosSep;
for (int n=0;n<FJobList->Count;n++)
{
FJobRecord=new TJobInfoRecord();
mListItem=ListView1->Items->Add();
Jobtxt= FJobList->Strings[n]; // extraction de la chaine
PosSep=Jobtxt.Pos(Separator); // recherche du premier sÉparateur
FJobRecord->jDate=Jobtxt.SubString(1,PosSep-1);
mListItem->Caption=FJobRecord->jDate; // Caption= 1 er ÉlÉment
Jobtxt.Delete(1,PosSep);
PosSep=Jobtxt.Pos(Separator); // on efface le premier ÉlÉment
FJobRecord->jtime=Jobtxt.SubString(1,PosSep-1);
mListItem->SubItems->Add(FJobRecord->jtime);
Jobtxt.Delete(1,PosSep);
PosSep=Jobtxt.Pos(Separator);
FJobRecord->jID=atol(Jobtxt.SubString(1,PosSep-1).c_str());
mListItem->SubItems->Add(FJobRecord->jID);
Jobtxt.Delete(1,PosSep);
PosSep=Jobtxt.Pos(Separator);
FJobRecord->jComputer=Jobtxt.SubString(1,PosSep-1);
mListItem->SubItems->Add(FJobRecord->jComputer);
Jobtxt.Delete(1,PosSep);
PosSep=Jobtxt.Pos(Separator);
FJobRecord->jUser=Jobtxt.SubString(1,PosSep-1);
mListItem->SubItems->Add(FJobRecord->jUser);
Jobtxt.Delete(1,PosSep);
PosSep=Jobtxt.Pos(Separator);
FJobRecord->jPrinter=Jobtxt.SubString(1,PosSep-1);
mListItem->SubItems->Add(FJobRecord->jPrinter);
Jobtxt.Delete(1,PosSep);
PosSep=Jobtxt.Pos(Separator);
FJobRecord->jDocument=Jobtxt.SubString(1,PosSep-1);
mListItem->SubItems->Add(FJobRecord->jDocument);
Jobtxt.Delete(1,PosSep);
PosSep=Jobtxt.Pos(Separator);
FJobRecord->jPages=atoi(Jobtxt.SubString(1,PosSep-1).c_str());
mListItem->SubItems->Add(FJobRecord->jPages);
Jobtxt.Delete(1,PosSep);
PosSep=Jobtxt.Pos(Separator);
mListItem->SubItems->Add(Jobtxt.SubString(1,PosSep-1));
int Pos= Jobtxt.Pos("o");
if (Pos !=-1)
{
Jobtxt=Jobtxt.SubString(1,Pos-2);
FJobRecord->jSize=1024*atol(Jobtxt.c_str());
}
else
FJobRecord->jSize=atol(Jobtxt.SubString(1,PosSep-1).c_str());
FJobListRec->Add(FJobRecord);
} // fin du traitement de la ListView
FJobList->Clear();
ListView1->CustomSort(CustomSortProc,0);
ListView1->Items->EndUpdate();
// on efface le contenu de la List
}
catch (...){};
} //---------------------------------------------------------------------------


on utilise le traitement des execptions pour empêcher intercepter C++ Builder
d'afficher une fenêtre d'erreur lors de la première utilisation du programme le fichier n'existant pas

l'appel à BeginUpdate empêche l'affichage de mise a jour de la listView
on itère ensuite dans le contenu de la stringList en utilisant le separateur<;>
pour séparer les éléments de la ListView


2.e. Procédure de tri en fonction de la date et l'heure


int __stdcall CustomSortProc(long Item1, long Item2, long ParamSort)
{
TDateTime n1,n2;
TListItem *i1,*i2;
i1=(TListItem*)Item1;
i2=(TListItem*)Item2;
n1=TDateTime(i1->Caption)+TDateTime(i1->SubItems->Strings[0]);
n2=TDateTime(i2->Caption)+TDateTime(i2->SubItems->Strings[0]);
if (n1==n2) return 0;
if (ParamSort)
{
if (n1>n2) return 1;
if (n1;<n2) return -1;
}
else
{
if (n1>n2) return -1;
if (n1;<n2) return 1;
}
}


2.f. Constructeur de la Classe Principale


__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
if (ParamCount())
{
if (ParamStr(1).LowerCase()=="-debug")
{
if (!DirectoryExists("log"))
CreateDir("Log");
SetCurrentDirectory("Log");
FDebug=true;
AnsiString Temp;
Temp="Logfile -";
Temp+=Date();
Temp+=" .log";
FileHandle=FileCreate(Temp);
Temp="Date\tHeure\tThreadId\tJobId\tComputer\tUser\tPrinter\tDocument\tPages\tSize";
Temp+="\r\n";
FileWrite(FileHandle,Temp.c_str(),Temp.Length());
}
}
FJobListRec= new TList(); // Construction de la Liste des Enregistrements de Job
FPrinterList= new TStringList(); // Construction de la Liste des imprimantes
FJobList=new TStringList(); // Construction de la Liste des Jobs
LoadListFromFile(); // Chargement du fichier JobListe +
FSortJobThread= new TSortJobThread(false,FJobListRec);// création de ThreadSortedJobList
FThreadList= new TList(); // Remplissage de la ListeView
FindInstalledPrinters(); // Recherche des Imprimantes Installées
for (int i=0;i<FPrinterList->Count;i++)
{
FPrinter=FPrinterList->Strings[i];
// GetDefaultPrinter(); // utilisé pour le Test
// FPrinter=FDefaultPrinter; // avec imprimante par défaut
if (IsValidHandle(FPrinter)) //si Handle Valide Construction d'un
{ // nouveau thread par imprimante
FPrintNotifier= new TPrinterNotifier(false,
FPrinter.c_str(),
FThreadList->Count,
FHandle,FDebug);
FThreadList->Add((TPrinterNotifier*)FPrintNotifier);
} // ajout du Pteur de thread dans la Liste
}
}


on teste tout d'abord si le programme a été lancé avec une ligne de commande
si la réponse est positive et la valeur du paramètre de la ligne de commande égale à<-debug>
on crée le directory Log s'il n'existe pas,on place la champ FDebug à true ,on crée un fichier log avec son entête
on crée une Liste FJobListRec de type TList,la StringList contenant les imprimantes, une StringListe
contenant la Liste des Jobs
Chargement du fichier JobListText dans la ListView,création d'une Liste contenant tous les Threads crées
recherche des Imprimante
on itère ensuite dans la StringList , on teste si le Handle est valide,si oui construction d'un object Thread par imprimante
on stocke le pointeur résultant dans dans la Liste de Thread


2.g. Destruction de la Classe Principale


delete FSortJobThread;
SaveListViewToFile(); // on sauve le contenu de la Listview dans un fichier
delete FPrinterList; // destruction de la Liste
for (int n=0;n<FThreadList->Count;n++) // destruction du contenu de la liste
delete (TPrinterNotifier*)FThreadList->Items[n];
delete FThreadList; // destruction de la Liste des PtrThreads
for (int n=0;n<FJobListRec->Count;n++) // destruction du contenu de la liste
delete (TJobInfoRecord*)FJobListRec->Items[n];
delete FJobListRec; // destruction de la Liste des PtrThreads
delete FJobList; // destruction de la JobListe
if (FDebug)
FileClose(FileHandle);


Sauvegarde de la listView dans le fichier JobList, destruction de la stringList contenant les imprimantes
destruction de tous les objets threads créés
destruction de la Liste de Threaad, fermeture du fichier de log si mode debug est activé


2.h. Classe TPrinterNotifier


la classe TPrinterNotifier encapsule les API Win32 FindFirstPrinterChangeNotification et FindNextPrinterChangeNotification


2.i. définition de la classe (PrintNotifier.h)


//--------------------------------------------------------------------------- {
    #ifndef PrintNotifierH
#define PrintNotifierH
//---------------------------------------------------------------------------
    #include <Classes.hpp>
#include "Winspool.h"
#define WM_PrinterNotifier WM_APP+2000
#define LogFileName "ThreadLog"
#define FileExt ".log"

}
//--------------------------------------------------------------------------- {
    class TPrinterNotifier : public TThread
    {
    private :
    bool FDebugMode;
    AnsiString FPrinterName;
    HANDLE hNotifier;
    int FileHandle;
    AnsiString FPrinter;
    AnsiString FComputer;
    AnsiString FUser;
    AnsiString FDocument;
    DWORD FId;
    HANDLE hPrinter;
    int FThreadID;
    int FPages;
    int FTotPages;
    DWORD FSize;
    void __fastcall PostLog();
    protected :
    void __fastcall Execute();
    public :
    __fastcall TPrinterNotifier(bool CreateSuspended): TThread(CreateSuspended){};
    __fastcall TPrinterNotifier(bool CreateSuspended, const char * PrinterName,
    int TId,HANDLE PrinterHandle,bool ActiveDebug);
    __fastcall ~TPrinterNotifier();
    __property AnsiString Printer = { read = FPrinter };
    __property AnsiString Computer = { read = FComputer };
    __property AnsiString User = { read = FUser };
    __property AnsiString Document = { read = FDocument };
    __property DWORD Id = { read = FId };
    __property int Pages = { read = FPages };
    __property int TotPages = { read = FTotPages };
    __property DWORD Size = { read = FSize };
    };
   
} //---------------------------------------------------------------------------
#endif

le but de cette classe est de transférer à la Form Principale (MainForm.pas) les informations sur le Job Courant
le problème principal en utilisant un object Thread est la synchronisation avec le Thread VCL Principal
l'utilisation de la méthode synchronize() fonctionne bien mais est relativement lente
dans notre cas si par exemple 3 imprimantes sont installées et que 3 taches d'impression arrivent
des problèmes de vitesse vont apparaître

une solution relativement simple pour contourner ce problème est l'envoi de message aux ThreadVcl avec la méthode
PostMessage,dans ce cas c'est la boucle de message de Windows qui synchronizera l'ensemble
dans la FormPrincipale on va ajouter un gestionnaire pour le message cré ainsi qu'une méthode adéquate

void __fastcall GPrinterNotifier(TMessage &msg);
public : // DÉclarations de l'utilisateur
__fastcall TForm1(TComponent* Owner);
BEGIN_MESSAGE_MAP
MESSAGE_HANDLER(WM_PrinterNotifier,TMessage,GPrinterNotifier)
END_MESSAGE_MAP(TForm)



le message WM_PrinterNotifier WM_APP+2000 est défini dans l'entête de fichier
le gestionnaire de message sera abordée ultérieurement

pour tansférer les informations sur le job j'ai défini des property permettant
un accès en lecture seule des champs stockés dans la partie private de ma classe
j'ai ajouté également un nouveau constructeur avec mes besoins spécifiques,Id,PrinterName,PrinterHandle,debug)


2.j. constructeur de la classe TPrinterNotifier


// constructeur du ThreadNotifier avec passage des paramÈtres
// PrinterName, ThreadId(Tid), Handle du Printer
__fastcall TPrinterNotifier::TPrinterNotifier(bool CreateSuspended ,
const char * PrinterName,
int TId,HANDLE PrinterHandle,
bool ActiveDebug)
: TThread(CreateSuspended)
{
FDebugMode=ActiveDebug;
FPrinterName=PrinterName;
FThreadID=TId;
hPrinter=PrinterHandle;
if (FDebugMode)
{
AnsiString Temp(LogFileName);
Temp+=" ";
Temp+=FPrinterName.SubString(3,20);
int pos=Temp.LastDelimiter("\\");
if (pos !=-1)
Temp.Delete(pos,2);
Temp+=" ";
Temp+=this ->ThreadID;
Temp+=FileExt;
FileHandle=FileCreate(Temp);
Temp="Date\tHeure\tThreadId\tJobId\tComputer\tUser\tPrinter\tDocument\tPages\tSize";
Temp+="\r\n";
FileWrite(FileHandle,Temp.c_str(),Temp.Length());
} }


si le paramètre Activedebug est actif on crée un nouveau Fichier par Object TPrinterNotifier
nommé par concaténation du printerName et du ThreadID


2.k. Méthode Execute



la méthode Execute est le coeur de toute l'application,on trouve à l'intérieur l'appel aux fonctions
FindFirstPrinterChangeNotification et FindNextPrinterChangeNotification


PRINTER_NOTIFY_INFO *Pni; // déclaration des différentes Variables
unsigned long i;
char *pBuf;
unsigned long *adwData;
PRINTER_NOTIFY_OPTIONS Pob; // Pnobt contient
PRINTER_NOTIFY_OPTIONS_TYPE Pnobt[1];// 1 élément PRINTER_NOTIFY_OPTIONS_TYPE
unsigned short jobnf[10]; // Array JobInf
unsigned long ChangeReason;
i=0; jobnf[i++] = JOB_NOTIFY_FIELD_PRINTER_NAME; // attribution des
jobnf[i++] = JOB_NOTIFY_FIELD_MACHINE_NAME; // différentes valeurs
jobnf[i++] = JOB_NOTIFY_FIELD_USER_NAME; // devant être notifiées
jobnf[i++] = JOB_NOTIFY_FIELD_DOCUMENT;
jobnf[i++] = JOB_NOTIFY_FIELD_TOTAL_PAGES;
jobnf[i++] = JOB_NOTIFY_FIELD_TOTAL_BYTES;
Pnobt[0].Type = JOB_NOTIFY_TYPE; // type de notification
Pnobt[0].Count = i; // nombre d'éléments dans PField
Pnobt[0].pFields = jobnf; // assignation de jobnf
Pob.Version = 2; // Version toujours = à 2
Pob.Count = 1; // 1 élément PRINTER_NOTIFY_OPTIONS_TYPE
Pob.pTypes = Pnobt; // assignation de Pnobt
// FindFirstPrinterChangeNotification retourne un Handle sur un objet de
// notification initialisé avec PRINTER_CHANGE_ALL et la variable Pob
hNotifier = FindFirstPrinterChangeNotification( hPrinter,
PRINTER_CHANGE_ALL,
0,
&Pob);

// pas de notifier;;
while (!Terminated) // hNotifier égal au handle de
{ // l'object notification
if (!hNotifier) return ;
WaitForSingleObject(hNotifier, INFINITE);
// notification a eu lieu
if (!Terminated) // ordre de fin du thread ?
{
Pob.Flags = 0; //demande de refresh
// FindNextPrinterChangeNotification rempli le contenu du pointeur pointé par
// Pni avec les éléments de notifications (jobinf) ayant changé
FindNextPrinterChangeNotification(hNotifier,
&ChangeReason,
&Pob,
(void **)&Pni);

if (Pni != NULL) // si pointeur diférent de NULL
{
for (i=0; i<Pni->Count; i++) // Itération
{
pBuf =(char *) Pni->aData[i].NotifyData.Data.pBuf;
adwData = Pni->aData[i].NotifyData.adwData; // assigantion des résultats
FId=Pni->aData[i].Id;
switch (Pni->aData[i].Field) // itération dans la valeur
{ // des champs
case JOB_NOTIFY_FIELD_PRINTER_NAME:FPrinter=pBuf;
break ; // assignation des membres
case JOB_NOTIFY_FIELD_MACHINE_NAME:FComputer=pBuf; // de la classe
break ;
case JOB_NOTIFY_FIELD_USER_NAME: FUser=pBuf ;
break ;
case JOB_NOTIFY_FIELD_DOCUMENT:FDocument=pBuf;
break ;
case JOB_NOTIFY_FIELD_TOTAL_PAGES:FTotPages=adwData[0] ;
break ;
case JOB_NOTIFY_FIELD_TOTAL_BYTES:FSize=adwData[0];
break ;
}
} // plusieurs notifications peuvent avoir lieu succéssivement
// les champs ne sont pas tous remplis simultanément
// on doit tester si le nombre de pages est
// >=1 et que le champ Size soit >0
if (FDebugMode) PostLog();
if ((FSize>0)&&(FTotPages>=1))
PostMessage(Form1->Handle,WM_PrinterNotifier,FThreadID,0);
} // on poste le message au Thread VCL Principal (Form1)
FreePrinterNotifyInfo(Pni); // libération du Buffer
}
}
}
//---------------------------------------------------------------------------


on crée tout d'abord 4 variable

PRINTER_NOTIFY_INFO *Pni; // Buffer initialisé en retour de l'appel à FindNextPrinterChangeNotification
PRINTER_NOTIFY_OPTIONS Pob; // Pnobt contient
PRINTER_NOTIFY_OPTIONS_TYPE Pnobt[1];// 1 ÉlÉment PRINTER_NOTIFY_OPTIONS_TYPE
unsigned short jobnf[10];


on va ensuite initialiser le Tableau Jobnf avec les informations devant être notifiées


i=0;
jobnf[i++] = JOB_NOTIFY_FIELD_PRINTER_NAME; // attribution des
jobnf[i++] = JOB_NOTIFY_FIELD_MACHINE_NAME; // diffÉrentes valeurs
jobnf[i++] = JOB_NOTIFY_FIELD_USER_NAME; // devant Être notifiÉes
jobnf[i++] = JOB_NOTIFY_FIELD_DOCUMENT;
jobnf[i++] = JOB_NOTIFY_FIELD_TOTAL_PAGES;
jobnf[i++] = JOB_NOTIFY_FIELD_TOTAL_BYTES;


on initialise ensuite le tableau Pnobt[],ainsi que la variable Pob de type PRINTER_NOTIFY_OPTIONS


Pnobt[0].Type = JOB_NOTIFY_TYPE; // type de notification
Pnobt[0].Count = i; // nombre d'ÉlÉments dans PField
Pnobt[0].pFields = jobnf; // assignation de jobnf
Pob.Version = 2; // Version toujours = À 2
Pob.Count = 1; // 1 ÉlÉment PRINTER_NOTIFY_OPTIONS_TYPE
Pob.pTypes = Pnobt; // assignation de Pnobt


l'appel suivant à FindFirstPrinterChangeNotification va nous fournir un Handle qui sera initialisé par
windows lors d'une notification de Job
on attend cette notification avec la procédure WaitForSingleObject
dés la signalisation de l'évenemment le programme sort de WaitForSingleObject
On initialise le membre Flags de la variable Pob à true ce qui a pour effet de demander un refresh a Windows
l'appel suivant à FindNextPrinterChangeNotification initialise la Pointeur pni avec les éléments notificateurs



if (Pni != NULL) // si pointeur diférent de NULL
{
for (i=0; i<Pni->Count; i++) // Itération
{
pBuf =(char *) Pni->aData[i].NotifyData.Data.pBuf;
adwData = Pni->aData[i].NotifyData.adwData; // assigantion des résultats
FId=Pni->aData[i].Id;
switch (Pni->aData[i].Field) // itération dans la valeur
{ // des champs
case JOB_NOTIFY_FIELD_PRINTER_NAME:FPrinter=pBuf;
break ; // assignation des membres
case JOB_NOTIFY_FIELD_MACHINE_NAME:FComputer=pBuf; // de la classe
break ;
case JOB_NOTIFY_FIELD_USER_NAME: FUser=pBuf ;
break ;
case JOB_NOTIFY_FIELD_DOCUMENT:FDocument=pBuf;
break ;
case JOB_NOTIFY_FIELD_TOTAL_PAGES:FTotPages=adwData[0] ;
break ;
case JOB_NOTIFY_FIELD_TOTAL_BYTES:FSize=adwData[0];
break ;
}
} // plusieurs notifications peuvent avoir lieu succéssivement
// les champs ne sont pas tous remplis simultanément
// on doit tester si le nombre de pages est
// >=1 et que le champ Size soit >0
if (FDebugMode)PostLog();


Si le pointeur est non NULL on itère dans le buffer, on assigne les variables pBuf et adwData
il ne reste plus qu'a travers un switch/case a assigner les membres concernés

il reste un problème à régler dans le cas d'un document il se peut que l'on aie plusieurs notifications
de suite pour le même Job
par exemple une page supplémentaire arrive dans le spooler on devra donc filter les enregistrements recu par JobId

le premier filtre est simplement établi de la manière suivante un message est envoyé à la forme principale
uniquement si le nombres de pages est plus grand que 1 et que la taille du document soit supérieur à 0
en placant un <-debug> dans la ligne de commande l'étude des fichiers trace sera très utiles
pour comprendre la problématique de ces notifications multiples


if (FDebugMode)PostLog();
if ((FSize>0)&&(FTotPages>=1))
PostMessage(Form1->Handle,WM_PrinterNotifier,FThreadID,0);
} // on poste le message au Thread VCL Principal (Form1)
FreePrinterNotifyInfo(Pni); // libÉration du Buffer


2.l. méthode PostLog


void __fastcall TPrinterNotifier::PostLog()
{ char TimeBuffer[15];
SYSTEMTIME mSystime,mLocaTime;
TIME_ZONE_INFORMATION mTimeZone={0};
GetSystemTime(&mSystime);
GetTimeZoneInformation(&mTimeZone);
SystemTimeToTzSpecificLocalTime(&mTimeZone,
&mSystime,
&mLocaTime);
sprintf(TimeBuffer,"%d:%d:%d:%d",mLocaTime.wHour,
mLocaTime.wMinute,
mLocaTime.wSecond,
mLocaTime.wMilliseconds);
AnsiString Temp;
AnsiString Separator="\t";
Temp+=Date();
Temp+=Separator;
Temp+=TimeBuffer;
Temp+=Separator;
Temp+=FThreadID;
Temp+=Separator;
Temp+=FId;
Temp+=Separator;
Temp+=FComputer;
Temp+=Separator;
Temp+=FUser;
Temp+=Separator;
Temp+=FPrinter;
Temp+=Separator;
Temp+=FDocument;
Temp+=Separator;
Temp+=FTotPages;
Temp+=Separator;
Temp+=FPages;
Temp+=Separator;
Temp+=FSize;
Temp+="\r\n";
FileWrite(FileHandle,Temp.c_str(),Temp.Length());
}


dans cette méthode on crée un enregistrement par notification avec l'heure en format hh:mm:ss:ms



2.m. destructeur de la classe TPrinterNotifier


__fastcall TPrinterNotifier::~TPrinterNotifier()
{
if (Suspended) Resume();
SetEvent(hNotifier);// on sort du WaitforSingleObjet
Terminate(); // ordre de sortie du Thread (Terminated=true)
if (hNotifier) WaitFor(); // Attente de la fin du Thread
FindClosePrinterChangeNotification(hNotifier); //Libération du Handle hNotifier
ClosePrinter(hPrinter); // libération du Handle hPrinter
if (FDebugMode)FileClose(FileHandle);

}

Retour a la Classe Principale
on a vu dans la classe précédente que l'object PrinterNotifier comuniquait avec le thread principal
par message interposé,ainsi que de la nécessité de supprimer les notifications identiques pour le même Job
on créera donc une nouvelle classe qui aura pour fonctions de supprimer les multiples notifications



2.n. gestionnaire de message WM_PrinterNotifier


void __fastcall TForm1::GPrinterNotifier(TMessage &msg)
{ // FPrintNotifier contient le Ptr sur le Thread qui a transmis le Msg
FSortJobThread->Suspend(); // on suspend le fonction du SortedThread
FPrintNotifier=(TPrinterNotifier*)FThreadList->Items[msg.WParam];
FPrintNotifier->Suspend(); //on suspend le fonctionnemnt du thread émetteur
TJobInfoRecord *NewRec= new TJobInfoRecord();
NewRec->jDate=Date();
NewRec->jtime=Time();
NewRec->jID=FPrintNotifier->Id;
NewRec->jComputer=FPrintNotifier->Computer;
NewRec->jPrinter=FPrintNotifier->Printer;
NewRec->jUser=FPrintNotifier->User;
NewRec->jDocument=FPrintNotifier->Document;
NewRec->jPages=FPrintNotifier->TotPages;
NewRec->jSize=FPrintNotifier->Size;
FJobListRec->Add((TJobInfoRecord*)NewRec);
if (FDebug)WriteLog(NewRec,msg.WParam);
FPrintNotifier->Resume();
FSortJobThread->Resume();
}
//---------------------------------------------------------------------------


FSortJobThread est un pointeur sur le classe de tri que nous créerons ultérieurement
on stoppe le fonctionnement de ce Thread
FPrintNotifier contient le Ptr sur le Thread qui a transmis le Msg, on crée ensuite un nouvel objet de type
TJobInfoRecord et on initialise ses champs avec les property du Thread transmetteur
si mode debug on écrit dans le LogFile,on ajoute l'object de Type TJobInfoRecord dans une TList qui sera triée par
la classe de tri


2.o. gestionnaire de message WM_SortedListNotifier


le message WM_SortedListNotifier est généré par la classe de tri


void __fastcall TForm1::GUpdateListView(TMessage &msg)
{
FSortJobThread->Suspend();
ListView1->Items->BeginUpdate();
// début du traitement de la listView
ListView1->Items->Clear();
TListItem* mListItem;
TJobInfoRecord *First;
for (int n=0;n<msg.WParam;n++)
{
First=(TJobInfoRecord*)FJobListRec->Items[n];
mListItem=ListView1->Items->Add();
mListItem->Caption=First->jDate;
mListItem->SubItems->Add(First->jtime);
mListItem->SubItems->Add(First->jID);
mListItem->SubItems->Add(First->jComputer);
mListItem->SubItems->Add(First->jUser);
mListItem->SubItems->Add(First->jPrinter);
mListItem->SubItems->Add(First->jDocument);
mListItem->SubItems->Add(First->jPages);
if (First->jSize >1024)
{
AnsiString Size= First->jSize 1024; // test pour affichage
mListItem->SubItems->Add(Size+ " Ko"); // en octets ou en Ko
}
else
mListItem->SubItems->Add(First->jSize);
}
ListView1->CustomSort(CustomSortProc,0);
ListView1->Items->EndUpdate();
FSortJobThread->Resume();
}
//---------------------------------------------------------------------------


on suspend d'abord le fonctionnement de la classe de tri
on empêche la mise à jour de l'affichage dans la list View
on efface le contenu de la list View
on remplit itère dans la FJobListRec pour remplir la ListView
affichage en Ko de la taille du document
on trie la liste en sens inverse
on affiche le résultat
on reprend le fonctionnement de la classe de tri



2.p. Classe de Tri



cette classe a pour but d'éliminer les enregistrements en double


//---------------------------------------------------------------------------
#ifndef SortedJobThreadH
#define SortedJobThreadH
//---------------------------------------------------------------------------
#include <Classes.hpp>
#define WM_SortedListNotifier WM_APP+2010
//---------------------------------------------------------------------------
class TSortJobThread : public TThread
{
private :
TList* FSortedList;
int FCounter;
int FThreadNbr;
protected :
void __fastcall Execute();
public :
__fastcall TSortJobThread(bool CreateSuspended): TThread(CreateSuspended){};
__fastcall TSortJobThread(bool CreateSuspended, TList* JobList,int ThreadNbr);
__fastcall ~TSortJobThread();
};
//---------------------------------------------------------------------------
#endif


on utilise la même méthode de communication avec le Thread VCl Principal ce qui nécessite la création d'un message
WM_SortedListNotifier
on déclare un nouveau constructeur avec comme paramètre supplémentaire un pointeur sur la JobList


__fastcall TSortJobThread::TSortJobThread(bool CreateSuspended,
TList* JobList,
int ThreadNbr)
: TThread(CreateSuspended)
{
FSortedList= JobList;
FCounter=JobList->Count;
FThreadNbr=ThreadNbr;
}


2.q. Méthode Execute


void __fastcall TSortJobThread::Execute()
{
int Iter=0,FirstPos=1;
TJobInfoRecord *First,*Second;
while (!Terminated)
{
while (Iter<=(FSortedList->Count-2)) // premier élément est égal au second
{ // on echange les éléments dans la liste,puis on efface le premoier élément
if (Terminated) break ; // on teste si on trouve un doublon dans la liste
First=(TJobInfoRecord*)FSortedList->Items[Iter]; // n° Test (elem* elem/2)-elem;
// teste en boucle avec tous les élément de la liste
while (FirstPos<FSortedList->Count)
{
if (Terminated) break ;
Second=(TJobInfoRecord*)FSortedList->Items[FirstPos];
if ((First->jID== Second->jID)&&(First->jDate==Second->jDate )) // même Id et Même Jour
{
FSortedList->Exchange(Iter,FirstPos);
FSortedList->Delete(FirstPos);
}
else
FirstPos++;
}
Sleep(2);
Iter++;
FirstPos=Iter+1;
} if (FCounter !=FSortedList->Count)
{
PostMessage(Form1->Handle,WM_SortedListNotifier,FSortedList->Count,0);
FCounter=FSortedList->Count;
}
Iter= FCounter-(FThreadNbr*2);
if (Iter<0)Iter=0;
FirstPos=Iter+1;
Sleep(2);
}
}
//---------------------------------------------------------------------------


principe de fonctionnement

tant que le premier élément de la liste est égal au second élément on echange les éléments trouvés sinon on recommence
a partir du 2 em élément ceci jusqu'a la fin de la liste si le champ Counter est différent du nombre d'éléments de la liste triée on envoye un message au ThreadVcl
pour raffraichir la List View le champ Counter prend la valeur du nombre d'éléments de la liste triée et le cycle repend dès le début



Voir également mes autres articles :
Utilisation des Files Mapping sous C++Builder C++ Builder
La technique des PatchFiles C++ Builder
Maîtrisez les files d'impression sous windows C++ Builder
Travailler avec les Interfaces en C++Builder Partie 1 C++ Builder
Composant de gestion des ports d'imprimantes C++ Builder
Programme de Test Version ActiveX du composant TDLIoport Delphi
Sans oublier :
la FAQ C++ Builder par Geronimo
la FAQ C
la FAQ C++

Ce document est issu de http://www.developpez.com et reste la propriété exclusive de son auteur.
La copie, modification et/ou distribution par quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.

Responsables bénévoles de la rubrique Accueil : Nicolas Vallée (gorgonite) et Guillaume Rossolini (Yogui) - Contacter par EMail :
Vos questions techniques : forum d'entraide Accueil - Publiez vos articles, tutoriels et cours
et rejoignez-nous dans l'équipe de rédaction du club d'entraide des développeurs francophones
Nous contacter - Copyright © 2000-2008 www.developpez.com - Legal informations.