ILE permet d'utiliser en RPG 4 des fonctions écrites ou destinées au C/400. le prototypage RPG 4 va permettre de réaliser cela. correspondance des paramètres ------------------------------ quelques rappels EN C, le passage de paramètres se fait par défaut par valeur EN RPG le passage de paramètres se fait par défaut par référence (on passe un pointeur, comme en C par int*), pour un passage de paramètre par valeur, indiquer VALUE EN C, les chaines de caractères sont terminées par x'00', en RPG elles sont à taille fixe, sans terminaison, l'option OPTIONS(*STRING) assure la correspondance, et si la paramètre envoyé est une chaine , GAP stocke l'information en mémoire temporaire, ajoute un zéro hexadécimal et transmet l'adresse (le pointeur). |
+-------------+------------+--------------------------------------------+ + C + RPG 4 + Mots-clés + +-------------+------------+--------------------------------------------+ + int, long + 10I 0 + VALUE + +-------------+------------+--------------------------------------------+ + unsigned int+ 10U 0 + VALUE + +-------------+------------+--------------------------------------------+ + double + 8F 0 + VALUE + +-------------+------------+--------------------------------------------+ + char + 1A + VALUE (il s'agit d'UN octet seul) + +OU ----------+------------+--------------------------------------------+ + char + 10U 0 + VALUE + +-------------+------------+--------------------------------------------+ + short + 10i 0 + VALUE + +-------------+------------+--------------------------------------------+ + int* + 10i 0 + + +-------------+------------+--------------------------------------------+ + unsigned* + 10U 0 + + +-------------+------------+--------------------------------------------+ + double* + 8F 0 + + +-------------+------------+--------------------------------------------+ |
+-------------+------------+--------------------------------------------+ + C + RPG 4 + Mots-clés + +-------------+------------+--------------------------------------------+ + char* + xxxA + OPTIONS(*STRING) + +OU ----------+------------+--------------------------------------------+ + char* + * + VALUE (il faudra utiliser %ADDR().) + +-------------+------------+--------------------------------------------+ + void* + * + VALUE + +(pointeur) + + + +-------------+------------+--------------------------------------------+ + (*) + * + VALUE PROCPTR + +(fonction) + + + +-------------+------------+--------------------------------------------+ L'utilisation du mot-clé CONST sera souvent un plus pour les données élémentaires, par exemple si une fonction C admet un integer, le prototype .................................................................. : D mafonctionC PR extproc('maFonctionC') : : D 10i 0 CONST : :................................................................: permet d'utiliser du décimal paquet, le compilateur assurant la conversion. |
par exemple, la définition suivante double cos(double) se traduit en RPG , par : .................................................................. : D cosinus PR 8F 0 extproc('cos') : : D 8F 0 VALUE : :................................................................: et celle ci (utilisant des chaînes) char* strcpy (char* str1, str2) se traduit en RPG , par : .................................................................. : D stringcopy PR * extproc('strcpy') : : D str1 25A OPTIONS(*STRING) : : D str2 25A OPTIONS(*STRING) : :................................................................: |
Exemple d'un pgm complet utilisant atoi (alpha -> int) et atof (->flot) ----------------------------------------------------------------------- H bnddir('QC2LE') DFTACTGRP(*NO) ACTGRP(*CALLER) D atoi pr 10i 0 extproc('atoi') D num * options(*string) value D atof pr 8f extproc('atof') D num * options(*string) value D i s 10i 0 D p s 13p 7 C movel '-100 ' num 6 C eval i = atoi(%trim(num)) * * I = -100 C eval(h) p = atof(%trim(num)) * * P = -000100.0000000 C movel '-5.67 ' num 6 C eval(h) p = atof(%trim(num)) * * P = -000005.6700000 return |
Pour une valeur retour de type chaine, nous avons deux possibilités : 1/ baser une variable sur le pointeur recu : .................................................................. : D ptr S * : : D chaine S 50A BASED(ptr) : : : : /free : : ptr = stringcopy(chaine1 : chaine2) ; : : if chaine = *blanks; : :................................................................: 2/ Utiliser la fonction RPG %str() .................................................................. : D ptr S * : : D chaine S 50A : : : : /free : : ptr = stringcopy(chaine1 : chaine2) ; : : eval chaine = %str(ptr) : :................................................................: |
Certaines fonctions utilisent des variables globales, par exemple system() H bnddir('QC2LE') DFTACTGRP(*NO) ACTGRP(*CALLER) D Exec pr 10i 0 Extproc('system') D cmd * options(*string) value D ErrMsgID S 7 IMPORT('_EXCP_MSGID') D retour S 10I O /free retour = Exec('DLTSPLF *SELECT'); if retour > 0; if ErrMsgID = 'CPF....' ; .../... /end-free |
Tout ceci permet d'utiliser les sockets IP, routines de base du protocole, destinées à établir un dialogue réseau de programme à programme. Un socket est au réseau ce qu'un fichier est au disque dur ==> un niveau d'abstraction Vous devez être familier avec les concepts suivants -Protocole : TCP/UDP fonctionnant tous les deux sur IP ou ICMP -Adresse IP: identifiant réseau d'un poste (par exemple 192.168.1.2) -DNS : mechanisme permettant d'établir la correspondance entre un nom usuel et une adresse IP -Port : l'adresse IP identifant le système, le n° de port identifie l'application (sur un serveur à l'écoute, souvent < 1024) la connexion (sur un client, port dynamique > 1024) -socket : flux (comme un fichier stream) représentant le réseau |
inet_addr() convertit une adresse IP du format "clair", en binaire format utilisé par les sockets .................................................................. : D inet_addr PR 10U 0 extproc('inet_addr') : : D char_addr * VALUE option(*string) : :................................................................: gethostbyname retrouve l'adresse IP correspondant au nom de host .................................................................. : D gethostbyname PR * extproc('gethostbyname'): : D host_name * VALUE option(*string) : :................................................................: il faut renseigner le fichier host(CFGTCP/10) ou le serveur DNS(CFGTCP/12) |
Exemple : /copy socket_H D user_addr s 200A D addr s 10U 0 **DS en retour de GetHostByName (déja déclarée dans socket_H) D*p_hostent S * D*hostent DS Based(p_hostent) D* h_name * D* h_aliases * D* h_addrtype 5I 0 D* h_length 5I 0 D* h_addrlist * D*p_h_addr S * Based(h_addrlist) D*h_addr S 10U 0 Based(p_h_addr) Rappel : avec le mot-clé BASED la variable recouvre l'adresse mémoire indiquée par le pointeur passé en paramètre. ici, il s'agit d'un pointeur basé sur un pointeur !! |
Utilisation /free addr = inet_addr(%trimr(user_addr)); if (addr = INADDR_NONE); p_hostent = gethostbyname(%trimr(user_addr)); if (p_hostent = *NULL); DSPLY 'Host lookup failed.' return; else; addr = h_addr; endif; endif; /end-free toutes les constantes (INADDR_NONE) sont fournies dans SOCKET_H |
getservbyname retrouve le port utilisé pour un service donné .................................................................. : D getservbyname PR * extproc('getservbyname'): : D service_name * VALUE option(*string) : : D protocolname * VALUE option(*string) : :................................................................: D*p_servent S * D*servent DS Based(p_servent) D* s_name * D* s_aliases * D* s_port 10I 0 D* s_proto * /free p_servent = getservbyname('smtp': 'tcp'); if (p_servent <> *NULL); port = s_port; else; port = 25; endif; |
Ensuite vous devez : 1/ créer le socket 2/ vous connecter au système distant Création d'un socket .................................................................. : D socket PR 10I 0 Extproc('socket') : : D AddrFamilly 10I 0 Value : : D SocketType 10I 0 Value : : D Protocol 10I 0 Value : .................................................................. AddrFamilly : AF_INET (2) = IP/V4 AF_INET6 = IP/V6 (non traité dans ce cours) SocketType : SOCK_STREAM (1) = TCP SOCK_DGRAM (2) = UDP Protocol : IPPROTO_IP (O) = Choisir le protocole en fonction du type (paramètre précédent) |
Connexion .................................................................. : D connect PR 10I 0 Extproc('connect') : : D Sock_Desc 10I 0 Value : : D P_sockAddr * Value : : D AddressLen 10I 0 Value : .................................................................. le 1er paramètre est le descripteur retourné par l'API "socket" le 2ème est un pointeur (%ADDR) vers une structure décrivant le socket D p_sockaddr S * D sockaddr_in DS based(p_sockaddr) D sin_Family 5I 0 D sin_Port 5U 0 D sin_addr 10U 0 D sin_zero 8A le troisième indique la longueur (%SIZE) du 2eme |
Exemple : D s s 10I 0 D connto ds likeds(sockaddr_in) /free s = socket(AF_INET: SOCK_STREAM: IPPROTO_TCP); if (s = -1); DSPLY 'socket failed !'; return; endif; connto = *ALLx'00'; connto.sin_family = AF_INET; // toujours socket_H connto.sin_addr = addr; // inet_addr ou gethostbyname connto.sin_port = port; // getservbyname if ( connect(s: %addr(connto): %size(connto)) = -1 ); callp close(s); DSPLY 'Connect failed !'; return; endif; |
Envoi/Réception .................................................................. : D Send PR 10I 0 Extproc('send') : : D Sock_Desc 10I 0 Value : : D P_buffer * Value : : D BufferLen 10I 0 Value : : D flag 10I 0 Value : .................................................................. .................................................................. : D Recv PR 10I 0 Extproc('recv') : : D Sock_Desc 10I 0 Value : : D P_buffer * Value : : D BufferLen 10I 0 Value : : D flag 10I 0 Value : .................................................................. Sock_Desc : le descripteur retourné par "socket" P_buffer : pointeur vers les données à lire/à écrire attention à la conversion EBCDIC/ASCII si besoin (autre OS) BufferLen : longueur (%size) du buffer flag : ne sert pas transmettre à 0 |
Exemple d'envoi : D cmd s 10 D lencmd S 10i 0 /free cmd = 'HEURE' ; lencmd = send(s : %addr(cmd) : %len(%trimr(cmd)) : 0); if lencmd < %len(%trimr(cmd)) ; DSPLY 'Erreur lors de l''envoi'; endif; /end-free |
Exemple de réception (chaîne de 6) : D temp s 10 D len S 10i 0 D reponse S 6 varying /free reponse = ' '; //on est pas sur que le 6 octets arrivent dou %len(reponse) = 6; // d'un coup (suivant charge réseau) len = recv(s : %addr(temp) : %size(temp) : 0); if len = -1; DSPLY 'Erreur lors de la réception ; endif; if len = 0; //plus rien leave; endif; reponse = reponse + %subst(temp : 1 : len); enddo /end-free |
Enfin, le socket doit être fermé explicitement, la fin de pgm ne suffit pas. un socket ne peut jamais être réutilisé (sauf option explicite) .................................................................. : D Close PR 10I 0 Extproc('close') : : D Sock_Desc 10I 0 Value : .................................................................. retourne -1 en cas d'erreur, 0 dans le cas contraire. vous pouvez ne pas recevoir la valeur retour : callp close(s) Pour plus de précision vous pouvez récuperer le code erreur (en général) par la routine standard "__errno": .................................................................. : D errptr * : : D errno 10I 0 based(errptr) : : D sys_errno PR 10I 0 Extproc('__errno) : : : : /free : : errptr = sys_errno(); : .................................................................. |
Pour faire un serveur, vous avez deux possibilités : 1/ méthode traditionnelle, tout faire 2/ utiliser le serveur de serveurs INETD (que nous verrons ensuite) Pour tout faire soi même : 1/ (facultatif) récupérer le port serveur par getservbyname() 2/ création d'un socket 3/ associer le socket au port par bind() 4/ lancer l'API listen() pour passer le port à l'écoute 5/ utiliser accept() en attendant une connexion (nouveau socket entrant) 6/ clore le socket du 5/ et recommencer |
.................................................................. : D bind PR 10I 0 ExtProc('bind') : : D socket 10I 0 Value : : D local_addr * Value : : D addresslen 10I 0 Value : :................................................................: .................................................................. : D listen PR 10I 0 ExtProc('listen') : : D socket_desc 10I 0 Value : : D back_log 10I 0 Value : .................................................................. .................................................................. : D accept PR 10I 0 ExtProc('accept') : : D socket_desc 10I 0 Value : : D adress * Value : : D addresslen 10I 0 Value : .................................................................. |
Cette technique permet le dialogue avec un client. Si vous souhaitez accepter la connexion de multiples clients, vous devez utiliser l'API select() ou bien utiliser INETD . Inet daemon est le serveur de serveurs, il va gérer les connexion entrantes (multiples) - créer un socket pour chaque connexion (vous manipulez un socket à 0) - soumettre votre programme déja connecté Vous devez : Enregistrez votre service dans la table des services (CFGTCP option 21/1) ADDSRVTBLE SERVICE('monservice') PORT(1234) PROTOCOL('tcp') Editer '/QIBM/USERDATA/OS400/INETD/inetd.conf' pour ajouter monservice stream tcp nowait QUSER /QSYS.LIB/MABIB.LIB/MONPGM.PGM lancer INETD par STRTCPSVR SERVER(*INETD) |
Exemple de serveur utilisant INETD H DFTACTGRP(*NO) BNDDIR('QC2LE') D CMD S 4 varying D reponse S 10 Ds S 10I 0 Dlen S 10I 0 Dtemp S 4 /copy socket_H /free dsply 'serveur lancé' 'QSYSOPR'; s = 0; CMD = ''; dou %len(CMD) = 4; len = recv(s : %addr(temp) : %size(temp) : 0); if len = -1; DSPLY 'Erreur lors de la réception' 'QSYSOPR'; endif; CMD = CMD + %subst(temp : 1 : len); enddo; |
if CMD = 'DATE'; reponse = %char(%date()); ENDIF; if CMD = 'TIME'; reponse = %char(%time()); ENDIF; reponse = reponse + x'0a'; len = send(s : %addr(reponse) : %len(%trimr(reponse)) : 0); callp close (s); *inlr = *on; /end-free ci dessous le client (utilisant ces propres fonction SendQ et GetR) |
* H dftactgrp(*no) /copy socket_H D SendQ PR 10I 0 D sock 10I 0 value D data 1024A value D GetR PR 10 D sock 10I 0 value D addr s 10U 0 D port s 5U 0 D s s 10I 0 D connto ds likeds(sockaddr_in) D attente s 1 /free // --------------------------------------------------- // recherche adresse ip (ici locale) et port // --------------------------------------------------- |
p_hostent = gethostbyname('LOOPBACK'); if (p_hostent = *NULL); DSPLY 'Host lookup failed.' ; return; else; addr = h_addr; endif; port = 666; // --------------------------------------------------- // creation socket // --------------------------------------------------- s = socket(AF_INET: SOCK_STREAM: IPPROTO_TCP); if (s = -1); DSPLY 'socket failed!' ; return; endif; // --------------------------------------------------- // connection // --------------------------------------------------- |
connto = *ALLx'00'; connto.sin_family = AF_INET; connto.sin_addr = addr; connto.sin_port = port; if ( connect(s: %addr(connto): %size(connto)) = -1 ); callp close(s); DSPLY 'Connect failed!' ; return; endif; SendQ(s: 'TIME'); DSPLY GetR(s) '*EXT' attente; // affichage écran pour test callp close(s); *inlr = *on; /end-free *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ * Envoi de la question *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
P SendQ B D SendQ PI 10I 0 D sock 10I 0 value D data 1024A value D len s 10I 0 /free len = %len(%trimr(data)); return send(sock: %addr(data): len: 0 ); /end-free P E *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ * Reception de la réponse * * On part du principe que la réponse fait 10 * sinon il faut gérer un caractère de fin, par exemple * LF (ASCII x'0a') ou CRLF (EBCDIC X'OD25') *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
P GetR B D GetR PI 10 D sock 10I 0 value D temp s 10A D data s 10 VARYING D reply s 10 D len s 10I 0 /free dou %len(Data) = 10 or %scan(X'0a' : data) > 0; len = recv(sock : %addr(temp) : %size(temp) : 0); if len = -1; DSPLY 'Erreur lors de la réception ; return 'Err'; endif; if len = 0 or %subst(temp : len: 1) = x'0a'; leave; ENDIF; DATA = DATA + %subst(temp : 1 : len); enddo; |
monitor; reply = %subst(data:1:%len(data)); on-error; reply = 'Erreur'; endmon; return reply; /end-free P E il est ainsi plus simple de gérer la conversion ASCII/EBCDIC dans les fonctions SendQ et GetR si vous dialoguez avec une machine en ASCII. 1ere possibilité QDCXLATE D QDCXLATE PR ExtPgm('QDCXLATE') D Size 5P 0 const D Data 32702A options(*varsize) D Table 10A const puis QDCXLATE( datalen : data: 'QTCPEBC' ); // 'QTCPASC', lors de l'envoi |
ca marche bien avec des données US (code page 37) mais mal avec nos caractères accentués. il est préférable, alors, d'utiliser iconv() (standard du langage C) H DFTACTGRP(*NO) *------------------------------------------------------------------ * Prototype for Code Conversion - Open *------------------------------------------------------------------ d IConvOpen PR 52A ExtProc('QtqIconvOpen') d * Value options(*string) d * Value options(*string) *------------------------------------------------------------------ * Prototype for Code Conversion *------------------------------------------------------------------ d IConv PR 10i 0 ExtProc('iconv') d 52a Value d * Value d 10I 0 d * Value d 10I 0 |
************************************************************************** * Code Page (iconv_t, réponse de IConvOpen, argument à iconv) ************************************************************************** d ToAscii DS d ICORV_A 1 4b 0 * return value to indicate if error occurred d ICOC_A 5 52b 0 DIM(00012) * d ToEbcdic DS d ICORV_E 1 4b 0 * return value to indicate if error occurred d ICOC_E 5 52b 0 DIM(00012) * divers variables Dp_InBuff S * Dp_OutBuff S * DInBytesLeft S 10I 0 DOutBytesLeft S 10I 0 Dp_InBytes S * Dp_OutBytes S * DRc S 10I 0 |
d p_Qascii S * inz(%addr(Qascii)) d Qascii DS 32 d asciiCP 1 4b 0 inz(01252) <- ANSI(Windows),819=ASCII d asciiCA 5 8b 0 inz(0) d asciiSA 9 12b 0 inz(0) d asciiSS 13 16b 0 inz(1) d asciiIL 17 20b 0 inz(0) d asciiEO 21 24b 0 inz(1) d asciiR 25 32a inz(*allx'00') * d p_Qebcdic S * inz(%addr(Qebcdic)) d Qebcdic DS 32 d ebcdicCP 1 4b 0 inz(00297) <- EBCDIC Français d ebcdicCA 5 8b 0 inz(0) d ebcdicSA 9 12b 0 inz(0) d ebcdicSS 13 16b 0 inz(1) d ebcdicIL 17 20b 0 inz(0) d ebcdicEO 21 24b 0 inz(1) d ebcdicR 25 32a inz(*allx'00') |
* Initialisation EBCDIC/ASCII c eval ToAscii = IConvOpen(p_Qascii: c p_Qebcdic) c if ICORV_A = -1 * erreur c DSPLY 'Erreur à l''initialisation' c endif * * conversion EBCDIC/ASCII c eval p_InBuff = %addr(TXTIN) c eval p_OutBuff = %addr(TXTOUT) c eval InBytesLeft = %len(%trim(TXTIN)) c eval OutBytesLeft = %len(TXTOUT) c eval rc = IConv(ToAscii: c %addr(p_InBuff): c InBytesLeft: c %addr(p_OutBuff): c OutBytesLeft) c if ICORV_A > 0 c DSPLY 'Erreur à la conversion' c endif |
* Initialisation ASCII/EBCDIC c eval ToEbcdic = IConvOpen(p_Qebcdic: c p_Qascii) c if ICORV_E = -1 * erreur c DSPLY 'Erreur à l''initialisation' c endif * * conversion ASCII/EBCDIC c eval p_InBuff = %addr(TXTIN) c eval p_OutBuff = %addr(TXTOUT) c eval InBytesLeft = %len(%trim(TXTIN)) c eval OutBytesLeft = %len(TXTOUT) c eval rc = IConv(ToEbcdic: c %addr(p_InBuff): c InBytesLeft: c %addr(p_OutBuff): c OutBytesLeft) c if ICORV_E > 0 c DSPLY 'Erreur à la conversion' c endif |
A chaque utilisation de iconv_open doit être associée iconv_close dont voici le prototype : *------------------------------------------------------------------ * Prototype for Code Conversion - Close *------------------------------------------------------------------ d IConvClose PR 10I 0 ExtProc('iconv_close') d 52A Value * réutiliser ensuite le résultat retourné par iconv_open c eval rc = IConvClose(ToEbcdic) c eval rc = IConvClose(ToAscii) avant de terminer votre programme C eval *inlr = *on |
Quelques références : Sites IBM ---------- la documentation à Information Center http://publib.boulder.ibm.com/infocenter/iseries/v5r4/topic/apis/unix8.htm http://publib.boulder.ibm.com/infocenter/iseries/v5r4/topic/apis/nls3.htm ce redbook sur RPG-IV http://www.redbooks.ibm.com/Redbooks.nsf/RedbookAbstracts/sg245402.html?Open Autres sites ------------ Scott Klement (proposant un SAVF avec qq exemples, dont SOCKET_H) http://www.scottklement.com/rpg/socktut/index.html |