I have multiple pre-compiled dynamic libraries that use the same extern "C"
function names. The functions can behave differently for each dynamic library. Ultimately these methods will be accessed in a SystemVerilog simulation via DPI (Direct Program Interface).
When trying to link all the libraries to my simulator, I noticed that the visibility of the methods is dependent on link order. This blocks me from accessing the desired method of the same name if it is not the first library.
One solution would have been to require each extern "C" function to have a unique name. But this fails in my case for two reasons:
- I do not own the source code of the dynamic libraries. I can make requests, but there is no guarantee whether or when they will be fulfilled.
- It would add a lot of verbosity and static code to my SystemVerilog code as each component would need to figure out which DPI method it needs to access. It also doesn't scale well if a library is added or removed.
My solution is to create my own dynamic library that uses dlopen()
and dlsym()
(from dlfcn.h
) to dynamically access methods from the pre-compiled dynamic libraries. Fortunately, all the pre-compiled library flavors use the same root class. They also contain a publicly accessible variable identifying the compiled flavor. I can use this identifier to decide which library to dynamically reference.
I cannot share the real code. Bellow is a runnable proof of concept. I am getting the desired output. It has been a few years since I have done this kind of coding with C++, so I'm hoping I not missing something.
my_model.h
#include <stdio.h>
#include <iostream>
#include <unordered_map>
#define CONCAT_(A, B) A ## B
#define CONCAT(A, B) CONCAT_(A, B)
#define EXPORTED __attribute__((visibility("default")))
#ifndef FLAVOR
#define FLAVOR PASSION
#endif
namespace my_ns{
enum Flavor { PASSION=0, ORANGE=1, GUAVA=2, POG=-1 };
class my_model {
public:
my_model(int cfg);
virtual ~my_model() { close(); }
virtual void close() { um.clear(); }
virtual int get_info(std::string key, int* value) { return -1; }
virtual int set_info(std::string key, int value) { return -1; }
virtual int del_info(std::string key) { return -1; }
const Flavor flavor = FLAVOR;
protected:
std::unordered_map<std::string,int> um;
};
#if FLAVOR>=0
class CONCAT(my_model_,FLAVOR) : public my_model {
public:
CONCAT(my_model_,FLAVOR)(int cfg) : my_model(cfg) {}
virtual int get_info(std::string key, int* value);
virtual int set_info(std::string key, int value);
virtual int del_info(std::string key);
};
#endif
extern "C" {
int EXPORTED create_model(void** model_h, int flavor, int cfg);
int EXPORTED delete_model(void** model_h);
int EXPORTED get_info(const void* model_h, const char* key, int* value);
int EXPORTED set_info(const void* model_h, const char* key, int value);
int EXPORTED del_info(const void* model_h, const char* key);
}
}
my_model.cpp
#include "my_model.h"
namespace my_ns {
my_model::my_model(int cfg) {
um["cfg"] = cfg;
um["flavor"] = static_cast<int>(FLAVOR);
printf("Info: cfg:%0d FLAVOR:%0d (%s:%s:%d)\n", um["cfg"],FLAVOR,__func__,__FILE__,__LINE__);
}
int CONCAT(my_model_,FLAVOR)::get_info(std::string key, int* value) {
printf("Info: %s(%s,%%d) : %s:%d\n", __func__,key.c_str(), __FILE__,__LINE__);
if (um.count(key)==0) return -1;
*value = um[key] + static_cast<int>(FLAVOR);
return 0;
}
int CONCAT(my_model_,FLAVOR)::set_info(std::string key, int value) {
printf("Info: %s(%s,%0d) : %s:%d\n", __func__,key.c_str(),value, __FILE__,__LINE__);
um[key] = value * um["cfg"] * um["cfg"];
return 0;
}
int CONCAT(my_model_,FLAVOR)::del_info(std::string key) {
printf("Info: %s(%s):%s:%d)\n", __func__,key.c_str(), __FILE__,__LINE__);
if (um.count(key)==0) return -1;
um.erase(key);
return 0;
}
int create_model(void** model_h, int flavor, int cfg) {
printf("Info: cfg:%0d FLAVOR:%d (%s:%s:%d)\n", cfg, FLAVOR, __func__,__FILE__,__LINE__);
*model_h = (void*) new CONCAT(my_model_,FLAVOR)(cfg);
return (!*model_h ? -1 : 0);
}
int delete_model(void** model_h) {
CONCAT(my_model_,FLAVOR)* _model = (CONCAT(my_model_,FLAVOR)*) *model_h;
if (!_model) { fprintf(stderr, "Error: NULL model_h"); return -1; }
delete _model;
*model_h = (void*) NULL;
return 0;
}
int get_info(const void* model_h, const char* key, int* value) {
CONCAT(my_model_,FLAVOR)* _model = (CONCAT(my_model_,FLAVOR)*) model_h;
if (!_model) { fprintf(stderr, "Error: NULL model_h"); return -1; }
return _model->get_info(std::string(key), value);
}
int set_info(const void* model_h, const char* key, int value) {
CONCAT(my_model_,FLAVOR)* _model = (CONCAT(my_model_,FLAVOR)*) model_h;
if (!_model) { fprintf(stderr, "Error: NULL model_h"); return -1; }
return _model->set_info(std::string(key), value);
}
int del_info(const void* model_h, const char* key) {
CONCAT(my_model_,FLAVOR)* _model = (CONCAT(my_model_,FLAVOR)*) model_h;
if (!_model) { fprintf(stderr, "Error: NULL model_h"); return -1; }
return _model->del_info(std::string(key));
}
}
my_top.cpp
#include "my_model.h"
#include <dlfcn.h>
namespace my_ns {
my_model::my_model(int cfg) {}
const char lib[3][16] = {"./libPassion.so","./libOrange.so","./libGuava.so"};
void *my_so[3];
int link_so(Flavor flavor) {
if (my_so[flavor] == NULL) {
printf("linking %s (%s:%s:%d)\n", lib[flavor], __func__,__FILE__,__LINE__);
my_so[flavor] = dlopen(lib[flavor], RTLD_NOW);
}
if (!my_so[flavor]) {
/* fail to load the library */
fprintf(stderr, "Error: %s\n", dlerror());
return -1;
}
return 0;
}
int create_model(void** model_h, int flavor, int cfg) {
link_so(static_cast<Flavor>(flavor));
int (*dlsym_create_model)(void** model_h, int flavor, int cfg);
*(void**)(&dlsym_create_model) = dlsym(my_so[flavor], "create_model");
return dlsym_create_model(model_h, flavor, cfg);
}
int delete_model(void** model_h) {
my_model* _model = (my_model*) model_h;
link_so(_model->flavor);
int (*dlsym_delete_model)(void** model_h);
*(void**)(&dlsym_delete_model) = dlsym(my_so[_model->flavor], "delete_model");
return dlsym_delete_model(model_h);
}
int get_info(const void* model_h, const char* key, int* value) {
my_model* _model = (my_model*) model_h;
link_so(_model->flavor);
int (*dlsym_get_info)(const void* model_h, const char* key, int* value);
*(void**)(&dlsym_get_info) = dlsym(my_so[_model->flavor], "get_info");
return dlsym_get_info(model_h, key, value);
}
int set_info(const void* model_h, const char* key, int value) {
my_model* _model = (my_model*) model_h;
link_so(_model->flavor);
int (*dlsym_set_info)(const void* model_h, const char* key, int value);
*(void**)(&dlsym_set_info) = dlsym(my_so[_model->flavor], "set_info");
return dlsym_set_info(model_h, key, value);
}
int del_info(const void* model_h, const char* key) {
my_model* _model = (my_model*) model_h;
link_so(_model->flavor);
int (*dlsym_del_info)(const void* model_h, const char* key);
*(void**)(&dlsym_del_info) = dlsym(my_so[_model->flavor], "del_info");
return dlsym_del_info(model_h, key);
}
}
my_dpi.sv
package my_dpi_pkg;
import "DPI-C" function int create_model( output chandle handle, input int flavor, cfg );
import "DPI-C" function int delete_model( inout chandle handle );
import "DPI-C" function int get_info( input chandle handle, input string key, output int value );
import "DPI-C" function int set_info( input chandle handle, input string key, input int value );
import "DPI-C" function int del_info( input chandle handle, input string key );
endpackage : my_dpi_pkg
module tb;
import my_dpi_pkg::*;
initial begin
chandle passion_h,orange_h, guava_h;
string str;
int flavor,val;
$display("Create");
assert(create_model(passion_h, 0, 10)==0);
assert(create_model( orange_h, 1, 16)==0);
assert(create_model( guava_h, 2, 8)==0);
$display("\nInfo via set_info()");
assert(set_info(passion_h, "alpha", 'd13)==0);
assert(set_info( orange_h, "beta", 'h13)==0);
assert(set_info( guava_h, "gamma", 'o13)==0);
$display("\nInfo via get_info()");
assert(get_info(passion_h,"flavor", flavor)==0);
assert(get_info(passion_h,"alpha", val)==0);
$display("passion_h flavor:%0d, alpha:'d%0d",flavor,val);
assert(get_info(orange_h,"flavor", flavor)==0);
assert(get_info(orange_h,"beta", val)==0);
$display("orange_h flavor:%0d, beta:'h%0h",flavor,val);
assert(get_info(guava_h,"flavor", flavor)==0);
assert(get_info(guava_h,"gamma", val)==0);
$display("guava_h flavor:%0d, gamma:'o%0o",flavor,val);
assert(get_info(guava_h,"cfg", flavor)==0);
val = 0;
assert(get_info(guava_h,"alpha", val)==-1);
$display("guava_h cfg:%0d, alpha:'d%0d",flavor,val);
$display("Delete");
assert(delete_model(passion_h)==0);
assert(delete_model(orange_h)==0);
assert(delete_model(guava_h)==0);
$finish(0);
end
endmodule
Commands to build and run:
g++ -fvisibility=hidden -fvisibility-inlines-hidden -s -shared -fPIC -std=gnu++11 -DFLAVOR=PASSION -o libPassion.so my_model.h my_model.cpp -Wall -g || exit 1
g++ -fvisibility=hidden -fvisibility-inlines-hidden -s -shared -fPIC -std=gnu++11 -DFLAVOR=ORANGE -o libOrange.so my_model.h my_model.cpp -Wall -g || exit 1
g++ -fvisibility=hidden -fvisibility-inlines-hidden -s -shared -fPIC -std=gnu++11 -DFLAVOR=GUAVA -o libGuava.so my_model.h my_model.cpp -Wall -g || exit 1
g++ -fvisibility=hidden -fvisibility-inlines-hidden -s -shared -fPIC -std=gnu++11 -DFLAVOR=POG -o libPOG.so my_model.h my_top.cpp -Wall -g
<sv_simulator> -<dpi_keyword> libPOG.so my_dpi.sv
Output:
Create
linking ./libPassion.so (link_so:my_top.cpp:11)
Info: cfg:10 FLAVOR:0 (create_model:my_model.cpp:29)
Info: cfg:10 FLAVOR:0 (my_model:my_model.cpp:7)
linking ./libOrange.so (link_so:my_top.cpp:11)
Info: cfg:16 FLAVOR:1 (create_model:my_model.cpp:29)
Info: cfg:16 FLAVOR:1 (my_model:my_model.cpp:7)
linking ./libGuava.so (link_so:my_top.cpp:11)
Info: cfg:8 FLAVOR:2 (create_model:my_model.cpp:29)
Info: cfg:8 FLAVOR:2 (my_model:my_model.cpp:7)
Info via set_info()
Info: set_info(alpha,13) : my_model.cpp:17
Info: set_info(beta,19) : my_model.cpp:17
Info: set_info(gamma,11) : my_model.cpp:17
Info via get_info()
Info: get_info(flavor,%d) : my_model.cpp:11
Info: get_info(alpha,%d) : my_model.cpp:11
passion_h flavor:0, alpha:'d1300
Info: get_info(flavor,%d) : my_model.cpp:11
Info: get_info(beta,%d) : my_model.cpp:11
orange_h flavor:2, beta:'h1301
Info: get_info(flavor,%d) : my_model.cpp:11
Info: get_info(gamma,%d) : my_model.cpp:11
guava_h flavor:4, gamma:'o1302
Info: get_info(cfg,%d) : my_model.cpp:11
Info: get_info(alpha,%d) : my_model.cpp:11
guava_h cfg:10, alpha:'d0
Delete
./my_dpi.sv:42 $finish(0);