16
Tags:
c++
Skrevet af
Bruger #2695
@ 05.02.2004
Hej alle C/C++ kodere.
Denne artikel er tilgængt dem, som vil udvikle software, som kan compile under flere operativ systemer som f.eks. Windows og Linux. Jeg håber, det vil lykkes mig at få flere til at skrive arkitektur uafhængig kode :-)
Baggrund
C/C++ definerer en syntax som er standardiseret. Det betyder i teorien, at kode, som compiler under Linux, også burde compile under Windows. Det er bare ikke altid tilfældet og det kan der være flere årsager til:
* Man bruger et API, som er tilgængeligt på én platform men ikke på en anden.
* Compilere virker desværre ikke altid helt éns.
Det er alligevel muligt, at producere et stort program, som kan compile på flere platforme og som endda virker éns. Det er det jeg vil beskrive i denne artikel.
Betinget compilering
Inden C/C++ kode compiles, bliver den præprocesseret. Det betyder at visse strenge i koden bliver udskiftet med noget andet. F.eks. udskifter præprocessoren '#include <iostream>' med indholdet af iostream filen og hvis man har en '#define MAX(x,y) ((x) > (y) ? (x) : (y))' så vil præprocessoren efterfølgende udskifte alle referencer til MAX med '((x) > (y) ? (x) : (y))'.
Præprocessoren har også en feature, der gør det muligt at fjerne kode, som under visse omstændigheder ikke skal compiles, og det er denne feature, vi skal kigge nærmere på.
Ligesom man kan definere symboler '#define SOME_SYMBOL', så kan man også tjekke på, om et symbol er defineret. Det gøres med '#if defined(SOME_SYMBOL)'. Det kan illustreres med et lille eksempel:
#include <iostream>
using namespace std;
#define SOME_SYMBOL
int main(int argc, char ** argv)
{
#if defined(SOME_SYMBOL)
cout << "SOME_SYMBOL er defineret" << endl;
#endif
return 0;
}
Man kan også tjekke om et symbol
ikke er defineret. Dette gøres med '#if !defined'. Eller man kan lave en '#else' eller '#elsif defined' som vist i dette eksempel:
#include <iostream>
using namespace std;
#define SOME_SYMBOL
int main(int argc, char ** argv)
{
#if defined(SOME_SYMBOL)
cout << "SOME_SYMBOL er defineret" << endl;
#elif defined(SOME_OTHER_SYMBOL)
cout << "SOME_OTHER_SYMBOL er defineret" << endl;
#else
cout << "Hverken SOME_SYMBOL eller SOME_OTHER_SYMBOL er defineret." << endl;
#endif
return 0;
}
Alle compilere kommer med nogle symboler, som altid er defineret. Under Linux er symbolet '__linux__' altid defineret og under Windows er '_WIN32' defineret. Det kan vi så tjekke på:
#include <iostream>
using namespace std;
int main(int argc, char ** argv)
{
#if defined(_WIN32)
cout << "Hello Windows user" << endl;
#elif defined(__linux__)
cout << "Hello Linux user" << endl;
#else
cout << "Hello user of unknown operating system" << endl;
#endif
}
Fejl
Præprocessoren har også en feature der gør det muligt at standse compileringen, hvis vi mener, at der er noget galt. Denne feature hedder '#error' og bruges sådan her:
#include <iostream>
using namespace std;
int main(int argc, char ** argv)
{
#if defined(_WIN32)
cout << "Hello Windows user" << endl;
#elif defined(__linux__)
cout << "Hello Linux user" << endl;
#else
#error Unsupported operating system
#endif
}
Et fuldt eksempelVi kan nu gå i gang med at lave et mere realistisk eksempel på et multiplatform program. Når man udvikler, laver man som oftest første version på én platform, og når den så virker, så porterer man koden til en anden, og den næste, osv. Jeg er Linux bruger, så jeg udvikler under Linux og porter til Windows.
Vores lille projekt bliver et program, som lister alle filer og directories i en given path.
Under Linux bruger man følgende funktioner til at iterere gennem filerne i et directory:
#include <dirent.h>
DIR *opendir(const char *name);
struct dirent *readdir(DIR *dir);
int closedir(DIR *dir);
Vi prøver med Linux versionen:
#include <iostream>
#include <dirent.h>
using namespace std;
int main(int argc, char ** argv)
{
//Hvis en sti er blevet specificeret så brug den
//Ellers bruger vi den nurværende sti
string path(argc > 1 ? argv[1] : ".");
DIR * dir = opendir(path.c_str());
//Hvis vi ikke kan åbne stien skriver vi en fejl
if(dir == NULL)
{
cerr << "Kunne ikke åbne stien: " << path << endl;
return -1;
}
struct dirent * ent;
//Iterer gennem alle entries
while((ent = readdir(dir)))
{
//Skriv navnet
cout << ent->d_name << " ";
//Skriv typen
switch(ent->d_type)
{
case DT_FIFO: cout << "FIFO"; break;
case DT_CHR: cout << "CHARACTER device"; break;
case DT_DIR: cout << "Directory"; break;
case DT_BLK: cout << "BLOCK device"; break;
case DT_REG: cout << "File"; break;
case DT_LNK: cout << "Symbolic link"; break;
case DT_SOCK: cout << "Socket"; break;
default: cout << "Unknown"; break;
}
cout << endl;
}
closedir(dir);
return 0;
}
Det det ser ud til at virke udmærket. Vi søger lidt på msdn.microsoft.com og finder ud af at de tilsvarende Windows kald hedder:
#include <windows.h>
HANDLE FindFirstFile(LPCTSTRlpFileName, LPWIN32_FIND_DATAlpFindFileData);
BOOL FindNextFile(HANDLEhFindFile, LPWIN32_FIND_DATAlpFindFileData);
BOOLFindClose(HANDLEhFindFile);
Så vi præprocesserer Linux koden ud og laver en Windows version:
#include <iostream>
#include <string>
#if defined(__linux__)
//dirent.h er Linux specifik
#include <dirent.h>
#elif defined(_WIN32)
//windows.h er Windows specifik
#include <windows.h>
#else
//Ukendt operativ system
#error Usupporteret operativ system
#endif
using namespace std;
int main(int argc, char ** argv)
{
//Hvis en sti er blevet specificeret så brug den
//Ellers bruger vi den aktuelle sti
string path(argc > 1 ? argv[1] : ".");
#if defined(__linux__)
//Her er Linux koden
DIR * dir = opendir(path.c_str());
//Hvis vi ikke kan åbne stien skriver vi en fejl
if(dir == NULL)
{
cerr << "Kunne ikke åbne stien: " << path << endl;
return -1;
}
struct dirent * ent;
//Iterer gennem alle entries
while((ent = readdir(dir)))
{
//Skriv navnet
cout << ent->d_name << " ";
//Skriv typen
switch(ent->d_type)
{
case DT_FIFO: cout << "FIFO"; break;
case DT_CHR: cout << "CHARACTER device"; break;
case DT_DIR: cout << "Directory"; break;
case DT_BLK: cout << "BLOCK device"; break;
case DT_REG: cout << "File"; break;
case DT_LNK: cout << "Symbolic link"; break;
case DT_SOCK: cout << "Socket"; break;
default: cout << "Unknown"; break;
}
cout << endl;
}
closedir(dir);
#elif defined(_WIN32)
WIN32_FIND_DATA findData;
HANDLE dir = FindFirstFile((path + "\\\\*.*").c_str(),&findData);
//Hvis dette gik galt
if(dir == INVALID_HANDLE_VALUE)
{
cerr << "Kunne ikke åbne " << path << endl;
return -1;
}
do
{
//Skriv navn
cout << findData.cFileName << " ";
//Skriv type
if(findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
cout << "Directory";
else if(findData.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE)
cout << "Archive";
else if(findData.dwFileAttributes & FILE_ATTRIBUTE_NORMAL)
cout << "File";
else
cout << "Unknown";
cout << endl;
} while(FindNextFile(dir,&findData));
FindClose(dir);
#endif
return 0;
}
Så nemt er det.
Arkitektur
Betinget compilering er en stærk feature men jo mindre man behøver at bruge den desto bedre, derfor bør man opbygge en arkitektur som abstraherer over forskelle i platformen.
Hvis vi skal bruge fil listninger flere steder i vores program ville det hurtigt blive grimt med en masse '#if defined' overalt. I stedet kunne vi definere en klasse, som indkapsler denne afhængighed og derefter glemme alt om den.
Man kan også slippe for at bekymre sig om arkitekturen ved at bruge platformuafhængige libraries. F.eks. kan man bruge ACE (
http://www.cs.wustl.edu/~schmidt/ACE.html) til netværks og multi trådet programmering og Crypto++ (
http://www.eskimo.com/~weidai/cryptlib.html) til kryptering.
Afslutning
Det var det !! Jeg håber ikke, at det var så svært, og jeg håber, at der kommer lidt mere platformuafhængig kode fra nu af.
Betinget compilering kan også bruges til andet end at skrive platform uafhængig kode.
Hvis man f.eks. er flere om at skrive på den samme kode og man gerne vil lave noget som måske ikke compiler eller virker længe, så kan man sørge for at det kun compiler hvis TEST_NEW_NET_PROTOCOL er defineret. Der er mange andre muligheder..flere end jeg vil nævne her.
Hvad synes du om denne artikel? Giv din mening til kende ved at stemme via pilene til venstre og/eller lægge en kommentar herunder.
Del også gerne artiklen med dine Facebook venner:
Kommentarer (5)
God artikel, men du kunne nok godt forklare lidt bedre hvad der sker i det sidste eksempel
Helt sikkert en god artikel, har længe manglet den!
Fint at du nævner ACE osv, men hvad med Boost??
www.boost.org
Så undgår du alle de grimme defines, plus du får lækker kode.
fin artikel men det går rigtig hurtig til sidst og ikke helt forklaret men 4 fordi at resten af artiklen var kanon"
Super artikel. Du får fire. Det er bestemt noget jeg vil have i baghovedet, næste gang jeg skriver et program
.
Du skal være
logget ind for at skrive en kommentar.