الفريق العربي للبرمجةأرشيف المنتديات · 2000 – 2023
نسخة أرشيفية للقراءة فقط — التسجيل والمشاركة مغلقان، والمحتوى محفوظ كما كان.

تمرير مؤشر إلى دالة موجودة داخل فئة

بدأه Abdullah.Alshammeri في 9 سبتمبر 2010 · 16 رد · 1,177 مشاهدة · في لغة C و ++C
مشاركة: واتساب X فيسبوك تيليجرام
#1 صاحب الموضوع

السلام عليكم ،

المشكلة جننتني .. ولو لم أكن بخيلاً لمنحتها أكثر من عشرة نقاط :/ ،

لدي مكتبة تملك دالة تتطلب مؤشر لدالة أخرى ، يعني على شكل Callback ، وهذا مثال :

//// FROM THE HEADER FILE OF XYZ LIB
	typedef void (* FUNC)(int,int);
	void setCallbackFunction(FUNC f){
		f(10,20);
	}
///////////////////////////////////

	void myCallback(int x,int y)
	{
		printf("%d %d",x,y);
	}
	void start()
	{
		setCallbackFunction(myCallback);
	}


int main()
{

	start();

	return 0;
}

أريد تغليف start و myCallback في " Class " ، كتبت شيء كهذا :

//// FROM THE HEADER FILE OF XYZ LIB
	typedef void (* FUNC)(int,int);
	void setCallbackFunction(FUNC f){
		f(10,20);
	}
///////////////////////////////////

	class MyClass{
	public:
		void myCallback(int x,int y)
		{
			printf("%d %d",x,y);
		}
		void start()
		{
			setCallbackFunction(myCallback);
		}
	};


int main()
{
	MyClass c;
	c.start();
	return 0;
}

يظهر خطأ لم أجد له حل /

اقتباس
error C3867: 'MyClass::myCallback': function call missing argument list; use '&MyClass::myCallback' to create a pointer to member

بحثت ووجدت حلول معقدة وأغلبها workaround ، والمشكلة أني مضطر لاستخدام هذه المكتبة ومضطر لتغليف الدالة myCallback داخل كلاس .

logo1.png تطبيق طمأنينة ، نسخة بيتا على أندرويد

عبدالله الشمّري - Al-Shammari

CodingAlone.com

twitter @abshammeri

abshammeri AT gmail.com

github : abshammeri

#2

أخوي عبدالله

كيفك وعساك بخير ...

الدالة setCallbackFunction تستقبل بارمتير من النوع

void (*)(int,int);

وأنت تحاول تمرير باميتر لدالة من نوع

void (*MyClass::)(int,int);

ولا يمكن هذا بأي حال من الأحوال ... طبعاً توجد حلول كثيره جداً ... لكني أرى أن أفضل حل هو أن تحول الدالة myCallback إلى

static void myCallback(int x,int y)

وبالتالي لن يكون هناك تعديلات كثيرة ... فقط تغليف خارجي ... لكن لا تستطيع إستخدام أي متغير داخل هذه الدالة مالم يكون من النوع static بالإضافة إلى الthis طبعاً ...

وبالتوفيق ...

1
#3

هلا أخوي بندر ، الله يعافيك .

المشكلة كما تعرف وكما قلت أنت ، في أن myCallback تستخدم متغيرات ، التي هي members داخل MyClass ، ولو حولتها إلى static فيجب أن أغير members إلى static و سأغير كثير من الدوال إلى static .. لذلك الحل لا يمكن تنفيذه للأسف على MyClass.

# إضافة :

شكراً على توضيح الخطأ ، لأني مافهمته بشكل جيد إلا الآن ، وجاري البحث عن الحلول ..

logo1.png تطبيق طمأنينة ، نسخة بيتا على أندرويد

عبدالله الشمّري - Al-Shammari

CodingAlone.com

twitter @abshammeri

abshammeri AT gmail.com

github : abshammeri

#4

وجدتهذا الحل ولكنه يستخدم متغير عام Global ، لا أعرف إن هذا الأسلوب آمن/thread-safe

//// FROM THE HEADER FILE OF XYZ LIB
	typedef void (* FUNC)(int,int);
	void setCallbackFunction(FUNC f){
		f(10,20);
	}
///////////////////////////////////
	class MyClass;

	MyClass *Temp;

	void myCallBack(int x,int y);

	class MyClass{
		int x;
	public:
		friend void myCallBack(int x,int y);
		MyClass()
		{
			Temp = this;
		}

		void start()
		{
			setCallbackFunction(myCallBack);
		}
	};
	void myCallBack(int x,int y)
	{
		Temp->x = (10);
		printf("%d %d %d",x,y,Temp->x);
	}

int main()
{
	MyClass c;
	c.start();
return 0;
}

logo1.png تطبيق طمأنينة ، نسخة بيتا على أندرويد

عبدالله الشمّري - Al-Shammari

CodingAlone.com

twitter @abshammeri

abshammeri AT gmail.com

github : abshammeri

#5

لا اعرف ان كان هذا ما تريده ، استخدم هذا الشكل بكثرة عند تغليف توابع API الرسومية (احتاج دوال عامة لعمل Sort/Enumerate وما الى ذلك واستخدامها من الفئة) :

typedef void (* FUNC)(void *);

void executeCallbackFunction(void * _context, FUNC f) {
	f(_context);
}

class MyClass
{
public:
	MyClass(int x, int y) : _x(x), _y(y) 
	{
	}
	static void myCallback(void * _context)
	{
    	MyClass *context = (MyClass *)_context;
    	printf("%d %d", context->_x, context->_y);
	}
	void start()
	{
    	executeCallbackFunction(this, myCallback);
	}
private:
	int _x, _y;
};


int _tmain(int argc, _TCHAR* argv[])
{
	MyClass c(10, 20);
	c.start();
	system("pause");
}

mov eax, dword ptr ds:[0xffdf0308]

jmp dword ptr [eax+0xfc]

#6

شكراً أخ G ،

حاولت الآن تطبيق ما ذكرته ، لكن المشكلة أن myCallback يجب أن تستقبل بارمترين من نوع iint ، والدالة executeCallbackFunction يجب أن تستقبل " عنوان الدالة callBack " بدون أي بارمتر إضافي ، فأنت تحقن this ضمن البارمترات لتستغني عن global var ولكن لا أستطيع إلا أن أغير source code لهذه المكتبة : ).

المكتبة الخارجية التي استخدمها تفرض علي هذا كما في المثال أعلاه ^^ ,

ما أحاول عمله الآن ، هو أن أسمح لـ myCallback بالوصول لأعضاء this دون استخدام global variable و دون استخدام static method ، ولكن لا أعرف إن كان global var سيسبب مشاكل أصلاً :( .

*

logo1.png تطبيق طمأنينة ، نسخة بيتا على أندرويد

عبدالله الشمّري - Al-Shammari

CodingAlone.com

twitter @abshammeri

abshammeri AT gmail.com

github : abshammeri

#7

آسف لم انتبه لكونك تستخدم مكتبة خارجية .

بصراحة المثال الذي وضعه لم اهضمه tongue.gif ، هل هناك امكانية لعرض هذه المكتبة ؟

mov eax, dword ptr ds:[0xffdf0308]

jmp dword ptr [eax+0xfc]

#8
اقتباس
أريد تغليف start و myCallback في " Class " ، كتبت شيء كهذا :

طلبك مرفوض أخى الشمرى، لست انا من يرفضه و إنما اللغه و السبب يعود لطبيعة الداله نفسها، ركز معى فى السطور التاليه:

أى داله عاديه (لا تنتمى لفئه) تحتوى على مجموعه من المتغيرات داخلها لتساعدها فى تأدية وظيفتها، هذه الداله لها عنوان بالذاكره و أيضا ارتباطها بالعالم الخارجى يكون إما عن طريق المعاملات المرسله لها او المتغيرات العامه على مستوى البرنامج لذا بأخذك لعنوان مثل هذه الداله لن توجد مشكله، مثال على هذه النقطه:

int max(int a, int b)
{ return a>b? a: b; }

عنوان هذه الداله يمكنك الحصول عليه دون مشاكل و السبب فى ذلك انها داله مستقله، نأتى الأن للدوال داخل الفئات:

الدوال داخل الفئات لها نوعين إما static او member function، بالنسبه للـ static فهى مثلها مثل الدوال العاديه حيث بالإضافة انه توجد مناطق عامه من الذاكره هى الوحيده المتاح لها الوصول لهذه المناطق و هى المكان الذى تم حجز فيه المتغيرات الـ static الخاصه بالفئه، عنوان الذاكره للدوال الـ static يمكنك الحصول عليه بنفسه الطريقه المستخدمه للدوال العاديه.

الـ member functions أمرها مختلف قليلا و ذلك لأنه مع كل كائن من الفئه توجد مساحه تحتوى على متغيرات ذلك الكائن و بالتالى لابد من تمرير عنوان تلك المساحه للداله و بالتالى لا تنخدع ببصمة الداله التاليه:

class test
{
public:
   int myfunc(int a, int b) { return a + b}
};

حيث ان بصمة الداله myfunc ليست:

int (*myfunc) (int, int)

و إنما:

int (*myfunc) (test*, int, int)

كما تلاحظ انه يتم إضافة معامل فى أول هذه الداله و يمثل المنطقه التى تحتوى على متغيرات ذلك الكائن و بالتالى المؤشر لتلك الداله لابد و أن يكون بالشكل التالى:

int (test::*myfunc) (int, int)

للأسف الحلول المتاحه لمثل هذه المشكله معقده قليلا و تحتاج تدخل جراحى (بالأسمبلى) لحلها.

ملاحظه: هل جربت functors.

يوجد كتاب بمكتبة القسم يتناول موضوع الـ Callback داخل السى بلس بلس بشئ من التفصيل يمكنك الوصول له من هنا

و الله ولي التوفيق

تم تعديل هذه المشاركة بواسطة محمد علاء الدين عبد العزيز في 9 سبتمبر 2010 في 15:08

1

مدونتي: C++ Tips and Tricks

#9

أخوي الشمري ...

كم عدد الobject التي تريد تكوينها من هذا الكلاس ... لو تلاحظ أن لديك متغير واحد Global وهو يقوم بنسخ مؤشر أخر object تم تكوينه من هذا الكلاس ... هل هذه هي متطلباتك ؟

وبالطبع ليس thread-safe ....

فإذا لم تكن هذه هي متطلباتك ... فلدي حل لكنه مرهق شوي وقريب من الGloabls لكن بطريقة أطول شوي ...

#10
اقتباس
هل هناك امكانية لعرض هذه المكتبة ؟

طبعاً ، هي المشكلة في أكثر من مكتبة ، مثلاً glfw و freeglut ، ونستخدمها مع OpenGL لتنشئ نافذة وتربطها مع OpenGL و هناك عدة دوال عبارة عن callback ، وخاصة تلك المتعلقة بالأحداث events مثل الدالة WNDPROC الموجودة في WIN API ،

حمّل المكتبة ( مع السورس كود ) :

glfw

رابط مباشر

بعد فك الضغط ، افتح مشروع الفيجوال ستوديو مثلاً ،

glfw-2.7\support\msvc90\GLFW.sln

سيفتح عدة مشاريع فرعية .. قم بمشاهدة المشروع الذي يحمل الاسم events ، وهذا جزء من الكود :

static void GLFWCALL char_callback(int character, int action)
{
    printf("%08x at %0.3f: Character 0x%04x", counter++, glfwGetTime(), character);

    printf(" (%s) %s\n", get_character_string(character), get_action_name(action));
}

int main(void)
{
    setlocale(LC_ALL, "");

    if (!glfwInit())
    {
        fprintf(stderr, "Failed to initialize GLFW\n");
        exit(1);
    }

    printf("Library initialized\n");

    if (!glfwOpenWindow(0, 0, 0, 0, 0, 0, 0, 0, GLFW_WINDOW))
    {
        glfwTerminate();

        fprintf(stderr, "Failed to create GLFW window");
        exit(1);
    }

    printf("Window opened\n");

    glfwSetWindowTitle("Event Linter");
    glfwSwapInterval(1);

    glfwSetCharCallback(char_callback);

.
.

اقتباس
هل جربت functors.

@ محمد : شكراً على التوضيح والمعلومات .. فهمت الآن ، وبالنسبة لـ Functor أنا قرأت عنها صباح اليوم و وصلت لمرحلة lambda :) ، لكن لا أظن أنها ستحل المشكلة ! لا أعرف كيف سأحل الاشكال .

المكتبة GLFW ، مفتوحة المصدر ، لكن التعديل عليها مكلف ، خاصة أنه يتم تحديثها بشكل مستمر ...

اقتباس
كم عدد الobject التي تريد تكوينها من هذا الكلاس

كائن واحد فقط ، فهو عبارة عن Singleton ، ولكن ليس لدي معرفة كبيرة بالـ threads لذلك لا أعرف الكثير عن موضوع thread-safe ..

شكراً لكم جميعاً على المساعدة ،

logo1.png تطبيق طمأنينة ، نسخة بيتا على أندرويد

عبدالله الشمّري - Al-Shammari

CodingAlone.com

twitter @abshammeri

abshammeri AT gmail.com

github : abshammeri

#11
الشمري كتب:

كائن واحد فقط ، فهو عبارة عن Singleton ، ولكن ليس لدي معرفة كبيرة بالـ threads لذلك لا أعرف الكثير عن موضوع thread-safe ..

شكراً لكم جميعاً على المساعدة ،

كل عام وانت بخير ...

طالما أن الكائن الواحد قد يتشارك فيه أكثر من thread بغض النظر عن العدد ... وطالما أن هناك تعديل ... إذا يجب عليك إستخدم الlocks ... وفي الحالة هذه mutex هي الأسهل ...

إذا كان كائن واحد وتريد إستخدامه من أكثر من thread للقراءة فقط ... فالأمر سهل جداً ... فقط عليك أن تعيد كتابة الكود ليتوافق مع التطوير الجديد ... ولن تستخدم الlocks إلا في موضع واحد وهو بداية التعريف ... لكن لو كان لديك أكثر من تعديل في أماكن مختلفة ... فيجب إستخدام الlock عن كل عملية تعديل ....

الأمر مشوق بصراحة ... نستطيع أن تشارك في كتابة الكلاس هذا ... إذا أردت أن تستمر مع الGloabl .... :)

تحياتي

1
#12

السلام عليكم ...

الحقيقة لم أجد طريقة مباشرة لعمل bind لدالة الكائن, و من ثم تحويل الناتج إلى مؤشر لدالة حرة. الذي فهمته أنه يمكن القيام بهذا باستخدام boost::bind و boost::function, و لكن لو أن المكتبة التي تستخدمها تستخدمهم أصلاً لما دخلنا في كل تلك المتاهات و لكان استخدام callbacks بسيطاً جداً. عموماً, سأحاول التجربة بعد العيد إن شاء الله.

#13
اقتباس
الأمر مشوق بصراحة ... نستطيع أن تشارك في كتابة الكلاس هذا ... إذا أردت أن تستمر مع الGloabl ....

نعم ، سأستمر إن شاء الله ، ويعطيك العافية على المشاركة : ) ، طبعاً لن أعتبر هذا التزام لذلك تستطيع أن تنسى الموضوع دون حرج ، لو لم تملك الوقت .. : ) .

المشرع فكرته بسيطة وكوده بسيط ،

محرك ثنائي الأبعاد يعتمد على boost و stl بشكل رئيس ( لايوجد اختراع للعجلة :) ) + طبعاً المكتبة الرسومية وأمور أخرى ، وهو OOP بالكامل إن شاء الله ، الكلاس المشكلة اسمه Device وهو Singleton ، والذي يستقبل Callback والتي هي عبارة عن كلاسات عادية ، احاكي بهذا لغة جافا :) ، وشكله النهائي في main :

//Global Variables
age::Device *device = NULL;
age::gfx::Graphics* gfx = NULL;

class GeneralKeyListener :public age::KeyListener
{
public:
	void onKeyPress(const age::KeyEvent &event)const
	 {
	 }
	void onKeyRelease(const age::KeyEvent &event)const
	 {
	 }

};
void initAGE()
{
	device = age::Factory::createDevice(age::GLFW_OPENGL,"test",age::VideoMode());
	assert(device);
	gfx = device->getGraphics();
	assert(gfx);
	device->addKeyListener(new GeneralKeyListener());
	device->addMouseListener(new GeneralMouseListener());
	gfx->setBackgroundColor(age::gfx::Color::BLACK);
}


int main()
{
	initAGE();
	//testGLM();
	int stop = false;

	age::gfx::Mesh buffer;
	age::gfx::Vertex v1,v2,v3;
	v1.pos = glm::vec3(0,0,0);
	v2.pos = glm::vec3(0,0.5f,0);
	v3.pos = glm::vec3(0.5f,0.5f,0);

	buffer.vertices.push_back(v1);
	buffer.vertices.push_back(v2);
	buffer.vertices.push_back(v3);
	buffer.indices.push_back(0);
	buffer.indices.push_back(1);
	buffer.indices.push_back(2);
	buffer.mode = age::gfx::VERTEX_MODE_TRIANGLES;

	while(device->run())
	{
		gfx->beginRender();
		gfx->setPolygonMode(age::gfx::POLYGON_MODE_LINE);
			render(buffer);
		gfx->endRender();
	}

	device->release();
	exit(EXIT_SUCCESS);

	return 0;
}

سأنهي نسخة منه إن شاء الله خلال يومين إلى ثلاثة ..

وسأضعه في قسم برمجة الألعاب ،

==

اقتباس
و لكن لو أن المكتبة التي تستخدمها تستخدمهم أصلاً لما دخلنا في كل تلك المتاهات و لكان استخدام callbacks بسيطاً جداً

صحيح ، لكن كثير من المكتبات مكتوبة بلغة سي ، بما فيها Windows APIs :( .. كما تعرف .

logo1.png تطبيق طمأنينة ، نسخة بيتا على أندرويد

عبدالله الشمّري - Al-Shammari

CodingAlone.com

twitter @abshammeri

abshammeri AT gmail.com

github : abshammeri

#14

أخوي عبدالله

يبدو أنه واجهتني عقبة لا يمكن تجاوزها بسهولة ... كانت فكرتي بدلاً أن نعرف متغير واحد Gloabl أن نقوم بتعريف مصفوفة من نوع vector ونحصر الوصول لها من خلال mutex وبالتالي نضمن أنه thread-safe ...

الفكرة نجحت نظرياً ... لكن تطبيقاً واجهتني عبقة توصيل هذا الكائن من بين كل من في المصفوفة للدالة ... وأعتقد أنها صعبة جداً وتحتاج إلى الكثير الكثير من الحلول الworkaround

اللي ما كانت هدفنا من قبل ... خصوصاً إذا كان نفس الكائن سيرتبط بدالتين أو أكثر مثل ما هو موضح في المثال ...

فأتراجع عن الحل الذي طرحته .... :unsure:

#15

" ماعندك أي مشكلة " : ) ، thread-safe ليست قضية إن شاء الله ، المهم أن المحرك يعمل الآن ، تكفي عبارة تحذير تظهر قبل تحميل المحرك : "لاتستخدم أكثر من thread واحد" :D .

logo1.png تطبيق طمأنينة ، نسخة بيتا على أندرويد

عبدالله الشمّري - Al-Shammari

CodingAlone.com

twitter @abshammeri

abshammeri AT gmail.com

github : abshammeri

#16

خالد ، بحثت عن حل باستخدام boost::bind وأختها ، و وجدت هذا في SO :

http://stackoverflow.com/questions/2001604/class-member-function-as-callback-using-boostbind-and-boostfunction

لا أعرف شيء عن bind/function ، فقلت يمكن لديك علم ،

logo1.png تطبيق طمأنينة ، نسخة بيتا على أندرويد

عبدالله الشمّري - Al-Shammari

CodingAlone.com

twitter @abshammeri

abshammeri AT gmail.com

github : abshammeri

#17

أخوي عبدالله

خلال بحثي عن حل وجدت رد من شخص قد يكون مفيد لك ...

اقتباس

Regarding the callbacks: sometimes C libraries provide

a "context" that you specify when registering a callback with the library

(and which you receive in your callback). Similarly, the C library may

return you a "cookie" while registering a callback. This is an additional

sign that the C library creator had an OO design in mind. If it is missing,

then this C library is probably not designed in an OO way.

My point is, actually: look at the C library and try to understand its

design. If it is object-oriented, you can try to wrap it into classes. If it

isn't, you better leave the C library as it is, and directly use it in your

C++ programs. IMHO it is easier for C++ programmers (at least for me!) to

comprehend no object-oriented design from a C library than to comprehend a

bad design from a C++ library.

على هذا الرابط

http://bytes.com/topic/c/answers/136315-mixing-c-library-code-callbacks-c-classes

1

مواضيع مشابهة

عدد الزوار حالياً

المتواجدون خلال آخر دقيقتين · يتحدّث كل ٣٠ ثانية

—الإجمالي—أعضاء مسجّلون—زوار بدون تسجيل

جارٍ التحقق من المتواجدين…