8.1 مكتبة جي glib | كتاب لينكس الشامل | >> |
مكتبة جي المعروفة اختصاراً glib
(يجب أن لا تخلط بينها وبين glibc فهذه الأخيرة هي مكتبة سي القياسية من جنو)
هذه المكتبة تحتوي وظائف شائعة الإستخدام
وتعمل على معظم الأنظمة المعروفة لسد
الفجوة في مكتبة سي القياسية حيث هناك بعض الوظائف
موجودة حصراً على يونكس خصوصاً تلك التي تتعامل مع أنظمة الملفات.
هذه المكتبة جزء من مشروع gtk. مرجع هذه المكتبة موجد في مجلد
/usr/share/gtk-doc/html/glib/
الذي يأتي مع حزمة
التطوير الخاصة بها glib-devel.
من أهم مميزاتها
لتصنيف برامج على هذه المكتبة يمكن استعمال الأداة pkg-config
bash$ gcc myfile.c -o myfile `pkg-config --cflags --libs glib-2.0`
gchar *
وتكون بتشفير utf8.
ولطباعة نص على الشاشة بنفس طريقة printf
نستعمل g_print
يتم معرفة الخطأ في بعض الوظائف بتمرير مؤشر من نوع
GError *
أحد أعضاؤه
message التي تحتوي رسالة الخطأ
ويمكن تمرير NULL في حال عدم رغبتك في
معرفة تفاصيل الخطأ والاعتماد على القيمة المعادة من الوظيفة.
الكثير من الوظائف تستقبل NULL كمعامل في حال عدم الرغبة
في تحديد قيمة وهذا من أجمل ما في المكتبة.
من مزايا glib أنها آمنة/مضمونة (خصوصاً تلك التي تتعامل مع الذاكرة) حيث أن هناك نوعان من الوظائف الأول ينجز المهمة أو ينهي البرنامج إذا فشل والآخر يعيد لك ما يدل على الخطأ ويعتمد عليك في إصلاحه. مثلاً الوظيفة القياسية malloc تعيد NULL في حال لم تتمكن من حجز الذاكرة (لا يوجد ذاكرة كافية) وتترك لك مهمة إنهاء البرنامج ولكن لأن المبرمجين يظنون بأن الأجهزة حديثة وهناك الكثير من الذاكرة فقد يتكاسل عن فحص "إذا كان NULL اطبع رسالة واخرج" كان من بين الحلول أن تعمل وظيفة في كل برنامج باسم xmalloc تحجز وتخرج إذا فشلت. تقدم لنا glib الوظيفة g_malloc و g_try_malloc الأولى تحجز بشكل موثوق الذاكرة وتنهي البرنامج إذا فشلت والأخرى تحاول وتعيد NULL في حال الفشل. وفي مختلف أنواع الوظائف يوجد هذا الأسلوب. يجدر هنا أن أشير إلى أهم وظئف التعامل مع الذاكرة
gpointer g_malloc(guint bytes);
gpointer g_new(struct_type,number);
gpointer g_realloc(gpointer ptr,guint bytes)
gpointer g_renew(type,ptr,new_number)
void g_free(gpointer ptr)
هناك وظائف متخصصة في عدة مجالات كما في التعامل مع الملفات
وتشغيل/تفريع البرامج ولكن الأهم من ذلك أنها توفر طريقة موحدة بين
النظم المختلفة مثلاً وظائف لدمج اسم الملف مع الدليل
في ويندوز ‘\‘ وفي يونكس ‘/‘ وهكذا. مثل تلك الوظائف
g_find_program_in_path
التي تمرر لها اسم برنامج مثلاً
g_find_program_in_path("ping")
فتبحث عن اسمه الكامل (في PATH) وتعيده كما قد تضيف له .exe في ويندوز
فقد يكون الناتج "C:\\WINNT\\ping.exe"
أما في يونكس "/usr/bin/ping"
.
بل وحتى تحتوي على وظائف الإكمال التلقائي كما في bash (عند ضغط TAB)
حيث تعمل قائمة وتعطيه سلسلة نصية فيعطيك قائمة جزئية.
تستطيع القول أن أي شيء شائع الاستخدام وتحتاجه بكثرة فإن glib توفره
توفر مكتبة glib العديد من الوظائف في العديد من المجالات لذا أفضل أن أشرحها من خلال أمثلة توضيحية.
#include<stdio.h> #include<glib.h> int main() { guint i,j; /* get input */ g_print("Enter a +ve integer less than %d : ",13845163); scanf("%d",&i); j=g_spaced_primes_closest(i); g_print("a prime larger than %d is %d \n",i,j); return 0; }
#include<time.h> #include<stdlib.h> #include<glib.h> int main() { gint i,j,n; GTimer *timer1=g_timer_new(); GError *error=NULL; GRand *rand1=g_rand_new(); /* timer test */ g_print("sleeping for 1,500,000 msec\n"); g_timer_reset(timer1); g_timer_start(timer1); g_usleep(1500000 ); g_timer_stop(timer1); g_print("1,500,000 msec needs %g sec\n",g_timer_elapsed(timer1,NULL)); /* timer test end */ /* test libc random numbers */ g_timer_reset(timer1); g_timer_start(timer1); srand(time(NULL)); for(i=0;i<900000;++i) { j=rand(rand1); j=rand(rand1)%6+1; } g_timer_stop(timer1); g_print("using libc needs %g sec\n",g_timer_elapsed(timer1,NULL)); /* test glib random numbers */ g_timer_reset(timer1); g_timer_start(timer1); g_rand_set_seed(rand1,time(NULL)); for(i=0;i<900000;++i) { j=g_rand_int(rand1); j=g_rand_int_range(rand1,1,7); /* 1,2,..,6 */ } g_timer_stop(timer1); g_print("using glib needs %g sec\n",g_timer_elapsed(timer1,NULL)); return 0; }
#include<stdio.h> #include<glib.h> int main() { GPatternSpec *gp; gchar pat[110]; gchar str[100]; /* get input */ g_print("Enter a pattern: "); fgets(pat,99,stdin); g_print("Enter a string: "); fgets(str,99,stdin); /* simple match means wildcards (*,?) */ if (g_pattern_match_simple(pat,str)) g_print("match\n"); else g_print("no match\n"); /* to make it faster to be called many times */ gp=g_pattern_spec_new(pat); if (g_pattern_match_string(gp,str)) g_print("match\n"); else g_print("no match\n"); return 0; }
#include<stdio.h> #include<glib.h> int main() { gchar *filename="foobar.txt"; gchar *text,*utf8; gsize len; GError *error=NULL; g_print("Loading [%s] ... ",filename); if (!g_file_get_contents(filename,&text,&len,NULL)) { perror("can't open file"); return 1; } g_print("OK\nConverting ... "); utf8=g_convert (text,len,"UTF-8","WINDOWS-1256",NULL,NULL,&error); if (error) { g_print("Error: %s\n",error->message); g_error_free(error); return 1; } g_print("OK\n"); g_print("%s",utf8); return 0; }
#include<glib.h> int main() { GError *error=NULL; GDir *dir=g_dir_open("./",0,&error); const gchar *nm; if (!dir) { g_print("Error: %s\n",error->message); g_error_free(error); return 1; } while((nm=g_dir_read_name(dir))!=NULL) g_print("[%s]\n",nm); g_dir_close(dir); return 0; }
#include<stdio.h> #include<string.h> #include<glib.h> int main() { gint i,j,n; GList *dirlist,*orglist; const gchar *nm; GError *error=NULL; GDir *dir=g_dir_open("./",0,&error); if (!dir) { g_print("Error: %s\n",error->message); g_error_free(error); return 1; } orglist=dirlist=g_list_alloc(); if (!dirlist) { perror("Can't Alloc memory"); return 1; return 1; } g_print("Adding to the list\n"); i=0; while((nm=g_dir_read_name(dir))!=NULL) { dirlist=g_list_append(dirlist,(gpointer)strdup(nm)); /* or simply but slower: g_list_append(dirlist,(gpointer)strdup(nm)); */ g_print("%d) adding [%s]\n",++i,nm); } g_dir_close(dir); g_print("%d element in the list\n",(n=g_list_length(dirlist))); g_print("random access to the list(slow)\n"); dirlist=g_list_first(dirlist); for (i=1;i<n;++i) { g_print("%d) [%s]\n",i,(gchar *)g_list_nth_data(dirlist,i)); /* or similarly: g_print("%d) [%s]\n",i,(gchar *)(g_list_nth(dirlist,i)->data)); */ } g_print("sequential access to the list(fast)\n"); dirlist=g_list_first(dirlist); orglist=dirlist; while((dirlist=g_list_next(dirlist))!=NULL) { g_print("[%s]\n",(gchar *)dirlist->data); } return 0; }
#include<stdio.h> #include<string.h> #include<glib.h> int main() { gint i,j,n; GPtrArray *dirarray; const gchar *nm; GError *error=NULL; GDir *dir=g_dir_open("./",0,&error); if (!dir) { g_print("Error: %s\n",error->message); g_error_free(error); return 1; } dirarray=g_ptr_array_new(); if (!dirarray) { perror("Can't Alloc memory"); return 1; } g_print("Adding to the array\n"); i=0; while((nm=g_dir_read_name(dir))!=NULL) { g_ptr_array_add(dirarray,(gpointer)strdup(nm)); g_print("%d) adding [%s]\n",++i,nm); } g_dir_close(dir); g_print("%d element(s) in the array\n",(n=dirarray->len)); g_print("random access to the array(fast)\n"); for (i=0;i<n;++i) { g_print("%d) [%s]\n",i+1,(gchar *)(dirarray->pdata[i])); /* or similarly: g_print("%d) [%s]\n",i+1,(gchar *)(g_ptr_array_index(dirarray,i)) ); */ } return 0; }
/* * txt2html.c convert a text file to html/xml * by Moayyad Saleh al-Sadi<alsadi[at]gmail.com> */ #include<stdio.h> /* for fopen */ #include<string.h> // for strstr() #include<glib.h> int main(int argc,char *argv[]) { gint i; gchar *filename,*oname="a.out.xml.html"; gchar *text,*xml; gsize len; GError *error=NULL; FILE *f; gboolean v=TRUE; /* verbous */ if (argc!=2) { fprintf(stderr,"syntax:\n\ttxt2html FILE\n"); return 1; } filename=argv[1]; if (v) g_print("Creating [%s] ... ",oname); f=fopen(oname,"wt+"); /* output the html header */ fprintf(f," <?xml vesion='1.0' ?> <html> <head> <title>%s</title> </head> <body> <div dir="ltr"><pre>\n" ,filename); /* End of html header*/ if (v) g_print("OK\nLoading [%s] ... ",filename); if (!g_file_get_contents(filename,&text,&len,NULL)) { perror("can't open file"); return 1; } if (v) g_print("OK\nEscaping ... "); xml=g_markup_escape_text(text,len); if (v) g_print("OK\nSaving ... "); fprintf(f,"%s",xml); if (v) g_print("OK\n"); /* output of html footer */ fprintf(f," </pre></div> </body> </html>\n"); /* End of html footer */ if (v) g_print("OK\n"); fclose(f); return 0; }
تعني تنفيذ أكثر من وظيفة دفعة واحدة وهي تشبه عملية تعدد المهام (تنفيذ أكثر من برنامج/عملية دفعة واحدة) ، ولكن هنا بدل إطلاق fork لعملية بنت للعملية الحالية نطلق وظيفة (تظهر في ps على أن البرنامج منفذ أكثر من مرة ولكن الحقيقة أنه يشغل نفس المكان الواحد في الذاكرة). إنها الطريقة التي تمكن موزيلا من تصفح أكثر من موقع أو تمكن nautilus من تصفح مجلد أثناء نسخه لملفات أو تمكنه من تحميل صور أيقونات الملفات الألف في الدليل البيت دفعة واحدة.
في glib كل thread عبارة عن وظيفة تأخذ معامل وحيد نوعه مؤشر تضعه أنت وفق احتياجاتك عند عمل ال thread وذلك لمزيد من المرونة (مثلاً نفس الوظيفة تستخدم في أكثر من thread) ، وتعيد هذه الوظيفة مؤشر(حتى يتسع لأي نوع تريد) للناتج الذي يفترض أن يأخذه كل ما ينتظر إنتهاؤه. صيغة الوظيفة
gpointer my_func(gpointer data);
g_thread_init(NULL);
وهذه يجب أن تستدعى مرة واحدة في بداية البرنامج إذا كنت غير متأكد من أنها المرة
الأولى انظر هل هذه الميّزة فعّالة وإلا استدع تلك الوظيفة
if (!g_thread_supported()) g_thread_init(NULL);
GError *error; GThread *thread1=g_thread_create(my_func,NULL,TRUE,&error);
لتعديل أولوية وظيفة محددة نستعمل g_thread_set_priority المعامل الأول هو مؤشر GThread والثاني هو أحد G_THREAD_PRIORITY_LOW أو G_THREAD_PRIORITY_NORMAL أو G_THREAD_PRIORITY_HIGH أو G_THREAD_PRIORITY_URGENT التي تعني أولوية منخفضة أو عادية أو عالية أو عاجلة (الأخيرة قد توقف كل الباقيات) ، إذا كنت تريد زيادة أولوية thread معين داخل الوظيفة الخاصة به لا داع لاستقبال مؤشر GThread عن طريق المعامل فالوظيفة g_thread_self تعيد لك هذا المؤشر. لانتظار ومعرفة ناتج تنفيذ thread معين يمكن استدعاء g_thread_join وتمرير GThread لها فتعيد مؤشر قيمته ما أعادته الوظيفة. ويمكن لل thread إتاحة الفرصة للthreads الأخرى أثناء الحسابات الطويلة باستدعاء g_thread_yield
في هذا المثال نطلق 3 threads الأول inc يزيد من قيمة متغير total ويطبع رسالة والثاني dec ينقصها ويطبع رسالة والثالث show فقط يطبع رسالة. كل واحد يكرر 10 مرات إلا الأخير فهو يكرر حتى يتم تنفيذ الأول 10 مرات (نعرف ذلك من خلال متغير inc_times)
/* thread1.c: this file show you how threads work */ #include<stdio.h> #include<stdlib.h> #include<string.h> #include<glib.h> gint inc_times=0; gint dec_times=0; gint total=0; gpointer show(gpointer foo) { while(inc_times<10 || total !=0) { g_print("show: inc_times=%i , dec_times=%i , total=%i\n", inc_times,dec_times,total); } g_print("show: inc_times=%i , dec_times=%i , total=%i\n", inc_times,dec_times,total); return NULL; } gpointer inc(gpointer foo) { int i; for (i=0;i<10;++i) { ++total; ++inc_times; g_print("inc: inc_times=%i , dec_times=%i , total=%i\n", inc_times,dec_times,total); // while(total>0) g_thread_yield(); // wait other threads } return NULL; } gpointer dec(gpointer foo) { int i; for (i=0;i<10;++i) { --total; ++dec_times; g_print("dec: inc_times=%i , dec_times=%i , total=%i\n", inc_times,dec_times,total); // while(total<0) g_thread_yield(); // wait other threads } return NULL; } int main(int argc,char *argv[]) { GError *error=NULL; GThread *thread1,*thread2,*thread3; if (!g_thread_supported()) g_thread_init(NULL); g_print("forking 3 threads\n"); if (!(thread1=g_thread_create(inc,NULL,TRUE,&error))) { g_print("Error: %s\n",error->message); exit(1); } if (!(thread2=g_thread_create(dec,NULL,TRUE,&error))) { g_print("Error: %s\n",error->message); exit(1); } if (!(thread3=g_thread_create(show,NULL,TRUE,&error))) { g_print("Error: %s\n",error->message); exit(1); } g_thread_join(thread3); // wait for thread3 }
system("cat foobar | sed -e 's/#include<(.*)>/#include\"\\1\"/g'");
والآخرى مثلاً system("diff /etc/mtab /proc/mounts");
while(total>0) g_thread_yield();
و while(total<0) g_thread_yield();
يجب الانتباه عند كتابة برنامج Multi-threads لعدة أشياء منها
_r
مثلاً strerror و strerror_r
و tmpnam و tmpnam_r
الوظائف الثانية تستعمل malloc لحجز الذاكرة في كل مرة وتكون مهمة التحرير عليك.
توفر glib وظائف mutex التي يمكن استعمالها لضمان
عدم تضارب مصالح كل thread مع الآخر حيث g_mutex_lock تنتظر تحرره
ثم تحجزه هي و g_mutex_unlock تعلن أنه عاد حراً.
يجب أن يكون الكود بينهما سريع لأنك لا تريد أن تبقى الأخرى تنتظر.
هذا ال mutex يحجز مرة واحد في البرنامج
(وليس في كل thread) بالوظيفة g_mutex_new.
هذه نسخة معدلة من البرنامج السابق بطريقة آمنة.
/* thread2.c: this file show you how threads work * this is a safe version of thread1.c * * the safe version lock a global GMutex to make sure no other * thread using it * */ #include<stdio.h> #include<stdlib.h> #include<string.h> #include<glib.h> gint inc_times=0; gint dec_times=0; gint total=0; static GMutex *my_mutex; // allocated once as global gpointer show(gpointer foo) { while(inc_times<10 || total !=0) { g_mutex_lock(my_mutex); g_print("show: inc_times=%i , dec_times=%i , total=%i\n", inc_times,dec_times,total); g_mutex_unlock(my_mutex); } g_mutex_lock(my_mutex); g_print("show: inc_times=%i , dec_times=%i , total=%i\n", inc_times,dec_times,total); g_mutex_unlock(my_mutex); return NULL; } gpointer inc(gpointer foo) { int i; for (i=0;i<10;++i) { g_mutex_lock(my_mutex); ++total; ++inc_times; g_print("inc: inc_times=%i , dec_times=%i , total=%i\n", inc_times,dec_times,total); g_mutex_unlock(my_mutex); while(total>0) g_thread_yield(); // wait other threads } return NULL; } gpointer dec(gpointer foo) { int i; for (i=0;i<10;++i) { g_mutex_lock(my_mutex); --total; ++dec_times; g_print("dec: inc_times=%i , dec_times=%i , total=%i\n", inc_times,dec_times,total); g_mutex_unlock(my_mutex); while(total<0) g_thread_yield(); // wait other threads } return NULL; } int main(int argc,char *argv[]) { GError *error=NULL; GThread *thread1,*thread2,*thread3; if (!g_thread_supported()) g_thread_init(NULL); my_mutex=g_mutex_new(); g_print("forking 3 threads\n"); if (!(thread1=g_thread_create(inc,NULL,TRUE,&error))) { g_print("Error: %s\n",error->message); exit(1); } if (!(thread2=g_thread_create(dec,NULL,TRUE,&error))) { g_print("Error: %s\n",error->message); exit(1); } if (!(thread3=g_thread_create(show,NULL,TRUE,&error))) { g_print("Error: %s\n",error->message); exit(1); } g_thread_join(thread3); // wait for thread3 }
شرحنا سابقاً في فصل مشاريع متعددة الملفات كيف يمكنك من خلال dl تحميل مكتبات وقت التنفيذ وقلنا أن تلك الطريقة خاصة بجنو/لينكس GNU/Linux توفر لنا glib توافقية عبر عدد كبير من الأنظمة مثل لينكس و ويندوز وتمكننا من عمل ذلك حتى في ويندوز. يكون ذلك بالتأكد أولاً من أن ذلك مسموحاً باستدعاء g_module_supported فإذا كانت FALSE نخرج. نحمل المكتبة ب g_module_open التي تأخذ معاملين الأول هو اسم الملف والثاني هو إما صفر أو G_MODULE_BIND_LAZY تعيد الوظيفة مؤشر ل GModule ثم نستعمل g_module_symbol التي تأخذ 3 معاملات أولها مؤشر ل GModule الخاص بالمكتبة ثم الوظيفة/المتغير الذي تبحث عنه ثم مؤشر ليتم وضع الناتج فيه. تعيد هذه الوظيفة TRUE في حال النجاح.
/* ... */ void (*myfunc)(); /* function to import */ if (!g_module_supported()) exit(-1); /* add dir like '/usr/lib' to name to get /usr/lib/lib[name].so */ gchar *path=g_module_build_path(".","mylib"); GModule *module1=g_module_open(path,0); if (!module1) { /* file not found */ g_print("ERROR: file '%s' not found\n",path); exit(1); } g_free(path); /* free it, we don't neet it later */ if (!g_module_symbol(module1,"myfunc",&myfunc)) { /* 'myfunc' not found */ g_print("ERROR: %s\n",g_module_error(module1)); exit(1); } myfunc(); /* call the loaded function */ g_module_close(module1);/* unload lib */ /* ... */
<< السابق | كتاب لينكس الشامل | التالي >> |