Skip to content

Inside IoT.js Validated Struct

Hosung Kim edited this page Jul 3, 2017 · 2 revisions

Validated struct is C struct wrapper for encapsulation and validity check.

  • Validated Struct Declaration
  • Constructors, Destructor, Methods
  • Ownership of validated struct instance
    • Case 1: Validated struct instance as local variable
    • Case 2: Validated struct instance as parameter & return
    • Case 3: Validated struct instance as member variable of other struct
    • Case 4: Validated struct instance as data of asynchronous execution

Validated Struct Declaration

typedef struct {
  int a;
  void* b;
} IOTJS_VALIDATED_STRUCT(iotjs_myclass_t);

Above struct will make the member variable encapsulated by wrapping real members with wrapper like below.

typedef struct {
  int a;
  void* b;
} iotjs_myclass_t_impl_t;

typedef struct {
  iotjs_myclass_impl_t unsafe;
  /* More members for struct validity check exist in debug mode */
} iotjs_myclass_t;

int main() {
  iotjs_myclass_t x;
}

Only wizards will access the members directly by using x.unsafe.a, x.unafe.b, ... . Otherwize the members are only accessible with its accessor function.

See src/iotjs_def.h for more details on real implementation.

Constructors, Destructor, Methods

You should create C++-like constructors, destructor and methods with provided accessor. Then you can access the encapsulated member variables using _this variable, which has almost same role with C++ this keyword. You must call destroy for every validated structs you've created.

/* Constructor */
iotjs_myclass_t iotjs_myclass_create(int a) {
  iotjs_myclass_t instance;
  IOTJS_VALIDATED_STRUCT_CONSTRUCTOR(iotjs_myclass_t, &instance);

  _this->a = a;
  _this->b = malloc(a);

  return instance;
}

/* Destructor */
void iotjs_myclass_destroy(iotjs_myclass_t* instance) {
  IOTJS_VALIDATED_STRUCT_DESTRUCTOR(iotjs_myclass_t, instance);
  free(_this->b);
}

/* Method */
int iotjs_myclass_get_a(iotjs_myclass_t* instance) {
  IOTJS_VALIDATED_STRUCT_METHOD(iotjs_myclass_t, instance);
  return _this->a;
}

int main() {
  /* Validated struct as local variable */
  iotjs_myclass_t local_instance = iotjs_myclass_create(3);
  printf("%d\n", iotjs_myclass_get_a(&local_instance));
  iotjs_myclass_destroy(&local_instance);
  return 0;
}

Ownership of validated struct instance

The ground rule is:

  • Use iotjs_classname_t typed variable if the variable is responsible for destruction of instance.
  • Use iotjs_classname_t* typed variable if the variable is not responsible for destruction of instance.

Below Case 1 ~ Case 4 shows the case-by-case example of the ownership rule.

Case 1: Validated struct instance as local variable

The local_instance variable in previous example was the local instance of validated struct. Since local_instance should be destructed inside the function scope, iotjs_myclass_t type was used.

Case 2: Validated struct instance as parameter & return

Previous example also included the example of validated struct instance as parameter and return. When accessing member variable a by calling iotjs_myclass_get_a(), iotjs_myclass_t* type was used as the parameter type, since it does not move the responsibility to destruct the instance.

And when returning the newly created instance by calling iotjs_myclass_create(), iotjs_myclass_t type was used as return type, since it does move the responsibility to destruct the instance.

Case 3: Validated struct instance as member variable of other struct

/* Validated struct as member variable of other struct */

typedef struct {
  iotjs_myclass_t member_instance;
} IOTJS_VALIDATED_STRUCT(iotjs_otherclass_t)

iotjs_otherclass_t iotjs_otherclass_create() {
  /* Initialization steps for iotjs_otherclass_t */
  _this->member_instance = iotjs_myclass_create(3);
}

void iotjs_otherclass_destroy() {
  /* Finalization steps for iotjs_otherclass_t */
  iotjs_myclass_destroy(&_this->member_instance);
}

In the case above, iotjs_myclass_t instance is used as member variable of other class. Since iotjs_otherclass_t is responsible for finalizing the member_instance, it owns the variable as iotjs_myclass_t type, not pointer type.

Case 4: Validated struct instance as data of asynchronous execution

Another usecase would be using validated struct as callback data. Currently, our all asynchronous datas are wrapped with iotjs_*wrap_t type, and they are destructed automatically.

/*
 * Public APIs in iotjs_module_fs.h
 */

typedef struct {
  iotjs_reqwrap_t reqwrap;
  uv_fs_t req;
} IOTJS_VALIDATED_STRUCT(iotjs_fsreqwrap_t);

iotjs_fsreqwrap_t* iotjs_fsreqwrap_create(const iotjs_jval_t* jcallback);
void iotjs_fsreqwrap_dispatched(iotjs_fsreqwrap_t* fsreqwrap);

As you can see, constructor returns the iotjs_fsreqwrap_t* type, because it does not pass the responsibility to destruct the return value. It is destructed when request is dispatched, which can be informed by calling iotjs_fsreqwrap_dispatched(). The destructor iotjs_fsreqwrap_destroy() is hidden in c file.

/*
 * Implementation in iotjs_module_fs.c
 */

iotjs_fsreqwrap_t* iotjs_fsreqwrap_create(const iotjs_jval_t* jcallback) {
  iotjs_fsreqwrap_t* fsreqwrap = IOTJS_ALLOC(iotjs_fsreqwrap_t);
  IOTJS_VALIDATED_STRUCT_CONSTRUCTOR(iotjs_fsreqwrap_t, fsreqwrap);
  iotjs_reqwrap_initialize(&_this->reqwrap, jcallback, (uv_req_t*)&_this->req);
  return fsreqwrap;
}

static void iotjs_fsreqwrap_destroy(iotjs_fsreqwrap_t* fsreqwrap) { // private function
  IOTJS_VALIDATED_STRUCT_DESTRUCTOR(iotjs_fsreqwrap_t, fsreqwrap);
  uv_fs_req_cleanup(&_this->req);
  iotjs_reqwrap_destroy(&_this->reqwrap);
  IOTJS_RELEASE(fsreqwrap);
}

void iotjs_fsreqwrap_dispatched(iotjs_fsreqwrap_t* fsreqwrap) {
  IOTJS_VALIDATED_STRUCT_METHOD(iotjs_fsreqwrap_t, fsreqwrap);
  iotjs_fsreqwrap_destroy(fsreqwrap);
}

/*
 * Use of iotjs_fsreqwrap_t
 */

void callback(uv_fs_t* req) {
    do_something(req);
    iotjs_fsreqwrap_dispatched(req); /* Call iotjs_*reqwrap_dispatched() when callback called */
}

void request(iotjs_jval_t* jcallback) {
    iotjs_fsreqwrap_t* wrap = iotjs_fsreqwrap_create(jcallback);
    uv_fs_request(loop, wrap->req, callback);
}

In the case of tuv request wrapper, iotjs_*reqwrap_dispatched() should be called when the request has been dispatched. In the case of tuv handle wrapper, iotjs_handlewrap_close() should be called when the handle has been closed. in the case of JavaScript object wrapper, you don't have to do anything because JavaScript engine will call the destructor when the object becomes inaccessible.

Clone this wiki locally