Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Porting polymorphic classes from C++ to C

36 views
Skip to first unread message

Frederick Gotham

unread,
Aug 18, 2020, 9:32:53 AM8/18/20
to

At the moment I'm taking the encryption technique I've coded in multithreaded C++ code, and porting it to single-threaded C for use on a microcontroller.

To demonstrate how I'm handling so-called 'polymorphic' function calls, let's start off with this C++ program:

#include <cstddef> /* size_t */
#include <cstring> /* strlen, strcpy */

#include <iostream> /* cout, endl */

class Parser {
protected:

char *_name;
bool _should_tell_name;

public:

Parser(char const *const arg, bool const shareable) : _should_tell_name(shareable)
{
_name = new char[std::strlen(arg)];
std::strcpy(_name,arg);

std::cout << "Object constructed" << std::endl;
}

virtual ~Parser(void)
{
delete [] _name;

std::cout << "Object destructed" << std::endl;
}

virtual bool CanTellName(void) const { return _should_tell_name; }

virtual char const *GetName(void) const
{
return CanTellName() ? _name : "No Comment";
}
};

int main()
{
Parser obj("Frederick", false);

std::cout << obj.GetName() << std::endl;
}


Well the first thing I do to turn this into C is to change the included header filenames, and replace cout's and new's:

#include <stddef.h> /* size_t */
#include <string.h> /* strlen, strcpy */
#include <stdlib.h> /* malloc, free */
#include <stdio.h> /* puts */

typedef int intbool; /* Because C90 doesn't have bool */

class Parser {
protected:

char *_name;
intbool _should_tell_name;

public:

Parser(char const *const arg, intbool const shareable) : _should_tell_name(shareable)
{
_name = (char*)malloc(strlen(arg));
strcpy(_name,arg);

puts("Object constructed");
}

virtual ~Parser(void)
{
free(_name);

puts("Object destructed");
}

virtual intbool CanTellName(void) const { return _should_tell_name; }

virtual char const *GetName(void) const
{
return CanTellName() ? _name : "No Comment";
}
};

int main()
{
Parser obj("Frederick", true);

puts(obj.GetName());
}



Next I turn the class into a POD struct with a Vtable as follows, the following with compile with a C compiler:


#include <stddef.h> /* size_t */
#include <string.h> /* strlen, strcpy */
#include <stdlib.h> /* malloc, free */
#include <stdio.h> /* puts */

typedef int intbool; /* Because C90 doesn't have bool */

struct Parser_Vtable {
void (*Destructor)(void);
intbool (*CanTellName)(void) /* const */ ;
char const *(*GetName)(void) /* const */ ;
};

void Parser_Detail_Destructor (void);
intbool Parser_Detail_CanTellName(void) /* const */ ;
char const *Parser_Detail_GetName (void) /* const */ ;

struct Parser_Vtable const g_parser_vtable = {
&Parser_Detail_Destructor,
&Parser_Detail_CanTellName,
&Parser_Detail_GetName
};

struct Parser {
struct Parser_Vtable const *vtable;

char *_name;
intbool _should_tell_name;
};

void Parser_Constructor(struct Parser *const this, char const *const arg, intbool const shareable)
{
this->_should_tell_name = shareable;
this->_name = (char*)malloc(strlen(arg));
strcpy(this->_name,arg);

puts("Object constructed");
}

int main()
{
struct Parser obj;

Parser_Constructor(&obj, "Frederick", 1 /*true*/);

/* puts(obj.GetName()); - - What do we do here ? */
}


Next I'll implement the three virtual functions in C:


void Parser_Detail_Destructor(void)
{
struct Parser *const this = Pop_This();

free(this->_name);

puts("Object destructed");
}

intbool Parser_Detail_CanTellName(void) /* const */
{
struct Parser const *const this = Pop_This_Const();

return this->_should_tell_name;
}

char const *Parser_Detail_GetName(void) /* const */
{
struct Parser const *const this = Pop_This_Const();

/* I will explain this next line in just a minute */
return Virtcall(this,CanTellName)() ? this->_name : "No Comment";
}


In order to get this all to work, here's my helper functions, and my macro "Virtcall" which is used for invoking a virtual method on an object:

void *g_stack_of_this[16] = {0};

static void Push_This(void const *const arg)
{
void **p = g_stack_of_this + 0u;

while ( 0 != *p ) ++p;

*p = (void*)arg; /* Okay to discard const here if correct form of Pop_This is used */
}

static void *Pop_This(void)
{
void *retval;

void **q = g_stack_of_this + (sizeof g_stack_of_this / sizeof *g_stack_of_this) - 1u;

while ( 0 == *q ) --q;

retval = *q;

*q = 0;

return retval;
}

static void const *Pop_This_Const(void) { return (void const*)Pop_This(); }

#define Virtcall(p, method) ( Push_This((p)) , ((p))->vtable->method )


The macro "Virtcall" yields a function pointer, so you can then put "()" after it to call the function with no arguments.

This code I've written isn't threadsafe because there is only one "g_stack_of_this" for the entire process. On a newer C compiler (e.g. C11), you could use the "_Thread_local" keyword on this global array to make everything threadsafe.

So anyway this is how I'm going about porting my C++ encryption technique to C.

Here's the full code:

#include <stddef.h> /* size_t */
#include <string.h> /* strlen, strcpy */
#include <stdlib.h> /* malloc, free */
#include <stdio.h> /* puts */

void *g_stack_of_this[16] = {0};

static void Push_This(void const *const arg)
{
void **p = g_stack_of_this + 0u;

while ( 0 != *p ) ++p;

*p = (void*)arg; /* Okay to discard const here if correct form of Pop_This is used */
}

static void *Pop_This(void)
{
void *retval;

void **q = g_stack_of_this + (sizeof g_stack_of_this / sizeof *g_stack_of_this) - 1u;

while ( 0 == *q ) --q;

retval = *q;

*q = 0;

return retval;
}

static void const *Pop_This_Const(void) { return (void const*)Pop_This(); }

#define Virtcall(p, method) ( Push_This((p)) , ((p))->vtable->method )

typedef int intbool; /* Because C90 doesn't have bool */

struct Parser_Vtable {
void (*Destructor)(void);
intbool (*CanTellName)(void) /* const */ ;
char const *(*GetName)(void) /* const */ ;
};

void Parser_Detail_Destructor (void);
intbool Parser_Detail_CanTellName(void) /* const */ ;
char const *Parser_Detail_GetName (void) /* const */ ;

struct Parser_Vtable const g_parser_vtable = {
&Parser_Detail_Destructor,
&Parser_Detail_CanTellName,
&Parser_Detail_GetName
};

struct Parser {
struct Parser_Vtable const *vtable;

char *_name;
intbool _should_tell_name;
};

void Parser_Constructor(struct Parser *const this, char const *const arg, intbool const shareable)
{
this->vtable = &g_parser_vtable;

this->_should_tell_name = shareable;
this->_name = (char*)malloc(strlen(arg));
strcpy(this->_name,arg);

puts("Object constructed");
}

int main()
{
struct Parser obj;

Parser_Constructor(&obj, "Frederick", 1 /*true*/);

puts( Virtcall(&obj,GetName)() );

Virtcall(&obj,Destructor)();

return 0;
}

void Parser_Detail_Destructor(void)
{
struct Parser *const this = Pop_This();

free(this->_name);

puts("Object destructed");
}

intbool Parser_Detail_CanTellName(void) /* const */
{
struct Parser const *const this = Pop_This_Const();

return this->_should_tell_name;
}

char const *Parser_Detail_GetName(void) /* const */
{
struct Parser const *const this = Pop_This_Const();

return Virtcall(this,CanTellName)() ? this->_name : "No Comment";
}

Frederick

Frederick Gotham

unread,
Aug 18, 2020, 2:20:40 PM8/18/20
to

> _name = new char[std::strlen(arg)];
> std::strcpy(_name,arg);


Array is one byte too small.


> this->_name = (char*)malloc(strlen(arg));
> strcpy(this->_name,arg);


Same bug here.

Ian Collins

unread,
Aug 19, 2020, 4:46:51 AM8/19/20
to

On 19/08/2020 01:32, Frederick Gotham wrote:
>
> At the moment I'm taking the encryption technique I've coded in
> multithreaded C++ code, and porting it to single-threaded C for use
> on a microcontroller.

Why don't you just use a better microcontroller with a C++ compiler?

--
Ian.

Juha Nieminen

unread,
Aug 19, 2020, 5:17:57 AM8/19/20
to
Sometimes there's no choice because the microcontroller is demanded by a
client.

Daniel Hyde

unread,
Aug 19, 2020, 5:21:48 AM8/19/20
to
> Sometimes there's no choice because the microcontroller is demanded by a
> client.

Kill the client !

David Brown

unread,
Aug 19, 2020, 6:43:45 AM8/19/20
to
If the client asks for something that is clearly virtually impossible -
such as converting a large C++ program into something that will run on
this microcontroller (with its severely limited 8-bit cpu, poor C
compiler, and 3K of ram), including Ethernet handling - then that's
usually something you discuss with the client. Perhaps there are
situations where it is best to simply try as hard as you can, but
usually honesty works best here.

In this case, the OP has chosen the microcontroller here himself as far
as I can tell. But I don't have a complete picture - he has described a
device that has two network ports, where unencrypted traffic comes in on
one port and encrypted traffic goes out on the other. How he intends to
do that with this PIC, with one Ethernet connection, is unknown to me.

Perhaps he will use two of them, connected back-to-back via a UART
interface. If all the encryption is done at the Ethernet packet level,
rather than TCP/IP or UDP level, then it's conceivable that it could be
done even with this small ram. (What the device would then talk to is
another matter.) It would be "security by making a system so slow that
no one would use it and thus there is no security risk", but maybe
that's his aim.



0 new messages