Ottenere path del file in esecuzione, cross platform

Uno dei tanti problemi nei progetti grandi, sono i path relativi. Se il programma ha la necessità di leggere un file durante la sua esecuzione, bisogna capire il path corretto all’interno del codice dove cercare il file.

Come si fa quindi ad ottenere il path del file in esecuzione?
N.B. non quello corrente, ma quello dove risiede il file eseguibile. Quando infatti mandiamo in esecuzione un programma, il path corrente non è detto che è lo stesso del file eseguibile.
Per accorgerci di questa ovvietà, basta considerare i lanciatori, quando chiamiamo per esempio il nostro IDE eseguiremo un comando del genere ./usr/bin/eclipse e il path corrente sarà /home/[nome_utente]

Come accade spesso con il c++, non esiste uno standard per fare ciò che sia portabile su sistemi linux e windows. E’ necessario quindi considerare entrambi i sistemi nel codice. Quindi da codice per sapere su quale sistema è in esecuzione il programma si usano le cosidette direttive al preprocessore. In particolare ci serve la direttiva #ifdef id che funziona esattamente come un ramo if, se la macro id è definita allora, e #endif chiude il ramo.
Esempio:

#ifdef _WIN32
    #include <windows.h>
#endif

La direttiva #include <windows.h> viene eseguita solo se il sistema operativo è windows a 32 o 64 bit.
Qui una lista delle macro che definiscono i sistemi operativi

#ifdef _WIN32
   //Windows 32bit e 64bit
   #ifdef _WIN64
      // Windows solo 64bit
   #endif
#elif __linux
    // linux
#elif __unix // le unix non incluse prima
    // Unix
#elif __posix
    // POSIX
#elif __APPLE__ 
    //quell'altro
    #include "TargetConditionals.h"
#endif

Dopo questa parentesi sulle direttive, come da titolo, vediamo come ottenere il path del file in esecuzione in sistemi linux e windows.
Per il nostro linux, esiste /proc/self/exe. Dove /proc/self è la directory che contiene informazioni sul nostro processo in esecuzione (in generale /proc/[pid] con [pid] identificativo del processo, per ogni processo in esecuzione) ed exe appunto ci dice il path dell’eseguibile del processo. Per ottenere la directory basta rimuovere l’ultima parte del path. Per spostarci nella directory si usa chdir e per visualizzare il path corrente getcwd

#ifdef __linux
    char buf[500];
    readlink("/proc/self/exe", buf, sizeof(buf));
 
    //elimino dalla stringa bufStr il nome dell'eseguibile stesso
    string bufStr = string(buf);
    string pathExe = bufStr.substr(0, bufStr.find_last_of("/"));
    cout << "I'm under linux, executable directory="<<pathExe<<endl;
 
    char buffer[500];
    cout << "I'm now in "<< getcwd(buffer, sizeof(buffer))<<" directory"<<endl;
 
    if(chdir(pathExe.c_str())<0){
	cout << "chdir FAIL."<<endl;
	return;
    }
 
    memset(buffer, 0, sizeof(buffer));//resetto la variabile buffer
    cout << "I'm now in "<< getcwd(buffer, sizeof(buffer))<<" directory"<<endl;
 
#endif

La stessa cosa in windows si può fare usando la libreria windows.h

#ifdef _WIN32
    #include <windows.h>
#endif
 
using namespace std;
#ifdef _WIN32
    CHAR buffer[MAX_PATH];
    if(GetModuleFileName(NULL, buffer, MAX_PATH)==0){
	cout << "GetModuleFileName FAIL. "<< GetLastError()<<endl;
	return;
    }
 
    //con GetModuleFileName ottengo il percorso completo dell'eseguibile,
    //devo eliminare il nome dell'eseguibile stesso
    string bufferStr = string(buffer);
    string pathExe = bufferStr.substr(0, bufferStr.find_last_of("\\"));
 
    //in pathExe ora ci sara' il path dove risiede l'eseguibile corrente
    cout<<"I'm under windows, exe directory="<<pathExe<<endl;
 
    //ora per spostarmi nel path di buffer
    CHAR bufferGet[MAX_PATH];
    DWORD dwRet;
 
    dwRet=GetCurrentDirectory(sizeof(bufferGet), bufferGet);//ottengo la directory corrente
    if(dwRet==0){
        cout << "GetCurrentDirectory FAIL. "<<GetLastError()<<endl;
        return;
    }
    if(dwRet>MAX_PATH){
        cout << "Buffer too small, need "<<dwRet<<" characters."<<endl;
        return;
    }
 
    cout << "I'm now in "<<bufferGet <<" directory"<<endl;
 
 
    //cambio directory in pathExe
    if( !SetCurrentDirectory(pathExe.c_str()) ){
        cout << "SetCurrentDirectory failed " << GetLastError() << endl;
        return;
    }
 
    memset(bufferGet, 0, sizeof(bufferGet));//resetto la variabile bufferGet
 
    dwRet=GetCurrentDirectory(sizeof(bufferGet), bufferGet);//ottengo la directory corrente
    if(dwRet==0){
        cout << "GetCurrentDirectory FAIL. "<<GetLastError()<<endl;
        return;
    }
    if(dwRet>MAX_PATH){
        cout << "Buffer too small, need "<<dwRet<<" characters."<<endl;
        return;
    }
 
    cout << "I'm now in "<<bufferGet <<" directory"<<endl;
#endif

Ecco la funzione completa cross-platform da poter incorporare in un progetto software

#include <iostream>
#include <string.h>
 
 
#ifdef _WIN32
	#include <windows.h>
#endif
 
 
using namespace std;
 
 
void goInExeDirectory(){
 
 
#ifdef __linux
	char buf[500];
	readlink("/proc/self/exe", buf, sizeof(buf));
 
	//elimino dalla stringa bufStr il nome dell'eseguibile stesso
	string bufStr = string(buf);
	string pathExe = bufStr.substr(0, bufStr.find_last_of("/"));
	cout << "I'm under linux, executable directory="<<pathExe<<endl;
 
	char buffer[500];
	cout << "I'm now in "<< getcwd(buffer, sizeof(buffer))<<" directory"<<endl;
 
	if(chdir(pathExe.c_str())<0){
		cout << "chdir FAIL."<<endl;
		return;
	}
 
	memset(buffer, 0, sizeof(buffer));//resetto la variabile buffer
	cout << "I'm now in "<< getcwd(buffer, sizeof(buffer))<<" directory"<<endl;
 
#endif
 
 
#ifdef _WIN32
	CHAR buffer[MAX_PATH];
	if(GetModuleFileName(NULL, buffer, MAX_PATH)==0){
		cout << "GetModuleFileName FAIL. "<< GetLastError()<<endl;
		return;
	}
 
	//con GetModuleFileName ottengo il percorso completo dell'eseguibile,
	//devo eliminare il nome dell'eseguibile stesso
	string bufferStr = string(buffer);
	string pathExe = bufferStr.substr(0, bufferStr.find_last_of("\\"));
 
	//in pathExe ora ci sara' il path dove risiede l'eseguibile corrente
	cout<<"I'm under windows, exe directory="<<pathExe<<endl;
 
	//ora per spostarmi nel path di buffer
	CHAR bufferGet[MAX_PATH];
	DWORD dwRet;
 
	dwRet=GetCurrentDirectory(sizeof(bufferGet), bufferGet);//ottengo la directory corrente
	if(dwRet==0){
		cout << "GetCurrentDirectory FAIL. "<<GetLastError()<<endl;
		return;
	}
	if(dwRet>MAX_PATH){
		cout << "Buffer too small, need "<<dwRet<<" characters."<<endl;
		return;
	}
 
	cout << "I'm now in "<<bufferGet <<" directory"<<endl;
 
 
	//cambio directory in pathExe
	if( !SetCurrentDirectory(pathExe.c_str()) ){
		cout << "SetCurrentDirectory failed " << GetLastError() << endl;
		return;
	}
 
	memset(bufferGet, 0, sizeof(bufferGet));//resetto la variabile bufferGet
 
	dwRet=GetCurrentDirectory(sizeof(bufferGet), bufferGet);//ottengo la directory corrente
	if(dwRet==0){
		cout << "GetCurrentDirectory FAIL. "<<GetLastError()<<endl;
		return;
	}
	if(dwRet>MAX_PATH){
		cout << "Buffer too small, need "<<dwRet<<" characters."<<endl;
		return;
	}
 
	cout << "I'm now in "<<bufferGet <<" directory"<<endl;
 
 
#endif
 
}

Basta poi richiamarla con goInExeDirectory() per spostare il path corrente al path del file eseguibile in esecuzione.

Curiosità: se provate a compilare questo codice con eclipse sul vostro sistema linux, magari usando un compilatore per windows come descritto in questo articolo, intelligentemente eclipse (almeno sono sicuro che la versione 4.2 lo fa, ma suppongo anche le altre) colorerà la parte di codice che non compilerà, cioè quella destinata a windows.
In questo modo
path del file in esecuzione