ObjO_emC

ObjO_emC

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:

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++):


1 Plan to work ObjectOriented in C

Topic:.ObjO_emC..


2 Using a mix of C and C++

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.


2.1 class and struct definition for the same CLASS_C

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.


2.2 ObjectJc in C++, ObjectifcBaseJcpp

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

3 Abstraction and Derivation, super classes 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.


4 The struct ObjectJc as base or super struct

Topic:.ObjO_emC.ObjectJc.

All more complex data types have common properties for usage:

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 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:


4.1 Notation of usage a super struct and ObjectJc

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.


4.2 Notation of the allocation and initializing routine and reflection setting

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:

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.


5 Interfaces in C, virtual Operations

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 base of a user's struct definition:

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 union, especially the ObjectJc from the interface is the same as the ObjectJc of all super types and the type itself. There is only one ObjectJc data on start of the data of the struct.

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 _F means 'final', it is the implementation. The __ on end is a clear mark to prevent using from an application. An application may use the routine with a faulty instance with Type of MyInterface_s__ but not type of MyStruct. An application should never invoke routines with __ on end, they are private.

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 myOperation_MyStruct_F__ is executed, because inline. But the compiler checks the type of thiz and prevent a faulty type.

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 ObjectJc. ObjectJc contains a reference to the ClassJc const* reflectionClass. The data of reflection are either stored in a ROM memory area (read only, on flash) or it can be memory-protected. Hence a change (disturbation) with faulty software can be excluded. The ClassJc contains a reference VtblHeadJc const* mtbl. This is adequate to the virtual table of C++. But it is referenced in two stages.

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 virtual which was not virtual before, a new member is inserted in the virtual table. If another compiling unit (part of a library) is not re-compiled, because the user thinks, 'data are not changed', the code in the library executes the faulty operation, 'close the door' instead the new virtual 'open', with posible damage on live. of course, the re-compilation is necessary but in can be forgotten.

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 emcTest/Test_ifc_C/src/ifc.h. The implementation looks like:

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 getVtbl_ObjectJc(...) is called with the given instance. This routine takes the information about the virtual table via reflection from the implementing machine code. It checks with three stages:

  • Check ithiz is a valid ObjectJc and not any desired faulty pointer

  • Check whether the found reflectionClass referred to an instance of ClassJc via check its reflectionClass. This is a obvious check. It is improbable that a faulty pointer find a ClassJc instance which is not such one.

  • The VtblHeadJc const* mtbl pointer is taken. In this struct a table with the signum sign_Vtbl_IfcTest is searched. It is a memory address which contains that specific text. If it is not found, the instance does not implement the required operation. Then null is returned. If it is found, the associated virtual table is in the same compilation unit as the implememtation.

The implementation virtual