Calling C ++ Shared Lib in a C program ... how to control?
I would like to write a wrapper for a C ++ framework. this framework is a kind of bug and not very nice in C ++ either. so I would like to be able to call my methods from outside (via good old C file) of my structure using just one shared lib. it sounds like a need for a wrapper that encapsulates the required infrastructure methods for use with C instead of C ++.
So far so good .... here's what I already did:
aldebaran.h interface (this is in my included folder, ultrasonic methods should be called from outside the framework):
#ifndef _ALDEBARAN_H
#define _ALDEBARAN_H
#ifdef __cplusplus
extern "C" {
#endif
void subscribe_ultrasound();
void unsubscribe_ultrasound();
float read_ultrasound();
#ifdef __cplusplus
}
#endif
#endif
now the wrapper:
cpp file aldebaran.cpp:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "aldebaran.h"
#include "alproxy.h"
#include "../../include/aldebaran.h"
/*
* Ultrasound defines
*/
#define ULTRASOUND_RESERVATION_MAGIC "magic_foobar"
#define ULTRASOUND_POLL_TIME 250
#define ULTRASOUND_READ_ATTEMPTS 50
#define ULTRASOUND_SLEEP_TIME 20
using namespace std;
using namespace AL;
/*
* Framework proxies
*/
ALPtr<ALProxy> al_tts;
ALPtr<ALProxy> al_led;
ALPtr<ALProxy> al_motion;
ALPtr<ALProxy> al_mem;
ALPtr<ALProxy> al_us;
ALPtr<ALProxy> al_cam;
ALPtr<ALProxy> al_dcm;
/*
* Constructor
*/
Aldebaran::Aldebaran(ALPtr<ALBroker> pBroker, std::string pName): ALModule(pBroker, pName)
{
try {
al_tts = this->getParentBroker()->getProxy("ALTextToSpeech");
al_led = this->getParentBroker()->getProxy("ALLeds");
al_motion = this->getParentBroker()->getProxy("ALMotion");
al_mem = this->getParentBroker()->getProxy("ALMemory");
al_us = this->getParentBroker()->getProxy("ALUltraSound");
al_cam = this->getParentBroker()->getProxy("NaoCam");
al_dcm = this->getParentBroker()->getProxy("DCM");
}catch(ALError& err){
std::cout << "XXX: ERROR: " << err.toString() << std::endl;
return 1;
}
printf("XXX: module aldebaran initiated\n");
fflush(0);
}
/*
* Destructor
*/
Aldebaran::~Aldebaran()
{
printf("XXX: module aldebaran destructed\n");
fflush(0);
}
/*
* Subscribe to ultrasound module
*/
void subscribe_ultrasound()
{
ALValue param;
param.arrayPush(ULTRASOUND_POLL_TIME);
al_us->callVoid("subscribe", string(ULTRASOUND_RESERVATION_MAGIC), param);
printf("XXX: ultrasound subscribed: %s\n", ULTRASOUND_RESERVATION_MAGIC);
fflush(0);
}
/*
* Unsubscribe to ultrasound module
*/
void unsubscribe_ultrasound()
{
al_us->callVoid("unsubscribe", string(ULTRASOUND_RESERVATION_MAGIC));
printf("XXX: ultrasound unsubscribed: %s\n", ULTRASOUND_RESERVATION_MAGIC);
fflush(0);
}
/*
* Read from ultrasound module
*/
float read_ultrasound()
{
int i;
float val1, val2;
float val_sum;
ALValue distance;
val_sum = .0f;
for(i = 0; i < ULTRASOUND_READ_ATTEMPTS; ++i){
SleepMs(ULTRASOUND_SLEEP_TIME);
distance = al_mem->call<ALValue>("getData", string("extractors/alultrasound/distances"));
sscanf(distance.toString(AL::VerbosityMini).c_str(),"[%f, %f, \"object\"]", &val1, &val2);
val_sum += val1;
}
return val_sum / (1.f * ULTRASOUND_READ_ATTEMPTS);
}
definition file for aldebaran.cpp:
#ifndef ALDEBARAN_API_H
#define ALDEBARAN_API_H
#include <string>
#include "al_starter.h"
#include "alptr.h"
using namespace AL;
class Aldebaran : public AL::ALModule
{
public:
Aldebaran(ALPtr<ALBroker> pBroker, std::string pName);
virtual ~Aldebaran();
std::string version(){ return ALTOOLS_VERSION( ALDEBARAN ); };
bool innerTest(){ return true; };
};
#endif
So this should be a simple example for my wrapper, and it compiles fine to libaldebaran.so.
now my test program in C:
... now I would like to call the methods of the aldebaran.h interface from a simple c file like this:
#include <stdio.h>
/*
* Begin your includes here...
*/
#include "../include/aldebaran.h"
/*
* End your includes here...
*/
#define TEST_OKAY 1
#define TEST_FAILED 0
#define TEST_NAME "test_libaldebaran"
unsigned int count_all = 0;
unsigned int count_ok = 0;
const char *__test_print(int x)
{
count_all++;
if(x == 1){
count_ok++;
return "ok";
}
return "failed";
}
/*
* Begin tests here...
*/
int test_subscribe_ultrasound()
{
subscribe_ultrasound();
return TEST_OKAY;
}
int test_unsubscribe_ultrasound()
{
unsubscribe_ultrasound();
return TEST_OKAY;
}
int test_read_ultrasound()
{
float i;
i = read_ultrasound();
return (i > .0f ? TEST_OKAY : TEST_FAILED);
}
/*
* Execute tests here...
*/
int main(int argc, char **argv)
{
printf("running test: %s\n\n", TEST_NAME);
printf("test_subscribe_ultrasound: \t %s\n", __test_print(test_subscribe_ultrasound()));
printf("test_read_ultrasound: \t %s\n", __test_print(test_read_ultrasound()));
printf("test_unsubscribe_ultrasound: \t %s\n", __test_print(test_unsubscribe_ultrasound()));
printf("test finished: %s has %u / %u tests passed\n\n", TEST_NAME, count_ok, count_all);
return (count_all - count_ok);
}
how can i call these methods? i mean in my C file, i have no way to instantiate an object like this (which generated all the ALProxies needed) i?
help would be really appreciated ... thanks
Thank you very much!
as xtofl said .. I would like to keep my interface as simple as possible (no other C ++ object):
#ifndef _ALDEBARAN_H
#define _ALDEBARAN_H
#ifdef __cplusplus
extern "C" {
#endif
void subscribe_ultrasound();
void unsubscribe_ultrasound();
float read_ultrasound();
#ifdef __cplusplus
}
#endif
#endif
the problem is that functions like subscribe_ultrasound () cannot be called without setting all proxies ... this is our precondition:
...
al_tts = this->getParentBroker()->getProxy("ALTextToSpeech");
al_led = this->getParentBroker()->getProxy("ALLeds");
al_motion = this->getParentBroker()->getProxy("ALMotion");
al_mem = this->getParentBroker()->getProxy("ALMemory");
al_us = this->getParentBroker()->getProxy("ALUltraSound");
al_cam = this->getParentBroker()->getProxy("NaoCam");
al_dcm = this->getParentBroker()->getProxy("DCM");
...
if I don't have the above code, everyone else will fail.
within them it is possible to "autoload" my libaldebaran.so via a python script, like this call:
myModule = ALProxy("Aldebaran", global_params.strRemoteIP, global_params.nRemotePort );
The structure log says:
May 10 15:02:44 Hunter user.notice root: XXX: module aldebaran initiated
May 10 15:02:46 Hunter user.notice root: INFO: Registering module : 'Aldebaran'
May 10 15:02:46 Hunter user.notice root: ______ End of loading libraries ______
which is quite normal ... he called my module constructor (so all other necessary proxies got support too).
but of course this instance does not belong to my C program ...
Maybe there is an opportunity to share this with all other processes?
a source to share
You said you should create a C wrapper API around the OO framework.
This means that you don't need any objects passing the wrapper API (as you can see from the header with the decryption). It seems that all required objects are created / destroyed behind the wrapper API, outside of your test program.
The first seems to be accidental. You don't need objects to validate the wrapper API. As a result, all objects are bytes (in memory), which are accessed through a fixed set of functions. It doesn't matter if the functions are written as member functions (C ++) or as simple C functions as long as they obey the intended semantics of your objects.
a source to share
You might want to take a slightly different approach. Consider something like this for your C interface:
#ifdef __cplusplus
extern "C" {
#endif
struct UltrasoundHandle;
UltrasoundHandle* ultrasound_Create();
void ultrasound_Destroy(UltrasoundHandle *self):
void ultrasound_Subscribe(UltrasoundHandle *self);
void ultrasound_Unsubscribe(UltrasoundHandle *self);
float ultrasound_Read(UltrasoundHandle *self);
#ifdef __cplusplus
}
#endif
The structure is UltrasoundHandle
purposefully opaque, so you can define it in the implementation however you want. Another modification I made was to add explicit create and destroy methods, similar to constructor and destructor. The implementation will look something like this:
extern "C" {
struct UltrasoundHandle {
UltrasoundHandle() {
// do per instance initializations here
}
~UltrasoundHandle() {
// do per instance cleanup here
}
void subscribe() {
}
void unsubscribe() {
}
float read() {
}
};
static int HandleCounter = 0;
UltrasoundHandle* ultrasound_Create() {
try {
if (HandleCounter++ == 1) {
// perform global initializations here
}
return new UltrasoundHandle;
} catch (...) {
// log error
}
return NULL;
}
void ultrasound_Destroy(UltrasoundHandle *self) {
try {
delete self;
if (--HandleCounter == 0) {
// perform global teardown here
}
} catch (...) {
// log error
}
}
The key is to wrap the C ++ to C interfaces to expose OO concepts through free functions, where the caller explicitly passes the object pointer ( this
) to the function and explicitly exposes the constructor and destructor in the same way. From there, the wrapper code can be almost mechanically generated. The other key points are that you never let exceptions propagate outward and avoid global object instances. I'm not sure what the latter will cause you grief, but I will worry about construct / destruction ordering issues.
a source to share
I don't understand if you know about this, but if you have C ++ code to load dynamically into your program, you should link your program to the C ++ compiler and make your main function a C ++ function as well - even if it is as trivial as:
int main(int argc, char **argv)
{
return(real_main_in_c(argc, argv));
}
A function real_main_in_c()
is what you called earlier main()
; it's just renamed. This ensures that the C ++ mechanisms to handle initialization of global and static variables, etc. Loading and running. C ++ startup code runs more than C startup code. Dynamic loading C
This is only one (small) facet of the answer - but it is an important practical option.
a source to share
Keep things as simple as possible - but not simpler. I think you are trying to make it too simple, wrap everything up, but use a C ++ compiler.
so if you create your wrapper with a C ++ compiler, you can create objects inside your subscriber function, release them all in unsubscribe using all static (or global) objects. The 3 functions you want to expose are simply wrapped in "extern C" and you have a C style interface that is accessible to any caller while still encapsulating C ++ objects.
If you need another function to instantiate all proxies, add them; alternatively, if they don't already exist, create them so that they are always created the first time they are called to subscribe.
Now, if you need proxy objects based on each instance (i.e. you have 2 subscribers who both want to subscribe and they need a unique proxy for each subscriber), you will have to store the objects in a collection (I suggest a map). every call you make must then pass a "handle" or "session" that you use to retrieve the objects for each call from the map.
a source to share