pvbrowser, pvb® - The Process Visualiation Browser.

  • English
  • Deutsch

Anatomie eines pvserver in C/C++

Im Rahmen der Einführung haben wir schon einen pvserver in C/C++ gesehen. Nun wollen wir den Quellcode eines Beispiel-pvserver analysieren. Wir verwenden den pvserver in pvbaddon/templates/weblayout .

Beachten Sie, dass der folgende Quellcode nicht manuell geschrieben wird. Statt dessen wird dieser Rahmen automatisch von der pvdevelop IDE generiert. Später können Sie Ihren eigenen Code in das Rahmenprogramm einfügen aber einige Teile werden von pvdevelop aktualisiert werden. Besonders gilt dies für die Definition der Widgets.

main()-Programm in main.cpp

Mit einem #ifdef entscheiden wir, ob ein Multi-Threaded pvserver oder ein pvserver, der über (x)inetd gestartet wird, gebaut werden soll. Im Falle des Multi-Threaded pvserver wird mit pvAccept() auf Klienten gewartet. Immer wenn ein pvbrowser Klient eine Verbindung herstellt wird ein neuer Thread zur Bedienung des Klienten mit pvCreateThread() erzeugt.

#ifdef USE_INETD
int main(int ac, char **av)
{
PARAM p;

  pvInit(ac,av,&p);
  /* here you may interpret ac,av and set p->user to your data */
  pvMain(&p);
  return 0;
}
#else  // multi threaded server
int main(int ac, char **av)
{
PARAM p;
int   s;

  pvInit(ac,av,&p);
  /* here you may interpret ac,av and set p->user to your data */
  while(1)
  {
    s = pvAccept(&p);
    if(s != -1) pvCreateThread(&p,s);
    else        break;
  }
  return 0;
}
#endif
    

Schritte

pvMain() in main.cpp

Die Bedienung eines einzelnen Klienten startet in pvMain(), die durch pvCreateThread() aufgerufen wird. Darin werden in einer Endlosschleife alle Masken aufgerufen, die wir entworfen haben. In diesem Fall gibt es nur mask1. Wir zeigen die Maske durch den Aufruf von show_mask1() an. Wenn der Klient die Verbindung beendet wird auch unser Thread beendet.

int pvMain(PARAM *p)
{
int ret;

  pvSetCaption(p,"pvs");
  pvResize(p,0,1280,1024);
  //pvScreenHint(p,1024,768); // this may be used to automatically set the zoomfactor
  ret = 1;
  pvGetInitialMask(p);
  if(strcmp(p->initial_mask,"mask1") == 0) ret = 1;

  while(1)
  {
    if(trace) printf("show_mask%d\n", ret);
    switch(ret)
    {
      case 1:
        pvStatusMessage(p,-1,-1,-1,"mask1");
        ret = show_mask1(p);
        break;
      default:
        return 0;
    }
  }
}
    

show_mask1() in mask1.cpp

In show_mask1() wird defineMask() aufgerufen, wo der automatisch generierte Quellcode der graphisch entworfenen Maske steht. Danach wird slotInit() aufgerufen, wo die lokalen Daten der Maske (Benutzerdefinierte Struktur DATA in mask1_slots.h) initialisiert werden. Dann geht show_mask1() in eine Ereignisschleife und ruft die slotFunktionen auf, die die vom pvbrowser Klienten gesendeten Ereignisse behandeln. Wenn der Klient kein Ereignis sendet, wird ein NULL_EVENT erzeugt. Dort können zyklische Aktualisierungen Ihrer Maske erfolgen.

int show_mask1(PARAM *p)
{
  DATA d;
  char event[MAX_EVENT_LENGTH];
  char text[MAX_EVENT_LENGTH];
  char str1[MAX_EVENT_LENGTH];
  int  i,w,h,val,x,y,button,ret;
  float xval, yval;

  defineMask(p);
  //rlSetDebugPrintf(1);
  if((ret=slotInit(p,&d)) != 0) return ret;
  readData(&d); // from shared memory, database or something else
  showData(p,&d);
  while(1)
  {
    pvPollEvent(p,event);
    switch(pvParseEvent(event, &i, text))
    {
      case NULL_EVENT:
        readData(&d); // from shared memory, database or something else
        showData(p,&d);
        if((ret=slotNullEvent(p,&d)) != 0) return ret;
        break;
      case BUTTON_EVENT:
        if(trace) printf("BUTTON_EVENT id=%d\n",i);
        if((ret=slotButtonEvent(p,i,&d)) != 0) return ret;
        break;
      case BUTTON_PRESSED_EVENT:
      // snip ...
    

Generierter Quellcode, der mask1 aufbaut

Der enum listet alle Widgets auf, die entworfen wurden. Sie verwenden diese Namen in Ihrem slotFunktionen, um ein Widget zu adressieren. Dann folgen die toolTip und whatsThis Eigenschaften der Widgets. Auch der widgetType wird aufgeführt. In generated_defineMask() generiert pvdevelop den notwendigen Quellcode, um den Widget Baum im pvbrowser Klienten aufzubauen.

// _begin_of_generated_area_ (do not edit -> use ui2pvc) -------------------

// our mask contains the following objects
enum {
  ID_MAIN_WIDGET = 0,
  upperWidget,
  leftWidget,
  centerWidget,
  rightWidget,
  obj1,
  iCenter,
  iUp,
  iDown,
  iLeft,
  iRight,
  sliderZoom,
  layout1,
  layout2,
  ID_END_OF_WIDGETS
};

  static const char *toolTip[] = {
  "",
  "",
  "",
  "",
  "",
  "",
  "",
  "",
  "",
  "",
  "",
  "",
  ""};

  static const char *whatsThis[] = {
  "",
  "",
  "",
  "test.svg",
  "",
  "",
  "1center.png",
  "1uparrow.png",
  "1downarrow.png",
  "1leftarrow.png",
  "1rightarrow.png",
  "",
  ""};

  static const int widgetType[ID_END_OF_WIDGETS+1] = {
  0,
  TQTextBrowser,
  TQTextBrowser,
  TQDraw,
  TQGroupBox,
  TQLabel,
  TQImage,
  TQImage,
  TQImage,
  TQImage,
  TQImage,
  TQSlider,
  -1 };

static int generated_defineMask(PARAM *p)
{
  int w,h,depth;

  if(p == NULL) return 1;
  w = h = depth = strcmp(toolTip[0],whatsThis[0]);
  if(widgetType[0] == -1) return 1;
  if(w==h) depth=0; // fool the compiler
  pvStartDefinition(p,ID_END_OF_WIDGETS);

  pvQTextBrowser(p,upperWidget,0);
  pvSetGeometry(p,upperWidget,5,5,945,200);
  pvSetFont(p,upperWidget,"Sans Serif",10,0,0,0,0);
  pvSetMinSize(p,upperWidget,0,200);
  pvSetMaxSize(p,upperWidget,99999,200);

  pvQTextBrowser(p,leftWidget,0);
  pvSetGeometry(p,leftWidget,5,215,175,445);
  pvSetFont(p,leftWidget,"Sans Serif",10,0,0,0,0);
  pvSetMaxSize(p,leftWidget,200,99999);

  pvQDraw(p,centerWidget,0);
  pvSetGeometry(p,centerWidget,190,215,580,425);
  pvSetFont(p,centerWidget,"Sans Serif",10,0,0,0,0);
  pvSetWhatsThis(p,centerWidget,"test.svg");
  pvSetMinSize(p,centerWidget,100,100);

  pvQGroupBox(p,rightWidget,0,-1,HORIZONTAL,"Tools");
  pvSetGeometry(p,rightWidget,780,210,170,450);
  pvSetFont(p,rightWidget,"Sans Serif",10,0,0,0,0);
  pvSetMaxSize(p,rightWidget,200,99999);

  pvQLabel(p,obj1,rightWidget);
  pvSetGeometry(p,obj1,10,150,145,35);
  pvSetText(p,obj1,"Put your tools here");
  pvSetFont(p,obj1,"Sans Serif",10,0,0,0,0);

  pvDownloadFile(p,"1center.png");
  pvQImage(p,iCenter,rightWidget,"1center.png",&w,&h,&depth);
  pvSetGeometry(p,iCenter,35,65,22,22);
  pvSetFont(p,iCenter,"Sans Serif",10,0,0,0,0);
  pvSetWhatsThis(p,iCenter,"1center.png");

  pvDownloadFile(p,"1uparrow.png");
  pvQImage(p,iUp,rightWidget,"1uparrow.png",&w,&h,&depth);
  pvSetGeometry(p,iUp,35,40,22,22);
  pvSetFont(p,iUp,"Sans Serif",10,0,0,0,0);
  pvSetWhatsThis(p,iUp,"1uparrow.png");

  pvDownloadFile(p,"1downarrow.png");
  pvQImage(p,iDown,rightWidget,"1downarrow.png",&w,&h,&depth);
  pvSetGeometry(p,iDown,35,90,22,22);
  pvSetFont(p,iDown,"Sans Serif",10,0,0,0,0);
  pvSetWhatsThis(p,iDown,"1downarrow.png");

  pvDownloadFile(p,"1leftarrow.png");
  pvQImage(p,iLeft,rightWidget,"1leftarrow.png",&w,&h,&depth);
  pvSetGeometry(p,iLeft,10,65,22,22);
  pvSetFont(p,iLeft,"Sans Serif",10,0,0,0,0);
  pvSetWhatsThis(p,iLeft,"1leftarrow.png");

  pvDownloadFile(p,"1rightarrow.png");
  pvQImage(p,iRight,rightWidget,"1rightarrow.png",&w,&h,&depth);
  pvSetGeometry(p,iRight,60,65,22,22);
  pvSetFont(p,iRight,"Sans Serif",10,0,0,0,0);
  pvSetWhatsThis(p,iRight,"1rightarrow.png");

  pvQSlider(p,sliderZoom,rightWidget,10,200,1,10,Vertical);
  pvSetGeometry(p,sliderZoom,125,30,25,100);
  pvSetFont(p,sliderZoom,"Sans Serif",10,0,0,0,0);

  pvQLayoutHbox(p,ID_MAIN_WIDGET,-1);

  pvQLayoutVbox(p,layout1,-1);

  pvQLayoutHbox(p,layout2,-1);

  pvAddWidgetOrLayout(p,ID_MAIN_WIDGET,layout1,-1,-1);
  pvAddWidgetOrLayout(p,layout1,upperWidget,-1,-1);
  pvAddWidgetOrLayout(p,layout1,layout2,-1,-1);
  pvAddWidgetOrLayout(p,layout2,leftWidget,-1,-1);
  pvAddWidgetOrLayout(p,layout2,centerWidget,-1,-1);
  pvAddWidgetOrLayout(p,layout2,rightWidget,-1,-1);

  pvEndDefinition(p);
  return 0;
}

// _end_of_generated_area_ (do not edit -> use ui2pvc) ---------------------
    

Was wir selbst programmieren müssen

Auch das Skelett dieses Quellcodes wurde von pvdevelop generiert, als wir die Maske eingefügt haben. Ihre Aufgabe ist nun den Inhalt der slotFunktionen zu schreiben.

In der benutzerdefinierten Struktur DATA haben wir eine Klasse von rllib eingefügt, die es uns erlaubt SVG-Grafiken zu animieren. Die kleine Hilfsfunktion drawSVG1 () wurde von Hand geschrieben. Der Rest des Codes zeigt die Logik, die wir geschrieben haben.

typedef struct // (todo: define your data structure here)
{
  rlSvgAnimator svgAnimator;
}
DATA;

static int drawSVG1(PARAM *p, int id, DATA *d)
{
  if(d == NULL) return -1;
  if(d->svgAnimator.isModified == 0) return 0;
  gBeginDraw(p,id);
  d->svgAnimator.writeSocket();
  gEndDraw(p);
  return 0;
}

static int slotInit(PARAM *p, DATA *d)
{
  if(p == NULL || d == NULL) return -1;
  //memset(d,0,sizeof(DATA));

  // load HTML
  pvDownloadFile(p,"upperWidget.html");
  pvDownloadFile(p,"leftWidget.html");
  pvSetSource(p,upperWidget,"upperWidget.html");
  pvSetSource(p,leftWidget,"leftWidget.html");

  // load SVG
  d->svgAnimator.setSocket(&p->s);
  d->svgAnimator.setId(centerWidget);
  d->svgAnimator.read("test.svg");

  // keep aspect ratio of SVG
  pvSetZoomX(p, centerWidget, -1.0f);
  pvSetZoomY(p, centerWidget, -1.0f);

  // draw SVG
  drawSVG1(p,centerWidget,d);

  // download icons
  pvDownloadFile(p,"1center.png");
  pvDownloadFile(p,"1uparrow.png");
  pvDownloadFile(p,"1downarrow.png");
  pvDownloadFile(p,"1leftarrow.png");
  pvDownloadFile(p,"1rightarrow.png");
  pvDownloadFile(p,"1center2.png");
  pvDownloadFile(p,"1uparrow2.png");
  pvDownloadFile(p,"1downarrow2.png");
  pvDownloadFile(p,"1leftarrow2.png");
  pvDownloadFile(p,"1rightarrow2.png");

  // set sliderZoom to 100 percent
  pvSetValue(p,sliderZoom,100);

  pvHtmlOrSvgDump(p,upperWidget,"dump.html");
  pvClientCommand(p,"html","dump.html");
  return 0;
}

static int slotNullEvent(PARAM *p, DATA *d)
{
  if(p == NULL || d == NULL) return -1;
  return 0;
}

static int slotButtonEvent(PARAM *p, int id, DATA *d)
{
  if(p == NULL || id == 0 || d == NULL) return -1;
  if     (id == iCenter)
  {
    pvSetImage(p,iCenter,"1center2.png");
    d->svgAnimator.zoomCenter(1.0f);
    d->svgAnimator.setMouseXY0(0,0);
    d->svgAnimator.setXY0(0.0f,0.0f);
    d->svgAnimator.moveMainObject(0,0);
    drawSVG1(p,centerWidget,d);
    pvSetValue(p,sliderZoom,100);
  }
  else if(id == iUp)     
  {
    pvSetImage(p,iUp,"1uparrow2.png");
    d->svgAnimator.setMouseXY0(0,0);
    d->svgAnimator.moveMainObject(0,-DELTA);
    drawSVG1(p,centerWidget,d);
  }
  else if(id == iDown)   
  {
    pvSetImage(p,iDown,"1downarrow2.png");
    d->svgAnimator.setMouseXY0(0,0);
    d->svgAnimator.moveMainObject(0,DELTA);
    drawSVG1(p,centerWidget,d);
  }
  else if(id == iLeft)   
  {
    pvSetImage(p,iLeft,"1leftarrow2.png");
    d->svgAnimator.setMouseXY0(0,0);
    d->svgAnimator.moveMainObject(-DELTA,0);
    drawSVG1(p,centerWidget,d);
  }
  else if(id == iRight)  
  {
    pvSetImage(p,iRight,"1rightarrow2.png");
    d->svgAnimator.setMouseXY0(0,0);
    d->svgAnimator.moveMainObject(DELTA,0);
    drawSVG1(p,centerWidget,d);
  }
  return 0;
}

static int slotButtonPressedEvent(PARAM *p, int id, DATA *d)
{
  if(p == NULL || id == 0 || d == NULL) return -1;
  return 0;
}

static int slotButtonReleasedEvent(PARAM *p, int id, DATA *d)
{
  if(p == NULL || id == 0 || d == NULL) return -1;
  if     (id == iCenter) pvSetImage(p,iCenter,"1center.png");
  else if(id == iUp)     pvSetImage(p,iUp,    "1uparrow.png");
  else if(id == iDown)   pvSetImage(p,iDown,  "1downarrow.png");
  else if(id == iLeft)   pvSetImage(p,iLeft,  "1leftarrow.png");
  else if(id == iRight)  pvSetImage(p,iRight, "1rightarrow.png");
  return 0;
}

// snip ...
    

Gültigkeitsbereich von Variablen

Die benutzerdefinierte Struktur DATA ist privat für eine Maske und den jeweils verbundenen Klienten. Die Instanz der Struktur wird in show_mask1() angelegt. Sie können die Variablen in DATA mit privaten Variablen einer C++ Klasse vergleichen, obwohl dies ANSI-C Technik ist.

Wenn Sie Variablen haben möchten, die in allen Masken verfügbar sind aber privat zu einem angeschlossenen Klienten, müssen Sie PARAM p->user = &ihre_datenstruktur; setzen. Die Instanz ihre_datenstruktur muss innerhalb pvMain() definiert werden.

Wenn Sie Variablen haben möchten, die global für alle Masken und Klienten sein sollen, können sie oben in main.cpp als IHRE_DATEN ihre_daten; angelegt werden. Auf diese Daten wird dann in allen Masken mit extern IHRE_DATEN ihre_daten; Bezug genommen. Bitte beachten Sie, dass Sie möglicherweise einen Mutex verwenden müssen, um den Zugriff auf diese Variablen zu steuern, während mehrere Threads für mehrere Kunden ausgeführt werden.