torsdag 26 mars 2009

HOWTO use GTK+ with Eclipse

I found very little information on Google on how to use GTK+ 2.0 together with Eclipse and C/C++, which I recently had to do. This very small howto explains the procedure on Ubuntu.

First, if you dont have Eclipse installed, get the Eclipse tars for C/C++ development from eclipse.org. Install it and add

export PATH=$PATH:/usr/local/eclipse

to your .bashrc if you want.

Then get the libgtk and the gnome development kit:

sudo apt-get install libgtk2.0-dev
sudo apt-get install gnome-core-devel

If you are using C++ you want to get gtkmm (the C++ API for GTK+, not needed but strongly recommended):

sudo apt-get install libgtkmm-2.4-dev

Now fire up Eclipse. Create a new Managed Make C or C++ project.

GTK+ uses a lot of different include paths and libs. To facilitate development they have a nifty tool called pkg-config that generates the correct dependencies. Open up Project Properties -> C/C++ Build. Select the Tool Settings tab and then GCC C/C++ Compiler -> Miscellaneous. Append the following line to the end of "Other flags":

`pkg-config --cflags gtk+-2.0`

, if you are using the standard C API. If use are using gtkmm, instead put

`pkg-config --cflags gtkmm-2.4`

Add one of the following lines (depending if you use gtkmm or not) to GCC C/C++ Linker -> Miscellaneous -> Linker flags:

`pkg-config --libs gtk+-2.0`

or

`pkg-config --libs gtkmm-2.4`

Finished! Eclipse will now set up include paths and libraries correctly, although you will never see them in the project browser.

lördag 21 mars 2009

Från hanterad kod till C++, del 1

Detta är del 1 i några kommande inlägg som kanske skulle kunna kallas "Från hanterad kod till C++". Inläggen är skrivna för dig som mestadels jobbar med ett hanterat språk (.NET/Java etc.), men av någon anledning nu blivit tvungen att gå över till C++.

Jag upplevde detta ganska nyligen och la märke till att det var flera saker i C++ som inte alls är självklara, och som upplevs som onödigt krångliga. Ändå hade jag läst C++ på högskolan, men jag märkte att det är stor skillnad på de korta labbar man skrev där jämfört med ett stort, riktigt C++ projekt utvecklat av ett team experter.

Kod i .NET/Java tenderar att aldrig bli lika openetrerbar som C++ kod.Detta är inte någon fullständig guide i C++, utan inläggsserien pekar istället ut ett fåtal valda områden som jag tror många har svårt för, av den enkla anledningen att jag själv hade svårt med dem.

Inläggen förutsätter att du har grundläggande kunskaper i C++, tex i hur man använder include-filer och deklarerar klasser och variabler. Du bör också veta vad pekare är och hur de används. Om du har mycket mer erfarenhet än så, å andra sidan, kommer du nog tycka följande text är tämligen värdelös.

Svårigheterna med C++ jämfört med Java/.NET
Om man kommer från Java/.NET världen och bara tittar på någon annans C++ kod tänker man lätt "Det här är inte möjligt. Hur kan någonting vara så oläsbart och omständligt?." Ett bra exempel är stränghantering, som ofta tar sig groteska uttryck i C++, samt extremt långa och svårläsliga funktionsdeklarationer.

De största problemen med språket som jag ser det är följande:
  • Legacy. Då C++ även ska inkorporera C, där man kodar helt annorlunda än i C++, känns hela språket bloatat med funktionalitet och operatorer som man i modernare språk inte behöver. Det har dessutom hunnit komma ut en rad med överlappande frameworks, och varje utvecklare har sina egna favoritfunktioner,
  • Begreppsförvirring. Det finns många riktigt skumma operatorer och keywords i C++ som kan betyda helt olika saker beroende på i vilket sammanhang de används,
  • Pekare. Även om teorin är ganska enkel blir det ofta förvirrat, vilket inte blir bättre av C++ många, mångtydiga operatorer.Vi kan börja med ett exempel. En inte helt overklig funktionsdeklaration i C++ skulle kunna se ut så här:

MYCALLCONV template <class T> inline const char* myfunc(
const int& _myvar1, T*& _myvar2,
void (*fnc)(int**)) const;

Vissa dödliga skulle kunna påstå att ovanstående deklaration är svårpenetrerad. Vi återkommer till deklarationen i slutet av inläggsserien då vi fått lite mer kött på benen angående C++ egenheter. Till att börja med ska vi titta på några av de mer icke-uppenbara sätt som pekare och referenser fungerar.

Pekare, referenser och kopior
I .NET kan man explicit deklarera pekare i ett unsafe-block, men vanligtvis behöver man inte göra det. Som bekant är allt förutom grundtyperna (int, char, bool osv) referenser (alltså pekare) i hanterad kod, och därför behöver man aldrig oroa sig om value-type eller reference-type, då man vet att vad man än skickar in i en metod så blir det en referens (undantaget grundtyperna alltså). I C++ är det inte lika enkelt. Se följande kod:

MyClass myfunc(MyClass obj)
{
obj.variable = 2;
MyClass declInFunc;
return declInFunc;
}

int main()
{
MyClass declInMain, retVal;
declInMain.variable = 1;
retVal = myfunc(declInMain);
return 0;
}

För det första. Efter anropen i main kommer inte declInMain.variable att ha ändrats, den kommer att vara 1. Det beror på att så fort man skickar en klass/variabel in i en funktion så anropas först klassens s.k copy-constructor, som objektet. Man kan deklarera en copy-constructor explicit, men vanligtvis klarar man sig med en implicit variant som kompilatorn alltid genererar (mer om detta senare).

För det andra. Ni som kan C skulle säga att declInFunc och retVal kommer att innehålla ett odefinierat värde, då scopet slutar där myfunc slutar. Igen så använder C++ automatiskt en copy-constructor för returvärden, så retVal kommer att innehålla vad vi förväntar oss. Mycket praktiskt.

I Java/.NET kan man skriva likadant utan att declInFunc pekar på något ogiltigt, men av en annan anledning, nämligen den automatiska minneshanteraren. Om vi istället vill att declInMain skulle ändras i funktionen (alltså vara en referens) kan vi t ex definiera funktionen så här istället:

MyClass myfunc(MyClass &obj);

Nu behandlas obj som en referens istället, vilket i Java/.NET är standardsättet att skicka parametrar på. Notera att &-tecknet lätt kan blandas ihop med dereferreringsoperatorn för pekare, ytterligare en onödigt förvirrade detalj i C++. Man kan självklart också uppnå samma sak genom att skicka in en vanlig pekare i funktionen:

MyClass myfunc(MyClass *obj)

men detta kräver att parametern man skickar in är en pekare. Ibland är det bra att skicka en parameter som referens, även fast funktionen inte alls tänker ändra på parametern. Detta beror på att copy-constructorn inte anropas då man skickar en referens, och copy-construtorn kan vara långsam för stora klasser.

new/delete
En annan egenhet är nyckelordet new i C++, som inte är exakt samma sak som new i Java/.NET. Jag har läst flera böcker och guider på Internet som misslyckas med att förklara precis vad new är bra för, så låt mig också misslyckas. Jämför dessa kodsnuttar:

void func()
{
BigClass obj;
...
}

void func2()
{
BigClass* obj = new BigClass(”inargument”);

delete obj;
}

I första fallet deklareras obj på stacken, och objektet kommer alltså att förstöras och minnet frigöras då funktionen avslutas. BigClass konstruktor kommer att anropas, men utan möjlighet att ge några argument till konstruktorn. I .NET/Java världen skulle obj varit oinitsialiserad efter detta, men icke så i C++ alltså, där konstruktorn kallas automatiskt.

I andra fallet deklareras obj på heapen genom att vi använder new, och vi måste explicit frigöra minnet med ett delete-anrop inom samma scope, annars uppstår en minnesläcka.

Varför göra som i exempel 2 och använda new/delete? För det första så är det inte lämpligt att deklarera stora objekt (t ex bilder eller stora listor) på stacken. Stacken har en begränsad storlek och deklarerar man för mycket minne får man det klassiska felet "stack overflow". Heapen å andra sidan växer då det behövs och begränsas egentligen bara av tillgängligt arbetsminne.

För det andra används new då man vill deklarera en array som man inte vet storleken på vid kompileringstid.

Det är det enda man behöver komma ihåg om new/delete. I modern C++ använder man sig dock alltid av automatiska pekare som har automatisk minneshantering tillagt, ungefär som för hanterad kod. Mer om detta i senare delar.

Som en not till C användare är new samma anrop som malloc(), förutom att man också har möjligheten att skicka med argument till konstruktorn.

ref-to-ptr
Man ser ofta följande deklaration i lite större projekt:

void myfunc(MyClass *&obj);

Detta ser ju inte så trevligt ut. Uttrycket betyder att man skickar in en referens till en pekare, och detta måste man göra om man vill modifiera själva pekaren inne i funktionen (alltså inte värdet som pekaren pekar på). Om man istället bara använder:

void myfunc(MyClass *obj);

int main()
{
MyClass* inParam = new MyClass();
myfunc(inParam);
}

så går det visserligen bra att göra ändringar i obj i myfunc som slår igenom till det inskickade inParam objektet. Men om man i myfunc försöker få obj att peka på ett helt annat objekt (alltså en annan minnesadress) kommer detta inte att slå igenom, då själva pekaren kopieras vid funktionsanropet (via vår kära copy-konstruktor).

Man kan uppnå samma funktionalitet genom att använda ett så kallat ptr-to-ptr uttryck, alltså

void myfunc(MyClass **obj);

Jag föredrar dock att alltid använda ref-to-ptr, då den sistnämnda deklarationen lätt kan förväxlas med en flerdimensionell array.

Slutsats
Detta var det jag på direkten kunde komma på angående pekare och new som för mig kändes krångligast just då man tittade på kollegornas kod. Nästa inlägg kommer att handla mer om speciella egenheter med C++ och den uppsjö av keywords som språket innehåller.