C ++ Wrapper لمكتبة C.
-
01-10-2019 - |
سؤال
لقد وجدت مؤخرًا مكتبة C أريد استخدامها في مشروع C ++ الخاص بي. تم تكوين هذا الرمز مع المتغيرات العالمية ويكتب إخراجها إلى الذاكرة التي أشار إليها مؤشرات ثابتة. عندما أقوم بتنفيذ مشروعي ، أرغب في تشغيل مثيلتين من برنامج C: واحدة مع التكوين A وواحد مع التكوين B. لا يمكنني تشغيل برنامجي مرتين ، لذلك أعتقد أن هناك خياران:
- جعل C ++ Wrapper: المشكلة هنا هي أن فئة Wrapper يجب أن تحتوي على جميع المتغيرات العالمية/الثابتة التي لدى مكتبة C. نظرًا لأن الوظائف الموجودة في مكتبة C تستخدم تلك المتغيرات ، سأضطر إلى إنشاء قوائم وسيطة كبيرة جدًا لتلك الوظائف.
- نسخ ولصق مكتبة C: هنا سأضطر إلى تكييف اسم كل وظيفة وكل متغير داخل مكتبة C.
أي واحد هو الحل الأسرع؟ هل هناك إمكانيات أخرى لتشغيل حالتين من مصدر C نفسه؟
شكرًا،
الأعلى
المحلول
C ++ -wrapper
تفلت من خلال لصق "المكتبة بأكملها" - فقط معدل قليلاً - في فصل.
// C
static char resultBuffer[42];
void ToResult(int x) { ... }
char const * GetResult() { return resultBuffer; }
يصبح
// C++
class CMyImportantCLib
{
private:
char resultBuffer[42];
void ToResult(int x) { ... } // likely, no code changes at all
char const * GetResult() { return resultBuffer; }
} ;
هناك في الغالب تغييرات إعلانية (مثل "قتل" إعلانات ثابتة وخارجية). ستحتاج إلى البحث عن متغيرات ثابتة داخل الأساليب ، وتحويلها إلى أعضاء أيضًا
مساحات أسماء منفصلة
هذا حل قبيح ، لكنه قد يكون كافياً بالنسبة لك:
// impMyLib.h
namespace A
{
#include "c-lib.h"
}
namespace B
{
#include "c-lib.h"
}
// impMyLib.cpp
namespace A
{
#include "c-lib.c"
}
namespace B
{
#include "c-lib.c"
}
إذا كنت محظوظًا ، فإن المحسن/الرابط ينجح في طي الكود المتطابق. ومع ذلك ، الأنواع في A::
و B::
لا علاقة لها.
نصائح أخرى
إذا كنت لا تستطيع تشغيله مرتين ، فماذا عن 3 مرات؟ يمكنك كتابة عملية أمامية صغيرة تطلق حالتين منفصلتين من برنامج C. من منظور الاستخدام ، سيظل يبدو وكأنه واحد. exe تقوم بتشغيله مرة واحدة فقط ولكن وراء الكواليس ، سيكون لديك عملية الوالدين مع طفلان. ليس لدي أي فكرة عما إذا كان هذا النهج يناسب احتياجاتك الفعلية ، لكن من المؤكد أنه سيكون أسرع من أي من الخيارين الآخرين.
iiuc ، ما لديك ، في الأساس ، هذا:
extern int a;
extern int b;
void f();
void g();
أين a
و b
تعديل سلوك f()
و g()
. هل هذا صحيح؟
إذا كان لديك هذا وترغب في لفه في C ++ ، فإن ما يمكنك فعله هو:
class the_library {
public:
the_library(int a, int b) : a_(a), b_(b) {}
void f() {a=a_; b=b_; ::f();}
void g() {a=a_; b=b_; ::g();}
private:
int a_;
int b_;
};
اعتمادًا على ما لديك بدلاً من a
و b
, ، قد لا يكون هذا فعالا بشكل رهيب.
بالطبع ، كما قال راكي في التعليقات ، لأن هذا يستخدم المتغيرات العالمية ، فهو ليس آمنًا على الإطلاق.
أنا أحب الفكرة هنا. لكن يجب أن أجعل مؤشرًا لكل متغير أحتاج إلى تعديله. هذا مثال:
lib.h:
void f();
int g();
lib.c:
#include "lib.h"
extern int a;
extern int * output;
void f(){
*output=(*output+6)*a;
}
int g(){
return *output;
}
Object.cc:
#include "lib.h"
#include <iostream>
using namespace std;
int a;
int * output;
class the_library {
public:
the_library(int a, int * output) : a_(a), output_(output) {}
void f() {a=a_; output=output_; ::f();}
int g() {a=a_; output=output_; ::g();}
private:
int a_;
int * output_;
};
int main(){
int out1=2;
the_library icache(3,&out1);
icache.f();
cout<<"icache.f() -> icache is "<<icache.g()<<endl;
icache.f();
cout<<"icache.f() -> icache is "<<icache.g()<<endl;
int out2;
out2=8;
the_library dcache(7,&out2);
dcache.f();
cout<<"dcache.f()\t-> icache is "<<icache.g()<<endl;
cout<<"\t\t-> dcache is "<<dcache.g()<<endl;
return 0;
}
ربما هناك شيء ما تلاشى ولكن ...
... تتم مشاركة المتغيرات العالمية بين المواضيع ، وليس العمليات ...
هذا يعني أنه في حالتك ، يمكن أن يكون لديك عمليتان من نفس برنامج C الذي يعمل ، ولن يتدخلوا في واحدة مع الآخر إلا إذا عملت بطريقة أو بأخرى مع ذاكرة العملية.
... إذا كنت بحاجة إلى حالتين من رمز C الذي يعمل في نفس العملية ...
ثم أنت مشدود.
TLS ، ربما؟
إما أنه يمكنك تشغيلها في مؤشرات ترابط منفصلة وإعلان المتغيرات العالمية كمتغيرات تخزين مؤشرات الترابط المحلي. على سبيل المثال ، على Visual C ++ ، الكود التالي:
int myGlobalVariable = 42 ; // Global variable
__declspec(thread) int myTLSVariable = 42 ; // Thread local variable
سيكون لكل موضوع نسخته الخاصة من المتغير. وبهذه الطريقة ، في نهاية الموضوع ، يمكنك نسخ المحتوى في مكان آخر.
إعادة كتابة الرمز ...
لا تحتاج إلى إضافة طبقة C ++ لذلك. يمكنك الاحتفاظ برمز C الخاص بك ، وإعلان جميع متغيراتك العالمية في البنية:
/* C global variable */
int iMyGlobalVariable = 42 ;
const char * strMyGlobalString = NULL ;
short iMyShortData = 7 ;
/* C struct */
typedef struct MyStruct
{
int iMyGlobalVariable ;
const char * strMyGlobalString ;
short iMyShortData ;
}
MyStruct ;
ثم تقوم بتعديل النماذج الأولية للوظائف لقبول مؤشر على هذا الهيكل كمعلمة أولى ، ثم بدلاً من تعديل المتغير العالمي ، تقوم بتعديل عضو الهيكل:
/* old function */
int foo(char *p)
{
/* fudge with the global variables */
iMyShortData = 55 ;
/* etc. */
fooAgain("Hello World", 42) ;
}
الذي يصبح:
/* new function */
int foo(MyStruct * s, char *p)
{
/* fudge with the struct variables */
s->iMyShortData = 55 ;
/* etc. */
fooAgain(s, "Hello World", 42) ;
}
بعد ذلك ، بشكل رئيسي ، بدلاً من استدعاء الوظيفة الأولى ، يمكنك الاتصال بها من خلال إعطائها المؤشر إلى الهيكل الصحيح. بدلاً من :
int main(int argc, char * argv[])
{
bar(42, 55) ;
}
انت تكتب :
int main(int argc, char * argv[])
{
MyStruct A = { /* initialize A's members if needed */ } ;
MyStruct B = { /* initialize B's members if needed */ } ;
bar(&A, 42, 55) ;
bar(&B, 42, 55) ;
return 0 ;
}
في المثال أعلاه ، يُطلق على الاثنين واحدًا تلو الآخر ، ولكن يمكن لعلاج الخيوط الخاصة بك بدلاً من ذلك.
إنقاذ الدولة العالمية؟
إذا كان الكود الخاص بك متخلفًا واحدًا ، فيمكنك InterLeave استدعاء المقدمة الأولى والاتصال بالثاني من خلال توفير/إعادة ضبط الحالة العالمية. دعونا نستخدم نفس البنية أعلاه:
/* C global variable */
int iMyGlobalVariable = 42 ;
short iMyShortData = 7 ;
void saveState(MyStruct * s)
{
s->iMyGlobalVariable = iMyGlobalVariable ;
s->iMyShortData = iMyShortData ;
}
void resetState(const MyStruct * s)
{
iMyGlobalVariable = s->iMyGlobalVariable ;
iMyShortData = s->iMyShortData ;
}
وبعد ذلك ، يمكنك الاتصال بوظائف الحفظ وإعادة ضبطها عند الحاجة:
int main(int argc, char * argv[])
{
MyStruct A = { /* initialize A's members if needed */ } ;
MyStruct B = { /* initialize B's members if needed */ } ;
resetState(&A) ; /* now, we work on A */
bar(42, 55) ;
saveState(&A) ; /* we save the progress on A */
resetState(&B) ; /* now, we work on B */
bar(42, 55) ;
saveState(&B) ; /* we save the progress on B */
resetState(&A) ; /* now, we work on A */
foo("Hello World", 3.14159) ;
saveState(&A) ; /* we save the progress on A */
resetState(&B) ; /* now, we work on B */
foo("Hello World", 3.14159) ;
saveState(&B) ; /* we save the progress on B */
/* etc. */
return 0 ;
}
يمكن أن يتم لفها بواسطة رمز C ++ لالتفاف تلقائيًا وظائف إعادة الشتاء/Savestate. فمثلا :
struct MyWrapper
{
void foo(const char * p, double d)
{
resetState(&m_s) ;
foo(p, d) ;
saveState(&m_s) ;
}
void bar(int i, short i2)
{
resetState(&m_s) ;
bar(i, i2) ;
saveState(&m_s) ;
}
MyStruct m_s ;
} ;
الذي تمكنك من إعادة كتابة "كبار" على النحو التالي:
int main(int argc, char * argv[])
{
MyWrapper A ;
MyWrapper B ;
A.bar(42, 55) ;
B.bar(42, 55) ;
A.foo("Hello World", 3.14159) ;
B.foo("Hello World", 3.14159) ;
// etc.
return 0 ;
}
الذي يبدو وكأنه أفضل بكثير من الإصدار C. ومع ذلك ، فإن mywrapper ليس آمنًا للخيط ...
استنتاج
الحل الأول (TLS) هو الحل السريع ، في حين أن الثاني هو إعادة تشكيل الكود لكتابته بشكل صحيح (هناك أسباب وجيهة للغاية متغيرات عالمية تعبئ ، ويبدو أنك تعثرت على أحدها) ، و والثالث هو "اختراق" يتيح لك interleave المكالمتين.
من بين جميع الحلول الثلاثة ، سيجعل الثاني فقط من السهل لف هذا الرمز داخل فئات C ++ الآمنة في مؤشر الترابط إذا كان لا يزال هناك حاجة.