Inhalt
Topic:.ObjO_emC.
back ==>../../emc/index.html
Legacy documentation in german ==>../../Jc/index.html
The style of Object Oriented Programming (OOP) is known since the 1980-years and usual used with C++, Java, C# etc. since the 1990-years. C was created in the years
before, with structured programming (if, while, for) but with the possibility of data structures with typedef struct
. C is not considered to be Object Orientierted? The OOP is not a property of a language but a style of programming. The language
should deliver enough syntactical possibilities to use ObjectOrientation. For this cases C is proper. It delivers:
Data structuring.
Link functions to data with a Data pointer.
Overridden operations via function pointer.
The goal is: Using C (for several reason) but using C in an ObjectOriented way. The goal is not: Replace C++ by flawy C constructs.
What are the reasons to use C (instead C++):
Some older compiler for special target processors do not support C++ language, or have less problems, which forces a decision: Use C.
C is near machine code. Using C++ some features are offered to use, but they produce a non proper machine code. For example
C is more strong for const
definition to define in an const memory segment after linkage. C++ allows to define calulated const data on startup time,
which cannot be placed in a Flash memory area. It depends on the tool chain whether really const memory segments are supported
or not.
The virtual table is refered inside the user's data. On a data damage the virtual pointer can be disturbed and a high sophisticated failure behavior can be effectuate. A better more safe implementation of the overridden operation concept may be proper.
The container classes in C++ uses dynamic memory in a elaborately kind. It is not proper for embedded software.
Topic:.ObjO_emC..
Define data in typedef struct ... MyData;
. That are the class definition data repectively the instance data to work.
Name functions (= operations, in OO slang often named methods) with the data type name in suffix.
Use myOperation_MyData(MyData* thiz,...)
the first argument named thiz
to let work the operation with its data. Don't name it this
because we may use a C++ compiler too.
For inheritance: Use the base data as first struct
in the derived struct
definition. This supports only single, not mulitple inheritance. The programming language Java does also only support single
inheritance. Multiple inheritance may not a recommendable feature.
For abstraction: A common base class should be defined. It is ObjectJc
in the emC concept. The ObjectJc
base structure can only be consist of one pointer, suitable for a simple embedded System. Elsewhere the ObjectJc
can have common data, especially a reference to reflection information. If you need an abstract pointer with data, define a base class based on ObjectJc
. If you need only an interface pointer, typedef
a struct
which contains only ObjectJc
to get a special type.
For overridden methods of an inherited class with dynamic call a safe algorithm is offered, more safe as the virtual table in C++. It works with a table of operation pointer (adequate to the virtual table in C++) but with a well checked reference
from the ObjectJc
base class. See TODO.
Topic:.ObjO_emC.cpp.
If you want to use C++ for your projects, then it is a proper decision. If you have some sources, proper to use, which are written in C, it can be integrated. No problem. If you have some sources, which should be used in a C environment, you can integrate it.
A bad decision would be: build a world in C++, and another world in C. Write sources necessarily in C++ though they are existing in C.
C is a subset of C++, they are less exemptions. Prevent using the exemptions. It means, you can compile any C-source with a C++ compiler as C++-source.
On Visual studio you can use the /TP
option to force C++ compilation for C sources. If you use a C++ compiler on PC platform, you get more error informations,
the code will be checked more on compilation time. You can use a C-compiler on the target platform nevertheless.
It may be an idea to use
extern "C"
to define operations a C-linklabel. The difference is: The C-linklabel is only the _name
of the operation with an underscore before. C cannot differ operations with the same name and different arguments. The C++
label of an operation is longer, complex, contains the types of the arguments. If you have differences in arguments (for example
const*
and non const pointer) between the prototype in the header and the implementation, C++ builds different operation labels.
Then a linker error occure ('cound not find ...'). The C++ compiler detects this drawback. You should be happy on any compiler
or linker error, because it is a early error.
It means, you should not use the extern "C"
possibility if it is not necessary. If you have pre-compiled sources with C (in an library), the extern "C"
is necessary. But you may compile the libraries as C++ newly.
Topic:.ObjO_emC.cpp..
The topic CLASS_C
is used in documentation in headerfiles to express the relationship between struct
definition and operations.
You can define
typedef struct MyClass_t { int element; } MyClass_s; int myOperation_MyClass(MyClass_s* thiz, int args); #ifdef __cplusplus class MyClass : private MyClass_s { public: myOperation(int args) { myOperation_MyClass(this); } }; #endif //__cplusplus
With this you have defined in the headerfile both, the C interface to MyClass
and the C++ approach. A using application can access only the C++ form. Advantage: private encapsulation of all data. Advantage
but disadvantage too: virtual operations (simple but unsafe in C++). Another application can use the C form. The implementation
is in C. Note: the struct
definition identifier ends with _s
, but all suffixes has not this _s
.
Topic:.ObjO_emC.cpp..
The class ObjectBaseJcpp was used in the 2006 till 10, the better concept is ObjectifcBaseJcpp:
For a C++ class based on the ObjectJc concept you should write:
/**The pure C definition, able to use for only-C-applications: */ typedef struct MyType_t { union { MySuperType_s super; ObjectJc object; MyInterface MyInterface; } base; int myData; } MyStruct_s; /**Operation in pure-C-Style. */ extern_C int myOperation_MyType(MyType_s thiz, int args); class MyType: public MyType_s, ObjectifcBaseJcpp { public: int myOperation(int args) { return myOperation_MyType(this, args); } public: ObjectJc* toObjectJc(){ return &this->base.object; } }
The definition of the whole functionality is done in C, implemented with the near-machine-level comprehensibility of C und use-able for pure C.
A C++ MyType
is defined for usage in C++ applications. The C++ class MyType
has not any own functinality but is only a wrapper. It inherits all data from the C struct MyType_s
. All C routines are defined for C++ access, which calls only the C form.
To access the ObjectJc*
reference, the toObjectJc()
routine is defined in ObjectifcBaseJcpp
and should implemented in the C++ class. On the other hand with this rule of implementation the ObjectJc
can be accessed anytime with reference->base.object
but the toObjectJc()
decouples the definition of the C struct
Type and the C++ usage.
With this rule of design the following usage schema is possible:
application in C++----! | enhancement of core software only in C++ application in C ! ! | C++ wrapper of core software ! ! core software in C
Topic:.ObjO_emC.super.
The ObjectOriented approach in C should support abstraction and inheritance. Java supports only single inheritance. That is a proven concept. C++ supports multiple inheritance as a super feature from beginning, but that is partial controversial. The C ObjectOrientation is restricted to single inheritance.
The data are arranged as first struct of super data in a struct. A union helps to access ObjectJc immediately (if used, not absolutely necessary, but recommended) and helps to define interfaces:
typedef MyDerivedClass_t { union{ MySuperClass super; ObjectJc object; ObjectJc MyInterface1; ObjectJc MyInterface2; }; //unnamed int elements; } MyDerivedClass_s;
Now you can access the super data from a derived reference using
MyDerivedClass_s* ptr; operation_MySuperClass(&ptr->super);
without casting with type check in compile time. In difference to C++ you should write more source code, the principle is the same. Advantage: You see what you have. C++ does the casting automatically:
operation(ptr); //C++: operation from super class is invoked, you do not see it.
If you refer the super type, you can initalize it with:
MyDerivedClass instance = {0}; //may be static instantiated. ..... MySuperClass* ref = &instance.super;
If an algorithm knows that the instance is type of MyDerivedClass_s
, you can write a simple unchecked cast. Either the algorithm knows it from other data, or it should check it, see next chapter.
struct MyDerivedClass_t* refdata = (struct MyDerivedClass_t*) ref;
Because the super struct start on beginning of the derived struct, it have the same address. In this example the forward declaration
is used, which does not need to know the definition of struct {...} MyDerivedClas_s
. It reduces dependencies in headers. For C++ this approach is not possible. The C++ cast should know both instances. It adjusts
the value of the address, refdata
and ref
have different addresses if the classes have virtual methods or it is mulitple inheritance. C++ adjust it correctly, but
not clearly visible. That is one of the differences between near machine code programming in C (you see what the machine code
will do) and the more high level slightly obscure C++. In C++ a cast
class MyDerivedData* refdata = (class MyDerivedData*) ref;
may adjust the address value depending on the situation of virtual operations. The vtbl
pointer is part of the classes. This is visible in debug, but not in view of the source code. There are some tripping points
in C++ for errors (static_cast
with void*
, wrongly used reinterpret_cast
etc.
Topic:.ObjO_emC.ObjectJc.
All more complex data types have common properties for usage:
virtual operations
reflection
typecheck on runtime
mutex
Therefore Java has defined java.lang.Object
as the super class of all. Any instance is an Object
. Any instance have reflection information, a type check can be done etc. That is a important advantage for safety code.
The disadvantage of a out of the question super class is: It needs memory. In Java primitive types are not classes. An array
of 1000 integer values need 4000 Byte (int32_t
) as in C. But a class with 2 elements, for example real and imagine of a complex number is based on Object already. To safe
storage it is possible to use simple float[]
or double[]
arrays, the even index for real, the odd one for complex, as workarround.
Therefore, in C ObjectOriented with emC style, the super class ObjectJc
is not necessary at all. It is only necessary for the following cases:
It contains the reflection reference. If reflection are need and elsewhere unknown, ObjectJc
is necessary. But for the inspector service using reflection, the reflection can be gotten from the typeinformation in the
reflection itself. It is not necessary to read the reflection from the instances.
If the type is unknown because derived classes can be used, and the derived type or the base type should be checked, it is
done via the reflection information. Therefore ObjectJc
as super class is necessary. Especially the Java-like check myObject instanceof Type
can be done with the operation instanceof_ObjectJc(&ref->object, &reflection_Type)
. It checks super and interface types too.
If virtual operations in C should be used, it works with ObjectJc
. See TODO.
ObjectJc
in its full capability supports access to mutex and synchronizing Objects from OSAL, as java.lang.Object
. The advantage is: The mutex and synchronization is associated to the data. It is recommended to use it: synchronized_ObjectJc(&myData->object)
.
It means: Considerable types should based on ObjectJc. Simple struct
types should not based on it.
But additional, there are two variants of the base class ObjectJc
:
The header file emc/source/emC/Object_emC.h
defines the type ObjectJc
with all that capabilities.
For a simple system without String processing, with low memory, the emc/sourceApplSpecific/applConv/ObjectJc_simple.h
is included in the central applstdef_emC.h
. That file defines a ObjectJc
only consist of a simple int32_t
variable. This variable holds an index to the Reflection offset table (16 bit) and an identifier for the instance to usefull
for data debugging, no more. It works with the InspcTargetProxy concept.
Topic:.ObjO_emC.ObjectJc.src.
Last changed: 2018-12-27
If a struct
has no other super struct than ObjectJc, you should define it as first element. Use a union though it seems as unnecessary,
but for unique style and access:
typedef MyBaseStruct_t { union { ObjectJc object; } base; int someMoreElements; } MyBaseStruct_s;
If a struct
is based on another one which is guaranteed based on ObjectJc
maybe in a further super struct, you can write it as union:
typedef MyDerivedStruct_t { union { MyBaseStruct super; ObjectJc object;} base; int someMoreElements; } MyDerivedStruct_s;
You should use that identifier. It is unique and it is used for reflection generation. The super class should be the first element. It is necessary for initializer with {...}
(INIZ_...
macro). The super class should be named super
. The union itself is named base
. The union could have beeen unnamed, it would save writing time. Unnamed unions are present by K&R since 1974 already and
it is standard for GNU. But the C89 standard committee has overslept this feature, C99 too. Only C11 has standardized the
unnamed union. But C11 is too young (only 7 years from 2018). Unfortunately a common style cannot use non-standard extensions.
Therefore you should write base
:
ObjectJc* objAccess = &myRef->base.object; MyBaseStruct_s* superAccess = &myRef->base.super;
If you have interfaces in C, see TODO. The interfaces routines have its thiz
data pointer in form of ObjectJc*
because the implementing class should based on ObjectJc
in any case. It is the 'common denominator' (C uses often void*
for adquate things).
Therefore interfaces should be defined in the union too as further elements:
typedef MyDerivedStruct_t { union { MyBaseStruct super; ObjectJc object; ObjectJc Interface_A; ObjectJc Ifc_XYZ; } base; int someMoreElements; } MyDerivedStruct_s;
The reflection generator can detect this writing style for interfaces and can build the virtual table inside the reflection with this information.
Topic:.ObjO_emC.ObjectJc.initObj.
Last changed: 2018-12-27
The following block in a source.c, here emc/source/ Ctrl/pid_Ctrl.c
includes the generated reflection file, see Reflection:Topic:.ReflectionJc.genReflection..
#ifdef __USE_REFLECTION__ #include <Ctrl/pid_Ctrl.crefl> #endif
The compiler switch should be set in the applstdef_emC.h
which may be project-specific but application-type-specific anyway. It the switch is set, a ...crefl
file should be present - that's the rule. Note: If the compiler is called with option define __DONOTUSE_REFLECTION__
the applstdef_emC.h
should not define __USE_REFLECTION__
by compiler switch test. This is a simple way to exclude reflection usage in an experimental phase.
Prevent non necessary include: This file should only be included here if the reflection data are need in this source immediately. Elsewhere it is possible
that the reflection for this souce are not necessary and they are not generated, but nevertheless and unfortunately the reflection
file should be present to include here. That's a wrong situation. The reflection data for the data struct
definded in the own header may be necessary for an aggregation from another module which uses reflection and which refers
this struct
. In this case the accompanying ...refl
file should not be included in the using source but it should be included in the main context of the application. Why? It
is possible that another source needs this ...crefl
as aggregation too. If both sources includes it it is compiled twice and a linker error results. Typical examples for this
situation are struct
definition non based on ObjectJc
without need of reflection intialization in the own context, but there are referenced with reflection. The majority of complex
sources should need and include the ...refl
itself, the effort to include it in the main context is only necessary for the special cases.
The path from where the ...crefl
file is read should be determine in the include path settings of the compiler. Here an alternate is possible:
Using full immediately reflection support: In this case the ...crefl
files contain the full const
definition of the reflection data (snippet):
const ClassJc reflection_Par_PID_Ctrl = { INIZ_objReflId_ObjectJc(reflection_Par_PID_Ctrl, &reflection_ClassJc, INIZ_ID_ClassJc) , "Par_PID_Ctrl" , 0 , sizeof(Par_PID_Ctrl_s) , (FieldJcArray const*)&reflection_Fields_Par_PID_Ctrl_s //attributes and associations
Using the target proxy concept. In this case the ...crefl
files contain a sparse definition of reflection for the target (snippet):
int32 const reflectionOffset_Par_PID_Ctrl[] = { 3 //index of class in Offset data , (int)( ((intptr_t)(&((Par_PID_Ctrl_s*)(0x1000))->kP)) -0x1000 )) }; extern_C ClassJc const reflection_Par_PID_Ctrl; //forward declaration because extern "C" ClassJc const reflection_Par_PID_Ctrl = { 3 //index of class in Offset data //sizeof(reflectionOffset_Par_PID_Ctrl) , &reflectionOffset_Par_PID_Ctrl[0] };
Both files can be existing side by side, in different directories. The source need not be adapted for both case, it can be compiled both with target-proxy (for example for a spare target processor) or with full reflection support (test on PC, re-usage of the same file for another application).
The next block in the source.c is the constructor for an ObjectJc
-based instance:
Par_PID_Ctrl_s* ctor_Par_PID_Ctrl(ObjectJc* othiz, float Tstep) { Par_PID_Ctrl_s* thiz = (Par_PID_Ctrl_s*)othiz; initReflection_ObjectJc(othiz, othiz, sizeof(Par_PID_Ctrl_s), &reflection_Par_PID_Ctrl, -1);
That is the start of the constructor routine. It gets the instance as ObjectJc
pointer. The instance should be allocated in heap or defined statically. The initReflection_ObjectJc(...)
expects either a 0-filled area or a pre-initialization with common ObjectJc head information, especially containing the memory
size. This is the more safe approach. Then this routine checks whether the given sizeof(..)
is equal (or lesser). This routine initializes The data of the ObjectJc
struct.
The reflection data are used here. The label, in this case reflection_Par_PID_Ctrl
is defined in the included ...crefl
file. Depending on the variants, full reflection or spare target, the initReflection..()
routine has two (or more) different implementations. The Type ObjectJc
is defined in two different ways, a full variant in emc/source/emC/Object_emC.h
and a spare variant in emc/sourceSpecial/ applConv/ObjectJc_simple.h
, with the proper init routine.
If the compiler-switch __USE_REFLECTION__
is not set, the init-Routine is defined as macro which ignores the reflection argument. Therefore no compiler error occurs
though the reflection label is not defined.
To allocate, there are three variants:
ObjectJc* myInstance = alloc_ObjectJc(size, typeident, _thCxt);
This routine is defined in Object_emC.h
. It allocates and sets especially the size information to check it on initReflection_ObjectJc(...)
. The kind of allocation (using malloc, OS_alloc or other) can be different for several implementation situations.
ObjectJc* myInstance = (ObjectJc*) malloc(size); memset(myInstance, 0, size);
This is not recommended but possible. The 0-initialization is necessary!
The static definition is possible too:
Par_PID_Ctrl_s myInstance = {0}; struct AllData { Par_PID_Ctrl_s myInstance; .... } data = {0};
The last form defines all data in one block. The {0}
initialization is simple and necessary.
Topic:.ObjO_emC..
The technique of dynamic linked (virtual) operations and interfaces is important for ObjectOrientation.
C++ is unsafe, because the pointer to the virtual table is part of the data, and the access is rightly fast but unchecked. If the data have a problem, disturbed from another error, then the processor runs undefined code.
The emC approach offers a safe form with a 3-level check. Of course the call of a virtual operation is a little bit slower, but it is safe.
Firstly either an interface should be defined which does not contain data (like in Java):
typedef struct { ObjectJc object; } MyInterface_s;
It is only formally. But it contains the ObjectJc
super struct.
or a super struct is used. Both are used as typedef struct MyType_t { union { MySuperType super; ObjectJc object; MyInterface_s MyInterface} base; int mydata ... } MyType_s; Because all super and interface things are a The implementation of a operation (method) for a super struct or interface should be written in the form: int myOperation_MyStruct_F__(MyInterface_s* ithiz, int args) { MyStruct_s thiz = (MyStruct_s*)ithiz; //upcast ithiz to the neccesary type return thiz->data * args; } The This routine is necessary unfortunately to declare in the headerfile. But in the headerfile the user-prepared routine is defined too: inline int myOperation_MyStruct(MyStruct_s* thiz, int args) { return myOperation_MyStruct_F__(&thiz->base.MyInterface, args); } It is a wrapper. To compiler dissolve it in machine code, only the original call of But that is not dynamical, it is the invocation of the final implementation routine yet now. How to get a dynamically call: Each instance with dynamic/virtual operations should base on The C++ compiler builds the virtual table by itself, without effort in source code, exactly (or unexpected on source or handling
errors). For example if a C++ method is declared as The virtual table in C should be manually written. It is an additional effort, unfortunately. But if a new method is inserted, it is an explicit action. It can be more aware. Another advantage: The association between the invoked virtual operation and its implementation is done in the same, changed, compilation unit. That is the important one for safety. /**Struct definition of the virtual table contains all dynamic linked (virtual) operations * of this interfaces. * This is the de facto interface definition. */ extern const char sign_Vtbl_IfcTest[]; //marker for methodTable check typedef struct Vtbl_IfcTest_t { VtblHeadJc head; OpType_ifcOperation_IfcTest* ifcOperation; Vtbl_ObjectJc ObjectJc; } Vtbl_IfcTest; This is a snippet form static int32 imlOperation_Impl_A_Ifc(ObjectJc* ithiz, int32 data) { return imlOperation_Impl_A((Impl_A_s*)ithiz, data); //casting admissible because static operation only used in inc vtbl } const char sign_Vtbl_IfcTest[] = "vtbl_IfcTest"; const char sign_Vtbl_Impl_A[] = "Vtbl_Impl_A"; const struct { Vtbl_Impl_A vtbl; MtblHeadJc end; } vtbl_Iml_A = { { sign_Vtbl_Impl_A, NrofMethodsForSize_VTBL_Jc(0)} //head , { { sign_Mtbl_ObjectJc, NrofMethodsForSize_VTBL_Jc(5) } , clone_ObjectJc_F , equals_ObjectJc_F , finalize_ObjectJc_F , hashCode_ObjectJc_F , toString_ObjectJc_F } { { sign_Vtbl_IfcTest, NrofMethodsForSize_VTBL_Jc(1) } , imlOperation_Impl_A , { { sign_Mtbl_ObjectJc, NrofMethodsForSize_VTBL_Jc(5) } , clone_ObjectJc_F , equals_ObjectJc_F , finalize_ObjectJc_F , hashCode_ObjectJc_F , toString_ObjectJc_F } } }, { signEnd_Mtbl_ObjectJc, null} }; To invoke a virtual operation an explicit operation is called. /**Frame to invoke the interface operation for usage. * It detects the implementation class and invokes the implementation. */ inline int32 ifcOperation_IfcTest(ObjectJc* ithiz, int32 data) { Vtbl_IfcTest* vtbl = (Vtbl_IfcTest*)getVtbl_ObjectJc(ithiz, sign_Vtbl_IfcTest); if (vtbl != null) { return vtbl->ifcOperation(ithiz, data); } else { return 0; } } Because it is inlined, it does not need an additional call instruction. Firstly
The implementation virtual |