FR/Documentation/LanguageCpp

From Apache OpenOffice Wiki
Jump to: navigation, search

Le but de ce chapitre est d'expliquer les particularités du langage C++ dans un environnement UNO mais pas d'expliquer en détail ce qu'est le C++.

Le langage C++ et UNO

Le but de ce chapitre est d'expliquer les particularités du langage C++ dans l'environnement UNO et non de fournir des connaissances en C++ traditionnel. Pour dire les choses autrement je veux donner les fondement de C++ et UNO pour nous permettre de commencer.

L'apprentissage de C++ peut se faire à l'aide de livres électroniques malheureusement en anglais ici.

Apache OpenOffice fournit une interface de programmation sous la forme de Universal Network Objects (UNO). C'est une interface orientée objet avec Apache OpenOffice subdivisée en différents objets qui assurent la liaison avec le package Apache OpenOffice package.

Comme C++ est un langage procédural, il faut lui ajouter certains constructeurs pour lui permettre d'utiliser UNO. Nous verrons ces détails un peu plus loin dans ce document (Obtenir le gestionnaire de services et introduction au bootstrapping). Ce chapitre aussi aborde un peu le sujet : le type Any, les types énumérés et les constantes UNO .

Notre exemple de départ : un binaire exécutable

Nous allons partir d'un exemple du SDK. L'objectif de l'exemple présenté est de créer un exécutable qui interragit avec OpenOffice.org. On peut imaginer deux types d'interactions : une interaction directe avec OpenOffice.org ou une interaction avec les librairies partagées d'OpenOffice. Nous nous intéresserons au second cas pour lequel le fichier makefile est plus simple. La librairie dynamique partagée est cppuhelper.uno.so (cppuhelper.uno.dll sous Windows). Le premier cas sera examiné plus tard.

Je suppose (je ne connais personne de l'équipe de développement du SDK) que cet exemple est fourni pour montrer le programme le plus simple qui peut être réalisé avec le SDK. C'est l'exemple Lifetime : voir dans le répertoire “<OpenOffice.org1.1_SDK>/examples/DevelopersGuide/ProfUNO/Lifetime”

Avant de lancer l'exemple, il vous faudra positionner votre environnement pour pouvoir créer un programme UNO. Ce qui est requis comme information dépend de la plateforme que vous utilisez. Nous montrons ci-dessous comment les choses se passent sous LINUX. Pour tester cet exemple nous avons simplement à lancer le makefile :

Documentation linux.png
cd <OpenOffice.org1.1_SDK>
./setsdkenv_unix
cd examples/DevelopersGuide/ProfUNO/Lifetime
make
make ProfUnoLifetime.runexe


La dernière ligne de commande lance seulement le binaire exécutable “ProfUnoLifetime” qui interagit avec cpphelper.uno.so (cppuhelper.uno.dll sous Windows) même si OpenOffice.org n'est pas lancé. Cet exemple crée et dispose un objet UNO sans plus avec des constructeurs et destructeurs qui affichent un message correspondant. Sa taille étant réduite, nous donnons son code source maintenant :

// Listing 1 Premier Exemple (du SDK)
// C++
#include <stdio.h>
#include <cppuhelper/weak.hxx>
 
class MyOWeakObject : public ::cppu::OWeakObject
{
public:
    MyOWeakObject() { fprintf( stdout, "constructed\n" ); }
    ~MyOWeakObject() { fprintf( stdout, "destructed\n" ); }
};
 
 
void simple_object_creation_and_destruction()
{
    // create the UNO object
    com::sun::star::uno::XInterface * p = new MyOWeakObject();
 
    // acquire it, refcount becomes one
    p->acquire();
 
    fprintf( stdout, "before release\n" );
 
    // release it, refcount drops to zero
    p->release();
 
    fprintf( stdout, "after release\n" );
}
 
 
int main( char * argv[] )
{
    simple_object_creation_and_destruction();
    return 0;
}

Les deux méthodes acquire et release seront rencontrées et détaillées plus tard. Ce qu'elles font exactement n'est pas important non plus pour le moment. Cet exemple nous rappelle aussi comment on écrit des classes qui héritent d'autres classes et comment instancier la classe.

Tous les listings de ce chapitre pourront être réalisés (et compilés) uniquement en changeant le code source de cet exemple sans en changer de nom, ce qui permet de garder le même makefile.

Types

Les types UNO sont répertoriés dans le tableau ci-dessous :

Types UNO
UNO Type description Java C++ Basic
char 16-bit unicode character type char sal_Unicode -
boolean boolean type; true and false boolean sal_Bool Boolean
byte 8-bit ordinal type byte sal_Int8 Integer
short signed 16-bit ordinal type short sal_Int16 Integer
unsigned short unsigned 16-bit ordinal type - sal_uInt16 -
long signed 32-bit ordinal type int sal_Int32 Long
unsigned long unsigned 32-bit type - sal_uInt32 -
hyper signed 64-bit ordinal type long sal_Int64 -
unsigned hyper unsigned 64-bit ordinal type - sal_uInt64 -
float processor dependent float float float (IEEE float) Single
double processor dependent double double double (IEEE double) Double

Les colonnes UNO représentent tous les types que nous pouvons trouver dans les fichiers IDL. Nous décrirons les fichiers IDL plus tard (voir chapitre 10). Les colonnes C++ sont celles qui nous intéressent le plus, quand nous programmons en C++. Si vous voulez tester les différents programmes donnés vous pouvez aller dans le répertoire exemple du SDK : <OpenOffice.org1.1_SDK>/examples/DevelopersGuide/ProfUNO/CppBinding et remplacer alors complètement le fichier initial ”office_connect.cxx” par le programme ci dessous :

//Listing 2 Petit programme d'exemple
// C++
#include <stdio.h>
#include <cppuhelper/bootstrap.hxx>
 
main( ) {
	sal_Int32 var1;
	var1 = 34;
	printf("The var1 value is : %d\n",var1);
   	return 0;
}

ensuite lancer un make :

make ALL
make office_connect.run

et cela fonctionne. Ne pas oublier: ./setsdkenv_unix sur Linux.

Séquences

Une Séquence est une vue abstraite de la notion d'ensemble. Comme exemple, nous donnons

//Listing 3 Utilisation des séquences
// C++
#include <stdio.h>
#include <cppuhelper/bootstrap.hxx>
#include <com/sun/star/uno/Sequence.hxx>
using namespace com::sun::star::uno;
 
main( ) {
	Sequence < sal_Int32 > SetOfInt(5);
	//var1 = 34;
	SetOfInt[2]=44;
	printf("The value is : %d\n",SetOfInt[2]);
    return 0;
}

Nous pouvons déduire 5 règles d'utilisation des séquences :

  • le type de chaque élément est défini entre : “<“ et “>”
  • la déclaration de séquence est faite comme d'habitude avec un constructeur nécessitant un paramètre optionnel entre parenthèses qui spécifie la taille de la séquence
  • l'accès à chaque éléments de la séquence est fait comme avec un tableau à l'aide de crochets et un index entre ceux-ci
  • Une directive d'inclusion est nécessaire ; rien ne se fera sans Sequence.hxx. Prenez bien note que pour cette fois le fichier correspondant est fourni avec le SDK mais comme j'ai déjà eu l'occasion de l'écrire, ce ne sera pas toujours le cas.
  • Il faut gérer l'espace de nommage correspondant : “using namespace ...” Si vous ne le faites pas il vous faudra écrire com::sun::star::uno::Sequence au lieu de Sequence.

Nous donnons maintenant un exemple sur une séquence d'entiers.

//Listing 4 Un autre exemple de séquence
// C++
#include <stdio.h>
#include <cppuhelper/bootstrap.hxx>
#include <com/sun/star/uno/Sequence.hxx>
using namespace com::sun::star::uno;
 
main( ) {
	Sequence < sal_Int32 > SetOfInt(5);
	sal_Int32 *table;
	SetOfInt[2]=44;
	printf("The value is : %d\n",SetOfInt[2]);
	// Tests whether the sequence has elements, i.e. elements count is greater than zero
	if (SetOfInt.hasElements()) printf("Set not empty\n");
	// Gets a pointer to elements array for reading and writing. If the sequence 
	//  has a length of 0, then the returned pointer is undefined
	table = SetOfInt.getArray();
	table[4]=78;
	printf("The value is : %d\n",SetOfInt[4]); // print out 78
	SetOfInt.realloc(7);
	printf("New length : %d\n",SetOfInt.getLength()); // print out 7
	return 0;
}

Les commentaires affichés expliquent ce qui résulte du lancement du programme. La méthode realloc est particulièrement importante : elle permet de changer la taille de l'ensemble en cours de route.

Portons notre attention maintenant sur l'utilisation des séquences comme paramètre de retour d'une fonction ou comme paramètre. Un exemple est encore plus parlant qu'un long discours :

// Listing 5 Sequence et Parametres 
// C++
// function
Sequence < sal_Int32 > initSequence(){
	sal_Int32 sourceArray[5]={1,2,3,4,5};
	Sequence < sal_Int32 > SeqOfInt(sourceArray,5);
	return SeqOfInt;
}
// reference parameter
void initSequence2(Sequence < sal_Int32 > &SeqOfInt ) {
	sal_Int32 sourceArray[4]={1,2,3,4};
	Sequence < sal_Int32 > SeqOfInt2(sourceArray,4);
	SeqOfInt=SeqOfInt2;
}

Un appel trivial pourrait être :

// Listing 6 Sequence et Parametres (suite) 
// C++
	SetOfInt = initSequence();
	printf("New length : %d\n",SetOfInt.getLength()); // prints out 5
	initSequence2(SetOfInt);
	printf("New length : %d\n",SetOfInt.getLength()); // prints out 4

On ne peut pas quitter cette section sans ajouter que les opérateurs == et != sont disponibles avec les séquences. A noter aussi que « toUnoSequence », « getCppuSequenceType » et « getCharSequenceCppuType() » sont des fonctions membres de la classe séquence.

Passer une séquence dans une fonction comme paramètre par valeur est, comme un vecteur (de la librairie standard C++ STL) quelque chose d'inefficace parcequ'il faut recopier toute la séquence dans la pile. Il est donc largement préférable d'utiliser une référence constante comme montré ci-dessous :

// Listing 7 La méthode de référence constante
//C++
#include <iostream>
#include <cppuhelper/bootstrap.hxx>
#include <com/sun/star/uno/Sequence.hxx>
using namespace com::sun::star::uno;
using namespace std;
 
// Passer une Sequence par valeur en utilisant la référence constante
void avoidCopy(const Sequence< double > &ConstRef){
	// ConstRef[2]=6.7; would fail here
	for(int i=0;i<ConstRef.getLength();i++)
           cout<<ConstRef[i]<<" ";
	cout<<endl;
 
}
 
main( ) {
    Sequence < double > demo(5);
    demo[0]=1.1;
    demo[1]=2.1;
    demo[2]=3.1;
    demo[3]=4.1;
    demo[4]=5.1;
    avoidCopy(demo);
    return 0;
}

Pour aller plus loin, allez consulter le Developer's Guide (UNO C++ Binding).

Chaîne de caractères

Nous redonnons la représentation des différentes chaînes de caractères.

Le type STRING et UNO
UNO description Java C++ Basic
string String of 16-bit unicode characters Java.lang.string ::rtl::OUString String

OUString et OString

UNO fournit deux classes pour la manipulations des chaînes de caractères OUString et OString. Pour simplifier disons que la première permet de manipuler les chaînes UNICode tandis que la deuxième permet de manipuler les chaînes ASCII. Seuls les OString peuvent donc être utilisé avec un printf.

Toutes les chaînes de caractères provenant d'UNO API sont des OUString. Cela signifie, que si vous voulez voir quelque-chose dans la console, vous devez convertir les OUString en OString. Si vous demandez quelque-chose à l'utilisateur et que vous voulez le donnez à UNO vous devez convertir les OString en OUString. Ensuite si vous voulez utiliser la console (en général pour des tests) vous devez apprendre les deux types de langages.

Conversion OUString en OString

Si e.Message est une OUString, nous donnons un exemple de conversion :

//Listing 8
// C++
	OString o = OUStringToOString( e.Message, RTL_TEXTENCODING_ASCII_US );
	printf( "Error: No such element ;%s\n", o.pData->buffer );

Conversion OString en OUString

La conversion inverse est aussi facile :

//Listing 9 
// C++
#include <rtl/string.hxx>
 
	OUString foo;
	foo = OUString::createFromAscii("Hi every body");

Pour compléter cet exemple de conversion, donnons maintenant un programme complet :

//Listing 10 
// C++
#include <stdio.h>
#include <cppuhelper/bootstrap.hxx>
#include <rtl/string.hxx>
using namespace rtl;
 
main( ) {
	OUString OUStr;
	OUStr = OUString::createFromAscii("Hi every body");
	OString OStr = OUStringToOString( OUStr, RTL_TEXTENCODING_ASCII_US );
	printf( "OUStr was : %s\n", OStr.pData->buffer );
	return 0;
}

Nous voyons qu'un “ using namespace rtl;” est nécessaire. Les méthodes des classes OUString et OString sont similaires : l'exemple complet ci-dessous en montre quelques-unes :

//Listing 11 
// C++
#include <stdio.h>
#include <cppuhelper/bootstrap.hxx>
#include <rtl/string.hxx>
using rtl::OUString;
using rtl::OString;
 
main( ) {
	OUString OUStr1,OUStr2;
	OString OStr1, OStr2;
	OStr1="Hi every body";
	OStr2="every body";
	OUStr1 = OUString::createFromAscii("Hi every body");
	OUStr2 = OUString::createFromAscii("Hi every body");
	// Returns the length of this string
	printf("Length : %d\n",OUStr1.getLength()); // prints out 13
	// Compares two strings
	printf("CompareTo : %d\n",OUStr1.compareTo(OUStr2)); // prints out 0
	// Compares two strings
	printf("equals : %d\n",OUStr1.equals(OUStr2)); // prints out 1
	if (OUStr1.equals(OUStr2)) printf("OK\n");// prints out OK
	// Compares two strings
	printf("CompareToAscii : %d\n",OUStr1.compareToAscii(OStr1)); // prints out 0
	// Compares two strings with a maximum count of characters
	printf("CompareToAscii : %d\n",OUStr1.compareToAscii(OStr1),10); // prints out 0
	// Perform a comparison of a substring in this string
	// is "every body" in position 3 of OUStr1 ?
	if (OUStr1.match(OUString::createFromAscii("every body"),3))
		printf("match : OK\n"); //prints out match : OK
	// Perform a comparison of a substring in this string
	if (OUStr1.matchAsciiL(OStr2,OStr2.getLength(),3))
		printf("matchAsciiL: OK\n");
	// Returns the float value from this string
	OUStr2 = OUString::createFromAscii("1.675");
	printf("toFloat : %f\n",OUStr2.toFloat()); // prints out 1.675000
	return 0;
}

Remarque : J'ai rencontré un problème avec la méthode “concat”.

Chemin d'accès (répertoires)

Les chemins d'accès aux fichiers dépendent du système d'exploitation. Pour supprimer cette dépendance, UNO utilise URL et un moyen de transformer un chemin d'accès en un URL. Exemple :

//Listing 12 
// Don't forget #include <osl/file.hxx>
	OUString sDocUrl;
    osl::FileBase::getFileURLFromSystemPath(
                 OUString::createFromAscii("/home/smoutou/edt.sxd"),sDocUrl);
// Windows C++ to check
//	osl::FileBase::getFileURLFromSystemPath(
//			  OUString::createFromAscii("C:\My Documents\tata.sxc"),sDocUrl);

Un exemple complet est donné ci-dessous :

//Listing 13 
// C++
#include <stdio.h>
#include <cppuhelper/bootstrap.hxx>
#include <rtl/string.hxx>
#include <osl/file.hxx>
using rtl::OUString;
using rtl::OString;
using namespace osl;
 
main( ) {
	OUString sDocUrl;
	OString toPrintOut;
   	osl::FileBase::getFileURLFromSystemPath(
                 OUString::createFromAscii("/home/smoutou/edt.sxd"),sDocUrl);
	toPrintOut = OUStringToOString( sDocUrl, RTL_TEXTENCODING_ASCII_US );
	printf( "URL is : %s\n", toPrintOut.pData->buffer );
	// prints out URL is : file:///home/smoutou/edt.sxd
	return 0;
}

lequel montre comment utiliser la conversion. Rappelez-vous que vous travaillez avec UNO et qu'ensuite les URL sont des OUString.

Exemple du SDK

Voici un peu de code fourni par le SDK :

//Listing 14 
// C++
#include <stdio.h>
 
#include <rtl/ustrbuf.hxx>
#include <rtl/string.hxx>
 
using rtl::OUString;
using rtl::OUStringBuffer;
using rtl::OString;
 
int main( int argc, char * argv [] )
{
    // string concatination
 
    sal_Int32 n = 42;
    double pi = 3.14159;
 
    // give it an initial size, should be a good guess.
    // stringbuffer extends if necessary
    OUStringBuffer buf( 128 );
 
    // append an ascii string
    buf.appendAscii( "pi ( here " );
 
    // numbers can be simply appended
    buf.append( pi );
 
    // lets the compiler count the stringlength, so this is more efficient than
    // the above appendAscii call, where length of the string must be calculated at
    // runtime
    buf.appendAscii( RTL_CONSTASCII_STRINGPARAM(" ) multiplied with " ) );
    buf.append( n );
    buf.appendAscii( RTL_CONSTASCII_STRINGPARAM(" gives ") );
    buf.append( (double)( n * pi ) );
    buf.appendAscii( RTL_CONSTASCII_STRINGPARAM( "." ) );
 
    // now transfer the buffer into the string.
    // afterwards buffer is empty and may be reused again !
    OUString string = buf.makeStringAndClear();
 
    // I could of course also used the OStringBuffer directly
    OString oString = rtl::OUStringToOString( string , RTL_TEXTENCODING_ASCII_US );
 
    // just to print something
    printf( "%s\n" ,oString.getStr() );
    return 0;
}

Cet exemple montre une autre manière d'utiliser le OUString, l'OString et le buffer.

Sequence de chaînes

Les Sequences ont été abordées dans une section précédente section. Si vous voulez construire des séquences de chaînes (utiles dans les list box ...) regardez avec attention ce code :

// Listing 15 An Example of Sequence of Strings
// C++
// Sequence of string
#include <rtl/string.hxx>
 
// Don't forget to add #include <com/sun/star/uno/Sequence.hxx>
// Don't forget to add using namespace com::sun::star::uno;
	Sequence<OUString>seqStrlistItems(2);
	seqStrlistItems[0]=OUString::createFromAscii("Item1");
	seqStrlistItems[1]=OUString::createFromAscii("Item2");
 
// retrieve them and prints out
	for (int i=0;i<seqStrlistItems.getLength();i++){
		OString toPrintOut = OUStringToOString(seqStrlistItems[i],RTL_TEXTENCODING_ASCII_US);
		printf("-- %s\n",toPrintOut.pData->buffer);
	}

Ce code affiche dans un terminal :

-- Item1
-- Item2
[smoutou@p3 Lifetime]$ 

Il est facile à comprendre et ne nécessite donc pas d'explications ultérieures.

Exercice d'application sur les séquences de chaînes

On vous demande de réaliser une fonction "convert2SeqOUStrInDEBUGMODE" qui prend comme paramètre une séquence de "sal_Int8" et la transforme en séquence de chaînes dans un format proche de l'ancien outil "debug" de DOS. Ce format sera utilisé ici mais sans cette fonction.

Solution : voici le code du sous-programme correspondant

// Listing 15a un exercice d'application
// C++
Sequence< OUString > convert2SeqOUStrInDEBUGMODE(Sequence < sal_Int8 > seqBytes){
  sal_Int8 Hexa[16]={'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
  sal_Int32 lin,col;
  sal_Char line[80];
  sal_Int32 len,sequenceSize,index;
  len=seqBytes.getLength();
  sequenceSize=(len>>4)+2;
  if ((len&0XFFF0)==len) sequenceSize--; //if len%16==0
  Sequence< OUString > SeqOUStr(sequenceSize);
  // Premiere ligne gere la longueur 
  SeqOUStr[0] = OUString::createFromAscii("Length:")+
                OUString::valueOf((sal_Int32) seqBytes.getLength());
  len = len&0XFFF0; // on retire le modulo 16
  for(lin=0;lin<len;lin=lin+16){
      for(col=0;col<16;col++){
        line[3*col]=Hexa[((unsigned char)seqBytes[lin+col])>>4];
	line[3*col+1]=Hexa[seqBytes[lin+col]&0x0F];
	line[3*col+2]=' ';
        if ((seqBytes[lin+col]<128)&&(seqBytes[lin+col]>20)) line[50+col]=seqBytes[lin+col];
        else line[50+col]='.';
      } /* end of for */
      line[66]=0; /* end of cstring...*/
      line[48]=' ';line[49]=' ';
  // pret a ajouter la chaine OUString a la Sequence
      if ((lin%16)==0) index = lin/16+1; else index=lin/16+2;
      SeqOUStr[index]=OUString::createFromAscii(line);
  } /* end of for */ 
  // la derniere ligne est plus compliquee parcequ'incomplete en general
  // on ne garde que le modulo
  len=seqBytes.getLength()&0x000F;
  if (len>0) { // only a line here if non-empty
  for (lin=0;lin<len;lin++){
    col=lin;
    line[3*col]=Hexa[((unsigned char)seqBytes[lin])>>4];
    line[3*col+1]=Hexa[seqBytes[lin]&0x0F];
    line[3*col+2]=' ';
    if ((seqBytes[lin]<128)&&(seqBytes[lin]>20)) line[50+col]=seqBytes[lin];
    else line[50+col]='.';
  }
  // on complete la ligne
  for (++col;col<16;col++){
	line[3*col]=' ';line[3*col+1]=' ';line[3*col+2]=' ';line[50+col]=' ';
  }
  line[66]=0; /* fin de C-chaine...*/
  line[48]=' ';line[49]=' ';
  // pret a ajouter la chaine OUString a la Sequence
  SeqOUStr[lin/16+2]=OUString::createFromAscii(line);
  } //end if
  return SeqOUStr;
}

et voici par la même occasion une façon d'utiliser cette fonction :

// Listing 15b un exercice d'application
// C++
sal_Int8 sourceArray[32]={1,22,23,24,55,56,57,8,9,10,11,12,13,14,15,16,17,18,19,61,62,-32,-33,-34,-35,26,127,-31,29,56,23,67};
        Sequence < sal_Int8 > seqBytes(sourceArray,32);
	Sequence < OUString > seqOUStr=convert2SeqOUStrInDEBUGMODE(seqBytes);
	printf("%s\n",OUStringToOString( seqOUStr[0], RTL_TEXTENCODING_ASCII_US ).pData->buffer);
	printf("%s\n",OUStringToOString( seqOUStr[1], RTL_TEXTENCODING_ASCII_US ).pData->buffer);
	printf("%s\n",OUStringToOString( seqOUStr[2], RTL_TEXTENCODING_ASCII_US ).pData->buffer);

Dans notre outil d'introspection nous aurons besoin de ce format d'affichage, mais dans une seule chaîne de caractères. Cela se fait facilement à l'aide du code :

// Listing 15c un exercice d'application
// C++
OUString convert2OUStrInDEBUGMODE(Sequence < sal_Int8 > seqBytes){
  Sequence <OUString> SeqOUStr = convert2SeqOUStrInDEBUGMODE(seqBytes);
  OUString OUStr;
  for(sal_Int32 i=0;i<SeqOUStr.getLength();i++)
    OUStr = OUStr+SeqOUStr[i]+OUString::createFromAscii("\n");
  return OUStr;
}

qui peut être utilisé avec :

// Listing 15d un exercice d'application
// C++
sal_Int8 sourceArray[32]={1,22,23,24,55,56,57,8,9,10,11,12,13,14,15,16,17,18,19,61,62,-32,-33,-34,-35,26,127,-31,29,56,23,67};
        Sequence < sal_Int8 > seqBytes(sourceArray,32);
 
        OUString OUStr=convert2OUStrInDEBUGMODE(seqBytes);
        printf("%s\n",OUStringToOString( OUStr, RTL_TEXTENCODING_ASCII_US ).pData->buffer);

Voilà, c'est tout pour ce sujet. Nous sommes prêt pour en aborder un autre.

Any

Le type “Any” est bien décrit dans le guide du développeur. Any est facile à utiliser avec les opérateurs <<= and >>= et nous utiliserons aussi souvent que possible des variables intermédiaires. On peut aussi utiliser la fonction membre “makeAny” comme ci-dessous :

// Listing 16 makeAny Example
// C++
Any any = makeAny((long) 1000);

Commençons par un exemple complet :

//Listing 17 
// C++
#include <stdio.h>
#include <cppuhelper/bootstrap.hxx>
#include <com/sun/star/uno/Any.hxx>
#include <com/sun/star/uno/Sequence.hxx>
using rtl::OUString;
using rtl::OString;
using com::sun::star::uno::Any;
using namespace com::sun::star::uno;
using namespace rtl;
 
main( ) {
	Any any,any2;
	sal_Int32 n=5,n2=18;
	any <<= n;
	// Gets the type name of the set value
	OUString OUStr = any.getValueTypeName();
	OString OStr = OUStringToOString( OUStr, RTL_TEXTENCODING_ASCII_US );
	printf( "Any type : %s\n", OStr.pData->buffer );// prints out Any type : long
	// Tests if any contains a value
	if (any.hasValue()) printf("Any has a value\n");// prints out Any has a value
	// Clears this any. If the any already contains a value, that value will be destructed and
	// its memory freed. After this has been called, the any does not contain a value.
	any.clear();
	if (any.hasValue()) printf("Any has a value\n");// prints out nothing
	any <<= n2;
	any2 <<= OUStr;
	// Unequality operator: compares two anys. The values need not be of equal type,
	// e.g. a short integer is compared to a long integer
	if (any != any2) printf("Two different any\n");
 
	Sequence< Any> aValues(2);
	aValues[0] <<= (float) 1.1; aValues[1] <<= OUString::createFromAscii("Text");
	aValues[1] >>= OUStr;
	OStr = OUStringToOString( OUStr, RTL_TEXTENCODING_ASCII_US );
	float real;
	aValues[0] >>= real;
	printf("Seq - length : %d val : %f val %s\n",aValues.getLength(),real,OStr.pData->buffer);
	return 0;
}

Ce programme affiche :

Any type : long
Any has a value
Two different any
Seq - length : 2 val : 1.100000 val Text

Un problème important est la séquence de séquence de type Any : nous donnons ici un exemple de code sans explications :

// Listing 18 Sequence de Sequence de Any
// C++
Sequence< Sequence< Any > > aValues(2); 
Sequence< Any > aValues2(2);
aValues2[0] <<=  (double) 1.1; aValues2[1] <<= OUString::createFromAscii("Hello");
aValues[0] = aValues2;
aValues2[0] <<=  (double)2.2; aValues2[1] <<= OUString::createFromAscii("Hi");
aValues[1] = aValues2;

Ce code utilise deux séquences. Je n'ai pas trouvé de façon plus simple pour faire cela. Si quelqu'un a une solution plus directe, je suis preneur. Peut être est-il possible d'accéder directement un élément de tableau 2D mais je n'ai trouvé qu'un moyen, celui d'utiliser une variable intermédiaire (avalues2) dans le cas de séquences de séquences.

Fichiers

Documentation linux.png

Nous voulons maintenant créer un fichier texte nommé “demo.txt”. Voici le code pour effectuer cette opération :

// Listing 19 A little Example manipulating Files
// C++
#include <stdio.h>
#include <cppuhelper/bootstrap.hxx>
#include <osl/file.hxx>
 
using rtl::OUString;
using rtl::OString;
using namespace osl;
 
main( ) {
	OUString FileName;
	sal_Int8 tab[25]="Hi every body\n";
	sal_uInt64 nb;
	osl::FileBase::getFileURLFromSystemPath(
                 OUString::createFromAscii("/home/smoutou/demo.txt"),FileName);
	File myfile(FileName);
	myfile.open(OpenFlag_Write);
	myfile.write(tab,(sal_uInt64)16,nb);
	printf("%d bytes are written\n",nb);
	myfile.close();
	return 0;
}


Documentation note.png La méthode open n'est pas décrite dans la documentation C++!!! Je l'ai trouvé en lisant le fichier “file.hxx”.

Les constantes prédéfinies que l'on peut passer à la méthode open sont : OpenFlag_Write, OpenFlag_Read, OpenFlag_Create. Il faut à tout prix utiliser l'espace de nommage “ using namespace osl;” même si le compilateur ne le réclame pas. Quand un fichier est ouvert, vous pouvez utiliser les méthodes ci-dessous :

// C++
// Write a number of bytes to a file
write( const void * pBuffer, sal_uInt64 uBytesToWrite, sal_uInt64 & rBytesWritten );
 
// Read a number of bytes from a file
read( void * pBuffer, sal_uInt64 uBytesRequested, sal_uInt64 & rBytesRead );
 
// Read a line from a file
ReadLine( ::rtl::ByteSequence & aSeq );
 
// Test if the end of a file is reached
isEndOfFile( sal_Bool * pIsEOF );
 
// Retrieve the current position of the internal pointer of an open file
getPos( sal_uInt64 & uPos );
 
// Set the internal position pointer of an open file. 
setPos( sal_uInt32 uHow, sal_Int64 uPos );

Il y a d'autres possibilités : move, remove, copy... Voir la documentation ou lire le fichier file.hxx.

Voir « Streaming interfaces » pour d'autres techniques d'utilisation des fichiers.

Processus légers (Threads)

Ce serait bien d'utiliser la classe correspondant aux threads, mais elle est si mal documentée que je préfère commencer par ce que j'ai réussi à faire, les processus. Je suis parti d'un exemple fonctionnant avec le tableur dans le forum (parlirincy). Cet exemple donne un code permettant de lancer OpenOffice.org s'il n'est pas lancé lors de l'exécution d'un binaire. J'ai retiré ce qui n'est pas nécessaire pour nous et donnons deux exemples.

Documentation linux.png
// Listing 20 Un exemple de Processus
// C++
#include <stdio.h>
#include <cppuhelper/bootstrap.hxx>
#include <osl/process.h>
#include <osl/file.hxx>
 
using rtl::OUString;
using rtl::OString;
 
main( ) {
// Don't forget to add #include <osl/process.h>
	oslProcess hProcess = NULL;
// Don't forget to add #include <osl/file.hxx>
	OUString FileURL;
	osl::FileBase::getFileURLFromSystemPath(
	OUString::createFromAscii("/home/smoutou/demo"),
		FileURL
	);
	oslProcessError osl_error = osl_executeProcess(
	 FileURL.pData,
	 NULL,
	 0,
	 osl_Process_DETACHED,
	 0, /* osl_getCurrentSecurity() */
	 NULL,
	 NULL,
	 0,
	 &hProcess );
	osl_error = osl_joinProcess(hProcess);
	return 0;
}


Documentation note.png Notez que ce fichier lancé ici est un fichier binaire dans /home/smoutou.

Si vous voulez passer quelque chose au programme lancé, regardez la variable AppArgs dans ce second exemple :

Documentation linux.png
// Listing 21 A second Process Example
// C++
#include <stdio.h>
#include <cppuhelper/bootstrap.hxx>
#include <osl/process.h>
#include <osl/file.hxx>
 
using rtl::OUString;
using rtl::OString;
 
main( ) {
// Don't forget to add #include <osl/process.h>
	oslProcess hProcess = NULL;
// Don't forget to add #include <osl/file.hxx>
	OUString FileURL;
	osl::FileBase::getFileURLFromSystemPath(
	OUString::createFromAscii("/home/smoutou/demo"),
		FileURL
	);
	OUString AppArgs = OUString::createFromAscii("Arg1");
	oslProcessError osl_error = osl_executeProcess(
	 FileURL.pData,
	 &AppArgs.pData,
	 1,
	 osl_Process_DETACHED,
	 0, /* osl_getCurrentSecurity() */
	 NULL,
	 NULL,
	 0,
	 &hProcess );
	osl_error = osl_joinProcess(hProcess);
 
	return 0;
}


J'ai réussi à travailler avec des threads mais seulement avec un style C pour le moment, c'est à dire sans utiliser les classes. Voici mon premier exemple :

Documentation linux.png
// Listing 22 Un exemple de thread
// C++
#include <stdio.h>
#include <cppuhelper/bootstrap.hxx>
#include <osl/thread.h>
#include <unistd.h> // LINUX
 
using rtl::OUString;
using rtl::OString;
 
/** wait _nSec seconds.
*/
void thread_sleep( sal_Int32 _nSec )
{
/// print statement in thread process must use fflush() to force display.
	printf("# wait %d seconds. ", _nSec );
	fflush( stdout );
	sleep( _nSec );
	printf("# done\n" );
}
 
void SAL_CALL thread1(void *) {
  while(1) 
	printf("\n thread running");
 
}
 
void SAL_CALL thread2(int *nb){
  while(1) 
	printf("\n thread nb=%d running",*nb);
}
 
main( ) {
// Ne pas oublier d'ajouter #include <osl/thread.h>
	oslThread hThread = NULL;
//oslWorkerFunction type : void (SAL_CALL *oslWorkerFunction)(void*); in osl/thread.h
	oslWorkerFunction pFunction = (void (SAL_CALL *)(void*)) thread2;
	int pthreaddata=2;
// creation et départ de thread2 avec 2 comme parametre 
	oslThread hThread2 = osl_createThread(pFunction,(void *) &pthreaddata);
// creation et départ de thread1 
	hThread = osl_createThread(thread1,NULL);
 
	thread_sleep(2);
	osl_suspendThread(hThread);
	osl_suspendThread(hThread2);
 
	return 0;
}


Ce qui est affiché à l'écran dépend de de votre système d'exploitation. Sous Linux j'obtiens quelque chose comme :

 ...
 thread running
 thread running
 thread running
 thread running# done
 
 thread nb=2 running
 thread nb=2 running
 thread nb=2 running
 thread nb=2 running
 ....

Ici vous pouvez constater que le second processus léger écrit en premier (il est aussi lancé le premier) et seulement quand le sleeping est terminé le thread un écrit. Les autres primitives de gestion de threads peuvent être trouvées dans osl/thread.h :

/** Create the thread, using the function-ptr pWorker as
	its main (worker) function. This functions receives in
	its void* parameter the value supplied by pThreadData.
	Once the OS-structures are initialized,the thread starts
	running.
	@return 0 if creation failed, otherwise a handle to the thread
*/
oslThread SAL_CALL osl_createThread(oslWorkerFunction pWorker, void* pThreadData);
 
/** Create the thread, using the function-ptr pWorker as
	its main (worker) function. This functions receives in
	its void* parameter the value supplied by pThreadData.
	The thread will be created, but it won't start running.
	To wake-up the thread, use resume().
	@return 0 if creation failed, otherwise a handle to the thread
*/
oslThread SAL_CALL osl_createSuspendedThread(oslWorkerFunction pWorker, void* pThreadData);
 
/** Release the thread handle.
	If Thread is NULL, the function won't do anything.
	Note that we do not interfere with the actual running of
	the thread, we just free up the memory needed by the handle.
*/
void SAL_CALL osl_destroyThread(oslThread Thread);
 
/** Wake-up a thread that was suspended with suspend() or
	createSuspended(). The oslThread must be valid!
*/
void SAL_CALL osl_resumeThread(oslThread Thread);
 
/** Suspend the execution of the thread. If you want the thread
	to continue, call resume(). The oslThread must be valid!
*/
void SAL_CALL osl_suspendThread(oslThread Thread);
/** Returns True if the thread was created and has not terminated yet.
	Note that according to this definition a "running" thread might be
	suspended! Also returns False is Thread is NULL.
*/
sal_Bool SAL_CALL osl_isThreadRunning(const oslThread Thread);
 
/** Blocks the calling thread until Thread has terminated.
	Returns immediately if Thread is NULL.
*/
void SAL_CALL osl_joinWithThread(oslThread Thread);
 
/** Blocks the calling thread at least for the given number
    of time.
*/
void SAL_CALL osl_waitThread(const TimeValue* pDelay);

Pour plus d'informations vous pouvez lire Multi-Thread Programming.

Aller plus loin avec le problème des types énumérés

Nous rencontrerons parfois des types énumérés et il nous faut apprendre à les manipuler. Des exemples seront montrés plus tard mais nous tenons à en proposer un maintenant quitte à le sortir de son contexte. Par exemple le type de contenu d'une cellule du tableur OOoCalc est décrit par une énumération. Comment puis-je savoir cela n'est pas évident avec ce que nous avons abordé jusqu'à maintenant, mais je tiens à aborder ce problème ici même si vous estimez bon de le passer en première lecture.

Tous les types utilisés par UNO sont décrits dans des fichiers IDL. Parfois ces fichiers IDL sont utilisés pour créer les fichiers hpp/hxx qui nous seront nécessaires.

Cet exemple tourne autour du fichier CellContentType.idl. Voici un résumé du contenu de ce fichier dans lequel nous avons retiré tous les commentaires pour ne pas l'alourdir :

// Listing 23  CellContentType Enumeration type (IDL File)
// IDL
namespace com{
namespace sun{
namespace star{
namespace table{
enum CellContentType
{
    CellContentType_EMPTY = 0,
    CellContentType_VALUE = 1,
    CellContentType_TEXT = 2,
    CellContentType_FORMULA = 3,
    CellContentType_MAKE_FIXED_SIZE = SAL_MAX_ENUM
};
} // table
} // star
} // sun
} // com

Trois étapes peuvent être utilisées pour utiliser ce genre d'énumération :

  • En C++ un espace de nommage correspondant est nécessaire :
using namespace com::sun::star::table

Cette ligne peut sembler obscure, en particulier on peut se demander comment est-il possible de deviner cette ligne. En fait elle est déduite du fichier IDL correspondant et plus exactement de son chemin de localisation (IDL/com/sun/star/table).

  • En C++ une directive d'inclusion est nécessaire
#include <com/sun/star/table/CellContentType.hpp>

Le nom du fichier à inclure et son chemin est facile à tirer du fichier IDL.

  • Il nous faut construire le fichier hpp précédent. Cela est fait en ajoutant la ligne correspondante dans le makefile :
TYPES := \
	com.sun.star.uno.XNamingService \
	com.sun.star.uno.XComponentContext \
	com.sun.star.uno.XWeak \
	com.sun.star.uno.XAggregation \
	com.sun.star.lang.XMain \
	com.sun.star.lang.XMultiServiceFactory \
	com.sun.star.lang.XSingleComponentFactory \
	com.sun.star.lang.XTypeProvider \
	com.sun.star.lang.XComponent \
	com.sun.star.registry.XSimpleRegistry \
	com.sun.star.registry.XImplementationRegistration \
	com.sun.star.bridge.XBridgeFactory \
	com.sun.star.bridge.XUnoUrlResolver \
	com.sun.star.table.CellContentType \
	com.sun.star.container.XHierarchicalNameAccess

Après cela, vous pouvez utiliser l'énumération sans problème comme cela :

// Listing 24 Enumeration type : an Example
//C++
// A vérifier
// Ne pas oublier d'ajouter #include <com/sun/star/table/CellContentType.hpp>
// Ne pas oublier d'ajouter "com.sun.star.table.CellContentType \" dans le makefile
 
	using namespace com::sun::star::table
	short enumval;
	enumval =  CellContentType_EMPTY;
	switch (enumval){
		case CellContentType_EMPTY : printf("Empty\n");break;
		case CellContentType_VALUE : printf("Numerical Value\n");break;
		case CellContentType_TEXT : printf("Text\n");break;
		case CellContentType_FORMULA : printf("Formula\n");break;
	}
Documentation note.png Ce qui est montré ici est général pour l'utilisation du SDK : on construit un fichier hpp à partir d'un fichier IDL. Cela est tellement important et surprenant pour les débutants que je répèterai plusieurs fois ces explications. A noter que dans les installations Windows décrites dans le chapitre précédent certaines créent entièrement et en une seule fois tous les fichiers hpp. Vous n'aurez donc plus à modifier le fichier makefile

Aller plus loin : les constantes UNO

Les valeurs ne sont pas toujours définies avec un type énuméré comme montré dans la section précédente. Parfois des constantes sont utilisées quand on programme avec l'interface UNO. Un exemple est encore abordé plus tard (référence anglaise pour le moment) mais nous voulons écrire dessus maintenant. On présente tout d'abord le fichier IDL correspondant (comme exemple)

// Listing 25 CellFlags Constants type : IDL File 
//IDL
module com {  module sun {  module star {  module sheet {
constants CellFlags
{
	const long VALUE = 1;
	const long DATETIME = 2;
	const long STRING = 4;
	const long ANNOTATION = 8;
	const long FORMULA = 16;
	const long HARDATTR = 32;
	const long STYLES = 64;
	const long OBJECTS = 128;
	const long EDITATTR = 256;
};
}; }; }; };

Essayez de comparer ce listing avec le Listing 23 pour voir les différences.

Si vous voulez utiliser le constantes c-dessus, il vous faut tout d'abord générer le fichier hpp correspondant en modifiant le fichier makefile comme indiqué déjà dans la section précédente et ensuite ne pas oublier d'ajouter la directive d'inclusion correspondante :

// C++
#include <com/sun/star/sheet/CellFlags.hpp>

dans votre code C++. Notez qu'avec cette directive d'inclusion vous pouvez directement écrire quelque chose comme :

// C++
VALUE | DATETIME

au lieu de

// C++
CellFlags_VALUE | CellFlags_DATETIME

comme vu précédemment avec le type énuméré. C'est une différence qu'il est bon de garder en tête.

Les Sequences (suite)

Nous voulons donner maintenant des sous-programmes utiles pour la suite du document.

Conversion de sequences en tableaux

Parcequ'une fonction membre fait ce travail on peut faire les choses tout simplement :

//Listing 27 Exemple de Conversion
// C++
Sequence < sal_Int32 > SetOfInt(5);
sal_Int32 *table;
table = SetOfInt.getArray();

Si vous avez lu attentivement ce document jusqu'à maintenant ce code doit vous être familier : il est tiré du listing 4 et donc déjà expliqué.

Conversion de Sequence de Sequence en tableau

Ce problème doit être résolu car nous utiliserons ce type de conversion plus tard. Nous commençons par donner deux sous-programmes pour afficher et convertir :

//Listing 28 Sequence of Sequence into 1D array
//C+
// Sequence of Sequence is printed out like that :
// toPrint[0][0] toPrint[0][1] toPrint[0][2]...
// toPrint[1][0] toPrint[1][1] toPrint[1][2]...
// ...
// Note that i denotes the first index
void printOutSeqSeq(const Sequence < Sequence < double > > &toPrint){
	cout << "Prints out SeqSeq" << endl;
        for(int i=0;i<toPrint.getLength();i++){
                Sequence < double > rLine = toPrint[i];
		double *line= rLine.getArray();
		for(int j=0;j<rLine.getLength();j++)
			cout << " " << line[j] << " ";
		cout << endl;
	}
}
 
double *SeqSeqToArray1D(const Sequence < Sequence < double> > &seqseq){
	sal_Int32 iMax=seqseq.getLength(),jMax;
	Sequence < double > rLine = seqseq[0];
	jMax=rLine.getLength();	
	double *table1D = new double[iMax*jMax];
	for (sal_Int32 i = 0 ; i < iMax; i++ )
		for(sal_Int32 j = 0 ; j < jMax; j++ )
			table1D[i*jMax+j]= seqseq[i][j];
	return table1D;
}

Utiliser ces sous-programmes est très facile, cela peut être fait comme :

//Listing 29 utilisation de la conversion
//C+
// Construction de la sequence
	Sequence < double > demo(5);
	demo[0]=1.1;
    demo[1]=2.1;
	demo[2]=3.1;
	demo[3]=4.1;
	demo[4]=5.1;
// Construction de la sequence de sequence
	Sequence < Sequence < double > > seqseq(3);
	for (int i=0; i<seqseq.getLength();i++)
            seqseq[i]=demo;
// Affichage du résultat de la construction	
	printOutSeqSeq(seqseq);
// Voici la conversion
	double *table = SeqSeqToArray1D(seqseq);
// On termine par l'affichage 
	cout << "Array1D -> 2D : "<< endl;
	for (int i=0; i< 3; i++){
		for (int j=0;j<5;j++)
			cout<<" "<<table[i*5+j]<<" ";
		cout<<endl;
	}
	....
	delete table;

qui nous donne :

Prints out SeqSeq
 1.1  2.1  3.1  4.1  5.1
 1.1  2.1  3.1  4.1  5.1
 1.1  2.1  3.1  4.1  5.1
Array1D -> 2D :
 1.1  2.1  3.1  4.1  5.1
 1.1  2.1  3.1  4.1  5.1
 1.1  2.1  3.1  4.1  5.1
[smoutou@p3 Lifetime]$

La conversion directe de sequence de sequence en un tableau 2D n'est pas simple parce que le compilateur doit connaître la taille du second index du tableau avant de compiler ce tableau. Dans un premier temps quelque chose comme :

//Listing 30 Array2D conversion
// C++
double **SeqSeqToArray2D(const Sequence < Sequence < double> > &seqseq){
	double **table2D; 
	double *table1D;
	table1D = SeqSeqToArray1D(seqseq);
	table2D = &table1D;
	return table2D;
}

semble intéressant mais examinons si cela fonctionne. Une première idée est d'écrire :

//Listing 31 Mauvais code pour la conversion
// C++
	double **table2= SeqSeqToArray2D(seqseq);
	for (int i=0; i< 3; i++){
		for (int j=0;j<5;j++)
			cout<<" "<< table2[i][j]<<" ";
		cout<<endl;
	}
	delete table2;

mais le compilateur est incapable de calculer table2[i][j]. Pour cela il a besoin d'évaluer l'expression i*jMax+j mais sans connaissance sur jMax cela semble bien difficile !!

Quand vous écrivez un addin, la gestion simultanée des groupes de cellules et des séries de valeurs peut vous conduire à écrire un code qui utilise les sequence et qui est présenté ici.

Abstractions avec la STL

Si vous planifiez d'utiliser C++ il est probable que vous utilisiez la STL (Standard Template Library) de manière intensive. Il vous faudra donc opérer des conversions. Nous commençons par la plus facile :

//Listing 32 Conversion de Sequence de Sequence en vector
// C++
// Inspired by Eric Ehlers's code but using template
// See chapter 14
template< typename T>
vector < T >SeqSeqToVectorTemplate(const Sequence< Sequence < T > >& ss) {
    vector < T >v;
    for (int i=0; i<ss.getLength(); i++)
        for (int j=0; j<ss[i].getLength(); j++)
            v.push_back(ss[i][j]);
    return v;
}

Mon point de départ pour établir ce code était le code d'Eric Ethler présenté ici. Ce code est de nouveau facile à utiliser :

//Listing 33 En utilisant la Conversion précédente
// C++
#include <iostream>
#include<vector>
#include <cppuhelper/bootstrap.hxx>
#include <com/sun/star/uno/Sequence.hxx>
using namespace com::sun::star::uno;
using namespace std;
 
template< typename T>
vector < T >SeqSeqToVectorTemplate(const Sequence< Sequence < T > >& ss);
main( ) {
	Sequence < double > demo(5);
	demo[0]=1.1;
    demo[1]=2.1;
	demo[2]=3.1;
	demo[3]=4.1;
	demo[4]=5.1;
	Sequence < Sequence < double > > seqseq(3);
// simply use the same sequence
	for (int i=0; i<seqseq.getLength();i++)
            seqseq[i]=demo;
// template
	vector <double> v=SeqSeqToVectorTemplate(seqseq);
	cout << "vector1D -> 2D : "<< endl;
	for (int i=0; i< 3; i++){
		for (int j=0;j<5;j++)
			cout<<" "<<v[i*5+j]<<" ";
		cout<<endl;
	}
	return 0;
}

Nous donnons maintenant trois autres conversions (vois aussi ici (en anglais))

//Listing 34 Converting Utilities with Template
// C++
// Inspired by Eric Ethler's code but using template
// See chapter 14
template< typename T>
Sequence< Sequence< T > > VectorTemplateToSeqSeq(const vector < T > &v) {
    Sequence< Sequence< double > > ss(v.size());
    for (unsigned int i=0; i<v.size(); i++) {
        Sequence< T > s(1);
        s[0] = v[i];
        ss[i] = s;
    }
    return ss;
}
template< typename T>
vector < vector < T > >SeqSeqToMatrixTemplate(const Sequence< Sequence < T > >& ss) {
    vector < vector < T > >vv;
    for (int i=0; i<ss.getLength(); i++) {
        vector < T >v;
        for (int j=0; j<ss[i].getLength(); j++)
            v.push_back(ss[i][j]);
        vv.push_back(v);
    }
    return vv;
}
template< typename T>
Sequence< Sequence< T > > MatrixTemplateToSeqSeq(
			const std::vector < std::vector < T > >&vv) {
    Sequence< Sequence< T > > ss(vv.size());
    for (unsigned int i=0; i<vv.size(); i++) {
        std::vector < T > v = vv[i];
        Sequence< T > s(v.size());
        for (unsigned int j=0; j<v.size(); j++)
            s[j] = v[j];
        ss[i] = s;
    }
    return ss;
}

Un moyen de l'utiliser est par exemple :

//Listing 35 Converting Utilities with Template
//C++
	...
// seqseq must be initialized	
	vector <vector <double> > v2=SeqSeqToMatrixTemplate(seqseq);
	cout << "vector2D : "<< endl;
	for (int i=0; i< 3; i++){
		for (int j=0;j<5;j++)
			cout<<" "<<v2[i][j]<<" ";
		cout<<endl;
	}

Puisqu'il existe un tas d'algorithmes basés sur la STL, je pense que ces utilitaires vous seront très utiles.

Retour à la page d'accueil

Page d'accueil du développement C++ à l'aide du SDK

Personal tools