8.1 مكتبة جي glib كتاب لينكس الشامل >>

8.1 مكتبة جي glib

8.1.1 مقدمة

مكتبة جي المعروفة اختصاراً 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 لتمثيل المحرف الواحد و gint و glong و gfloat و gdouble لتمثيل الأرقام ونستعمل guchar و guint و gulong لغير السالبة. وإذا كنت مهتماً بالحجم فهناك gint8 و gint16 و gint32 و gint64 وغير السالبة guint8 و guint16 و guint32 و guint64. ولتمثيل السلاسل النصية نستعمل مؤشرات من نوع 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_malloc0 و g_new0.

هناك وظائف متخصصة في عدة مجالات كما في التعامل مع الملفات وتشغيل/تفريع البرامج ولكن الأهم من ذلك أنها توفر طريقة موحدة بين النظم المختلفة مثلاً وظائف لدمج اسم الملف مع الدليل في ويندوز ‘\‘ وفي يونكس ‘/‘ وهكذا. مثل تلك الوظائف g_find_program_in_path التي تمرر لها اسم برنامج مثلاً g_find_program_in_path("ping") فتبحث عن اسمه الكامل (في PATH) وتعيده كما قد تضيف له .exe في ويندوز فقد يكون الناتج "C:\\WINNT\\ping.exe" أما في يونكس "/usr/bin/ping". بل وحتى تحتوي على وظائف الإكمال التلقائي كما في bash (عند ضغط TAB) حيث تعمل قائمة وتعطيه سلسلة نصية فيعطيك قائمة جزئية. تستطيع القول أن أي شيء شائع الاستخدام وتحتاجه بكثرة فإن glib توفره

8.1.2 أمثلة

توفر مكتبة 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;
}

8.1.3 تعدد خيوط المعالجة multi-threads

تعني تنفيذ أكثر من وظيفة دفعة واحدة وهي تشبه عملية تعدد المهام (تنفيذ أكثر من برنامج/عملية دفعة واحدة) ، ولكن هنا بدل إطلاق fork لعملية بنت للعملية الحالية نطلق وظيفة (تظهر في ps على أن البرنامج منفذ أكثر من مرة ولكن الحقيقة أنه يشغل نفس المكان الواحد في الذاكرة). إنها الطريقة التي تمكن موزيلا من تصفح أكثر من موقع أو تمكن nautilus من تصفح مجلد أثناء نسخه لملفات أو تمكنه من تحميل صور أيقونات الملفات الألف في الدليل البيت دفعة واحدة.

في glib كل thread عبارة عن وظيفة تأخذ معامل وحيد نوعه مؤشر تضعه أنت وفق احتياجاتك عند عمل ال thread وذلك لمزيد من المرونة (مثلاً نفس الوظيفة تستخدم في أكثر من thread) ، وتعيد هذه الوظيفة مؤشر(حتى يتسع لأي نوع تريد) للناتج الذي يفترض أن يأخذه كل ما ينتظر إنتهاؤه. صيغة الوظيفة

gpointer my_func(gpointer data);
أولاً تأكد من تشغيل هذه الميّزة ب g_thread_init(NULL); وهذه يجب أن تستدعى مرة واحدة في بداية البرنامج إذا كنت غير متأكد من أنها المرة الأولى انظر هل هذه الميّزة فعّالة وإلا استدع تلك الوظيفة
if (!g_thread_supported()) g_thread_init(NULL);
اعمل thread جديد من خلال g_thread_create المعامل الأول هو الوظيفة والثاني هو مؤشر للبيانات التي قد ترغب بتمريرها للوظيفة والثالث TRUE إذا كنت تريد أن يتم انتظار هذا thread والحصول على الناتج والمعامل الأخير هو عنوان مؤشر GError تعيد الوظيفة مؤشر من نوع GThread
GError *error;
GThread *thread1=g_thread_create(my_func,NULL,TRUE,&error);
بعد هذا السطر سيتم تنفيذ الوظيفة المحددة وكأنها برنامج آخر ولا ينتظر إنتهاؤها بل يتابع البرنامج والوظيفة العمل معاً. ويمكنك إطلاق threads أخرى

لتعديل أولوية وظيفة محددة نستعمل 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
}
إذا كان جهازك سريع فقد يتم إنهاء العشر دورات في الأولى inc قبل أن تبدأ الثانية dec وتنتهي الثانية dec قبل أن تبدأ الثالثة show. وبهذا لن تلاحظ أنهم يعملوا معاً. يمكنك أن تضع وظائف تأخذ وقت أطول مثلاً واحدة تكتب فيها
system("cat foobar | sed -e 's/#include<(.*)>/#include\"\\1\"/g'"); والآخرى مثلاً
system("diff /etc/mtab /proc/mounts");
وستحصل على ناتج غريب سطر من هذا البرنامج وآخر من ذاك (ليس التساوي بالضرورة) ولتوضح الفكرة سنجعل الوظيفة inc تنتظر الأخرى من خلال فحص قيمة total فإذا كانت موجبة فهذا يعني أن علينا انتظار انقاصها ب dec والأخرى تنتظر عندما تكون سالبة. وذلك بإزالة // من بداية سطر
while(total>0) g_thread_yield(); و
while(total<0) g_thread_yield();

يجب الانتباه عند كتابة برنامج Multi-threads لعدة أشياء منها

لتحقيق ذلك فإن معظم وظائف glib آمنة فهي تحجز ذاكرة خاصة بها بواسطة g_malloc وتبقي عليك مهمة تحريرها ب g_free في الوقت المناسب. كما أن مكتبة سي القياسية من جنو توفر وظائف إضافية -غير قياسية- لتكون آمنة في برامج 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
}

8.1.4 تطبيق عملي - طريقة عمل plugins

شرحنا سابقاً في فصل مشاريع متعددة الملفات كيف يمكنك من خلال 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 */
/* ... */
يجب عند تصميم مكتبة لتحمل بهذه الطريقة أن نسبق الوظائف ب G_MODULE_IMPORT أو G_MODULE_EXPORT الأولى لو كنا نصنف البرنامج الذي سيستعملها والثانية لو كنا نصنف المكتبة هذه ضرورية في ويندوز.


<< السابق كتاب لينكس الشامل التالي >>