apolukhin / Boost.DLL

Library for comfortable work with DLL and DSO
https://boost.org/libs/dll
110 stars 69 forks source link

Possible Extension - Loading mangled symbols #13

Closed klemens-morgenstern closed 6 years ago

klemens-morgenstern commented 9 years ago

This issue describes possible features I'd like to add to boost.DLL.

Preface

I was recently working on a shared library import library, though with a few more features boost.DLL does not have. It is currently more a proof-of-concept, but I could certify (on windows with gcc 5.1 and MSVC) that these work. Since Clang uses the same ABI as Gcc, I am quite confident that this will work on most platforms.

Idea

The basic Idea is to implement loading of mangled names. This shall be done by reading the complete outline from the shared library and demangle each name. These demangled names will then be stored in a map with the mangled one. That way we can easily match names for requested functions.

If this is added to boost.DLL, it would be an additional class, which shared a _sharedlib.

The examples given below are overly simple on purpose. I don't have a detailed design for the library, but I will add it during development. This shall give a basic idea and start a discussion.

Mangled Functions and Variables

Loading a plain mangled name is quite easy, though I have to add, that it's also type-safe in MSVC, since this compiler also mangles the typename, whlie the Itanium ABI does not. I will later discuss how this could be added for GCC. So now, compared to the plain boost.dll, a mangled name can be loaded rather easy:

var = dll::import_variable<int>("some_namespace::variable");

The check if the name is mangled is in itself trivial. Or for static functions:

plugin = dll::import_function<int(double)>("some_namespace::some_function");

Constructors/Destructors and Member functions

It is basicly as easy to get pointers to member-functions and constructors. Constructors and Member-functions behave in the same way, so once we have enough allocated memory, we can explicitly call the constructors.

Getting a Constructor:

plugin = dll::import_constructor<void(int, int)>("some_namespace::some_class");

Loading a member-function:

Getting a Constructor:

plugin = dll::import_constructor<double(int, int)>("some_namespace::some_class::method");

Safe Import

Now if we import a constructor, we can call him like a member-function, but we still do not know which size it has.

Hence we need a mechanism to import this safely. Additionally such a mechanism would also provide a safe import for all variables and functions, because we know their (return) types.

Variant 1 - Each export has a symbol

Now quite simple is the following solution: export a symbol for each variable, e.g.:

#define EXPORT_VARIABLE(Name) \
namespace boost { namespace dll_export {  \
struct Name##Export { static size_t size() {return sizeof(Name);}; }}}

We could also export it's type this way. The problem is, that the outline looks aweful afterwards.

Variant 2 - Export Table

We declare a global variable, which holds all information. Out macro will declary a dummy, whoms constructor will pass information to a singleton. This singleton then is exported.

I didn't think this concept through, but I will give it some thought as soon as I am finished with the core.

Status

As mentioned in the beginning, I have a proof-of-concept implemenation and I am currently working on building a implementation which ties into boost.dll. This will be the major part, i.e. to develop a layer which allows the import of mangled functions. If that works, one of the concepts described above will come in place, and I will exploit the type system, to construct type safety while importing functions.

klemens-morgenstern commented 9 years ago

I forgot: to demangle the functions names, we will need to add DbgHelp as a depency (when used) because I need the UndecorateFunctionsNames function. I hope that's not a dealbreaker. However: implementing this library myself seems to be a bad approach - not only because it's complicated, but because it would need to stay updated, everytime MS decides to change their abi agein.

apolukhin commented 9 years ago

This is a very interesting and promising feature, thanks for doing it!

Symbol mangling/demangling was avoided in DLL library until now, because false approach was taken by me: I was thinking of mangling an user's input. In other words, I was attempting to mangle "void boost::foo(std::sting)" into N5boostN3foosE. That lead to nowhere, because not enough info is provided by unmangled strings to make a valid mangled name (consider the "boost::foo(variant<int, short>)" that is impossible to mangle without actually seeing the class variant<T0, T1=na, T2=na, T3=na, T4=na> declaration).

Your approach seems more right. However you need to take care of overloaded functions. Cover in demangling tests the case when following functions are exported from a shared library:

/*0*/ void foo(int);
/*1*/ void foo(variant<int, short>);
/*2*/ void foo(variant<int, double>);
/*3*/ void foo(void*);
/*4*/ void foo();

User must be able to load any of the functions.

Both "Safe Import" features may be provided in examples, but must not be in Boost.DLL library. They have too many tuning points and variations, while writing them from scratch is a matter of 5 minutes. It's better to show them in example and leave the users to implement them in a way they like: using functions, global variables, specializing Boost.TypeIndex or anyway they wish to.

P.S.: Sorry for the late response. Currently I'm fighting with dynamic linker differences between platforms (and doing a lot of other misc stuff). This fight consumes a lot of time, so please do not expect me to be fast on response. Also it seems that the Boost.DLL won't be ready for the nearest Boost release.

klemens-morgenstern commented 9 years ago

Well overloaded functions to work already (at least getting the mangled name) but you're right, the variant would be a good test. It is done by providing the type names as a template parameter, which would also give you the right pointer type. Additionally one can create an alias for a class that is not known in the loading program. This is also done via passing the type as a template parameter. There already is a test for this, you can find this at my fork of the project.

Also should I add these features to the shared_lib class or rather build a custom class for that, e.g. smart_lib? Because I could just wrap around shared_lib and the user could skip the whole reading of the library, to create the symbol-table.

As far as I can see, the only problem is, that MSVC is incapable of importing functions with function pointers as there return types, because msvc messes up the demangling of those. But using a std::function or boost::function can take care of this.

MSVC also writes the return type into the mangled name, which is kind of nice. If I don't implement an export utility there would be no check for that on other compilers. Also, GCC provides up to three constructors/destructors, while there is only one in MSVC. But I'll try to build a interface which takes care of that.

I tried to implement a name mangling for MSVC, this is utter torture. I didn't even get as far as you.

klemens-morgenstern commented 8 years ago

@apolukhin I stil have a few problems, to actually parse the type names correctly, especially if using variants. Currently I am using a boost::tokenizer version, which yielded ugly and non-functional code. Would you consider a small boost.spirit.x3 parser an overkill? This would still keep the library header-only and only included if used, but would add another dependency. The actual project I needed my extension for is on hold, but I still think it'd be useful.

apolukhin commented 8 years ago

Go on with Spirit! I'm OK with additional dependencies.

klemens-morgenstern commented 8 years ago

So I'll write a few thoughts here, just so you know what I might be looking into.

Allocating Constructor issue

As remarked in the code, currently a initialization with the allocating ctor fails somewhat, that is, i would remain zero in the following example:

struct X
{
    int i;
    X() : i(42) {};
};

I don't really know what's going on there, and what exactly C2 is doing. I'll look into that at some point...

Ctor/Dtor

The constructor and destructor objects could maybe also get invocations operators. I'll think of a design for that, since we'd need to distinguish the allocation and the other one. Could also be, that I need to invoke C2 before C1, so that could be useful.

Importing the type id

From what I've seen, you can actually import the typeinfo and the vtable, but that did (I think) not happen on linux. Considering that this might be possible on linux, we could than import it and overload type_index so that you can get the typeinfo that way. Of course, this could also be helped by just declared a pointer to the corresponding typeinfo to export it.

Exporting/Importing specialised templates

Now this would be more of a 'Let's see what happens' case, because it actually makes very little sense. But: let's say I specialize template_class in my dll but not in my host. I could then import the member functions, without even declaring an override class. Don't know if it there is any use case, but would be interesting to see.

apolukhin commented 8 years ago

I've put more thoughts and here are my ideas.

Library users will probably wish to use the mangled stuff for those cases: 1) load a mangled C++ function or variable 2) dynamically load classes from plugin 3) get info about the mangled symbols of the plugin

Relying on those use cases, following interfaces would be user friendly: For case 1): smart_library is already good for those who wish to do it on low level. However a simplified interface also required

// import_mangled.hpp //  Ooh, I'd love to have this feature! :)

// Close to dll::import<> but loads a mangled symbol, not an `extern "C"`
// Example:   auto f = import_mangled<int(std::string&&)>("plugin.so", "cpp11_function");
auto import_mangled<Signature>(smart_library, name);
auto import_mangled<Signature>(shared_library, name);

For case 2): This is the hardest part and a totally new feature. With current interface it is very simple to shoot your own leg and forget to call the destructor for the loaded class.

Here's a controversial draft:

// imported_class.hpp

// base class for all the aliases of imported classes. This must be implemented in Boost.DLL
template <class Derived>
class imported_class {
    void*                           impl_;  // pointer to allocated imported class
    something_like_smart_library    lib_;
    destructor                      d_;

protected:
    typedef imported_class<Derived> base_t;

    // Deleted default constructor will force user to correctly initilize this class. This class have no empty/unallocated state intentionally!
    imported_class() = delete;

    // User will need to define it's own move and copy constructor classes if he wishes to do so.
    imported_class(imported_class&) = delete;
    imported_class(imported_class&&) = delete;
    imported_class& operator=(imported_class&) = delete;
    imported_class& operator=(imported_class&&) = delete;

    template <class Signature, class... Args>
    imported_class(shared_library& lib, const char* class_name, identity<Signature>, Args&&... args)
        : impl_(nullptr)
        , lib_(lib, class_name, typeid(Derived)) // aliases `Derived` to `class_name`
        , d_(lib_.get_destructor())
    {
        impl_ = lib_.get_constructor<Signature>()   // getting allocating constructor
            (std::forward<Args>(args)...);          // calling allocating constructor
    }

    ~imported_class() {
        if (d_) {
            d_(impl_);
        }
    }

    // Function to call any member function
    template <class Signature, class... Args>
    auto call(const char* name, Args&&... args) const {
        // this won't compile, some casting and black magic required
        return impl_->*(lib_.get_mem_function<Signature>(name))
            (std::forward<Args>(args)...);
    }

    // Function to call any member function
    template <class Signature, class... Args>
    auto call(const char* name, Args&&... args) {
        return impl_->*(lib_.get_mem_function<Signature>(name))
            (std::forward<Args>(args)...);
    }

    // Must be also implemented here. Did not write them here for shortness
    void copy(Derived&);
    void move(Derived&&);
    void operator<(const Derived&) const;
    void operator==(const Derived&) const;
    void operator!=(const Derived&) const;
    void operator<=(const Derived&) const;
    void operator>=(const Derived&) const;
    explicit operator bool() const;
    // ...
};

// Usage example:

// `class Foo` is implemented by user, according to it's needs
class Foo: imported_class<Foo> {
public:
    Foo()
        : base_t("plugin.so", "my_namespace::some_class")
    {}

    explicit Foo(int i)
        : base_t("plugin.so", "my_namespace::some_class", identity<Foo(int)>, i)
    {}

    Foo(Foo&& f)
        : base_t("plugin.so", "my_namespace::some_class", identity<Foo(Foo&&)>, std::move(f))
    {}

    Foo& operator=(const Foo& other) {
        base_t::copy(other);
        return *this;
    }

    Foo& operator=(Foo&& other) {
        base_t::move(std::move(other));
        return *this;
    }

    void do_work(int i, short s) {
        base_t::call<void(int, const short&)>("do_some_work", i, s);
    }

    void do_another_work(std::string&& s) const {
        base_t::call<void(std::string&&)>("print_string", std::move(s));
    }

    using base_t::operator==;
    using base_t::operator bool;
    // Other operators are not supported by this particular implementation
};

// ...

Foo f;
Foo f1(0);
f.do_work(1,0);
f.do_another_work("Hello");
assert(f == f1);

// assert(f != f1); // Compilation error: `base_t::operator!=` is protected

// destructors will be called automatically

For case 3): I have no clear idea how to do it :) Probably some interface like the following must be added to library_info using mangled_storage:

// It's better to implement it in a separate header to not affect current dll::library_info
dll::experimental::library_info c("plugin.so");
dll::experimental::library_info::class_name l = c.classes()[0]; // class_name is just `struct class_name { std::string name; };` or `typedef std::string class_name;`
c.member_functions(l);
c.constructors(l);
c.variables(l);
c.member_classes(l);
c.is_virtual(l);

Unfortunately, I have no time right now to implement all those features. If you have time and will to do at least some of them - that would be really cool!

Dependencies, compilation times and pre C++14 compilers support do not really matter right now. We'll fix them later.

klemens-morgenstern commented 8 years ago

Case 1

Makes sense, though of course the first solution would be incredibly slow. It would rebuild the whole mangled_storage each time it's called. Also: it does not allow class overrides, because I would need to store them globally. So I'd think it should be restricted to smart_lib.

Would you also want some functions to import Ctor/Dtors?

Case 2

That would be a nice way to to that. It does however have one problem: not every compiler (i.e. not MSVC) does export allocating constructors. We would need to get the size of it, by exporting a global variable. That could be done by a macro or defining a static method returning the size insize the class. Also the type_info. Note: MSVC does NOT export the type_info as a symbol, unlike gcc (both on Win & LInux), but rather with a negative offset to the vtable (see here) I do however not know how consistent that is. But probably worth a try.

Also: if we take this approach, I would pack the loaded class into a unique_ptr and pass it a deleter, that is no void here. I.e. unique_ptr<class_buffer<Derived>>

Now if we have the size, I'd also like a stack-based solution. That is, I can import the class with a fixed size, and the smart_lib then asserts, that the class fits into the buffer.

I think something like the following would work, but I don't know if it's worth the effort.

struct my_class : dll::imported_class<my_class , size<42>>
{
    BOOST_DLL_IMPORT_MEMBER(value, int);//possible if a macro is provided for the exporting class.
    BOOST_DLL_IMPORT_STATIC_MEMBER(s_value, double);//maybe not the best idea...
    BOOST_DLL_IMPORT_CTOR(int, double);
    BOOST_DLL_IMPORT_METHOD(meth, void(int, std::string));
};

my_class mc(smart_lib, 42, 3.124);

mc.meth(-1, "Thingy");
mc.value = 0xDEADBEEF;

Case 3

I am rather sure, that aliases, like std::string cannot be exported.

On windows, I saw the vtable getting exported, so that issue might help us find virtual classes. Also we could find the typeinfo there, so deduce if a scope is type. I would need to look into the compilation on linux, see if it can be exported there. If not, we could only deduce something is a class if it has Constructors or Destructors. Because elsewise GCC might give you something like that:

scope::function(int, double)
scope::value

Doesn't really help. If we know scope to be a class, we still wouldn't know if function is static.

Since we can't really find out what's actually happening there, the only way to export this stuff is to provide macros to get the information.

Alternatively we can require the user to provide an additional file containing the information. I believe the .exp file of MSVC contains such information and we could use dump-class-hierachy with gcc.

I think the latter version is the only really doable. If you want to have more information, provide the class dump. We would then have a reader for that, which provides information without the dll-stuff. Also when reading the dump, we could get the member variables imported rather easily.

klemens-morgenstern commented 8 years ago

As for dump-class hierachy, it looks like this for the test class:

Vtable for some_space::some_class
some_space::some_class::_ZTVN10some_space10some_classE: 6u entries
0     (int (*)(...))0
4     (int (*)(...))(& _ZTIN10some_space10some_classE)
8     (int (*)(...))some_space::some_class::func
12    (int (*)(...))some_space::some_class::func
16    (int (*)(...))some_space::some_class::~some_class
20    (int (*)(...))some_space::some_class::~some_class

Class some_space::some_class
   size=8 align=4
   base size=8 base align=4
some_space::some_class (0x0x11987ac0) 0
    vptr=((& some_space::some_class::_ZTVN10some_space10some_classE) + 8u)
  some_space::some_father (0x0x119d5188) 0 empty

Where the VTable does actually contain the type-info, and we know where the vtable pointer is in some_class. However, mem_val is not contained there, so if we wanted to export members, we would need to do that via a macro.

klemens-morgenstern commented 8 years ago

Bug: this causes an error. For some reason the (BOOST_COMP_MSVC >= BOOST_VERSION_NUMBER(19,0,0)) does not work, which is why it didn't see it before. I'll include a fix in a future pull request.

The error is, that I thought the given version by calling cl( 19.00.23506) would be the one used here. It does however use the 14. So it was correct before I changed that.

klemens-morgenstern commented 8 years ago

I am really sry that my version-guard was wrong - but the current version of the function pointers in the ctor/dtor do not work.

What do you think of this for ctor/dtor:

template<typename Class, typename ...Args>
struct constructor<Class(Args...)> {
    typedef void(Class::*standard_t)(Args...);
    typedef Class*(*allocating_t)(Args...);

    //! The standard, i.e. not allocating constructor.
    standard_t standard;
    //! The allocating constructor.
    allocating_t allocating;

    //! Call the standard contructor
    void call_standard  (Class * const ptr, Args...args){ (ptr->*standard)(static_cast<Args>(args)...); }

    //! Call the deleting destructor
    Class * call_allocating(Args...args){ return (*allocating)(static_cast<Args>(args)...); }
};

The static_cast is used, because someone might use a lvalue-reference. Additionally we could provide operator() for convenience:

template<typename Class, typename ...Args>
struct constructor<Class(Args...)> {

    //! Call the standard contructor
    void operator()(Class * const ptr, Args...args){ (ptr->*standard)(static_cast<Args>(args)...); }

    //! Call the deleting destructor
    Class * operator()(Args...args){ return (*allocating)(static_cast<Args>(args)...); }
};

The destructor variant seems rather strange, but does actually make sense.

template <typename Class>
struct destructor {
    //! Call the standard contructor
    void operator()(Class & ptr){ (*standard)(&ptr); }

    //! Call the deleting destructor
    void operator()(Class * const ptr){ (*deleting)(ptr); }

This way the compiler-details could be hidden. Again, sry I broke the msvc test. And I really dislike it anyway, but we can't get around it, if we want a portable library.

apolukhin commented 8 years ago

I'm the last who changed the destructor, so it's probably my fault. Anyway, I've added Appveyor CI, so from now on test are run on each commit for Windows platform (you may do the same for your fork, just set in the Project configuration the "Custom configuration .yml file name" to test/appveyor.yml)

Case 1

Ok to restrict it to smart_lib.

Functions to import constructors and destructors won't be good here: import* functions were designed to provide a ready-to-use result. Just a constructor or just a destructor seems kind of useless, a lot of work remain to make them work together and to import member functions.

Case 2

Adding size variable and other additional modifications to exported class (plugin) would make user uncomfortable.

Consider two cases:

If we force the user to modify plugin and do a lot of work on import part of the code, then BOOST_DLL_ALIAS macro seems more useful because BOOST_DLL_ALIAS requires writing less code.

Let's stick to the idea that plugin sources must not be modified when user uses imported_class. Let's try to find a way to get size of a class somehow from binary. Maybe vtable contains some information?

Case 3

Hmmm. How about making the std::string demangle_symbol(const char *mangled_name); from include/boost/dll/detail/demangling/demangle_symbol.hpp available in boost::dll::experimental:: namespace?

In that case user will be able to demangle symbols and build it's own logic on top of it.

Some minor fixing required:

inline std::string demangle_symbol(const char *mangled_name) {
    if (*mangled_name == '_') {
        // because it start's with an underline _
        return boost::core::demangle(mangled_name); // returns mangled name on failure
    }

    // could not demangle
    return mangled_name;
}

template <class T>
inline void demangle_symbols(T& symbols) {
    for (auto& s: symbols) {
        s = demangle_symbol(s);
    }
}

User will be able to make own reflections:

library_info inf("plugin.so");
auto symbols = inf.symbols("my_sect"); // section were user keeps classes
experimental::demangle_symbols(symbols);

std::set<std::string> classes;
for (auto& s: symbols) {
    if (s.find("classes_namespace::") != std::string::npos) // namespace were user keeps classes
        classes.insert( s.substr(0, s.find("::")) );
}

What do you think of this for ctor/dtor:

Looks OK, try it out :). But it's better not to use operator() here. It makes easier to misuse the class and accidentally call allocating version instead of non-allocating (or vice versa).

klemens-morgenstern commented 8 years ago

Case 1

Ok, actually makes sense. But, what about this:

auto p = boost::dll::allocate<class_type(Args...)>(smart_lib, Args...);
// or with override
auto p = boost::dll::allocate<override_type(Args...)>("class_type", smart_lib, Args...);

boost::dll::construct<Args...>(smart_lib, class_&, Args...);

boost::dll::deallocate(smart_lib, class_*);
boost::dll::destruct(smart_lib, class_&); 

Would be easy enough to use I think.

Case 2

That would make sense, the class-size is just not existant in a mingw-dll. I tested this the following way: compile two versions of the same class with different sizes and binary diff both.

You actually can find the size in there as a debug-symbol, but that doesn't really help. The release versions where actually identical (except for some time stamps).

Also the allocating ctor only exists when used (checked that) so we actually have a problem.

So there is no way around an export of the size as a variable/static function.

Case 3

Currently we already have access to boost::core::demangle, but that could be pulled into boost::dll. This is already included since I use boost::typeindex.

Well, reading the symbols is actually possible with the current implementation, via the symbol storage. It returns the mangled and the demangled name for each symbol. That could be extended though.

But there is actually no need to provide a demangle_symbol function, because you can actually get every symbol of a library demangled, via library_information--> storage.

klemens-morgenstern commented 8 years ago

I am currently working on a fix - just so you know: a member-function pointer has two pointers, which means, hat you can't just cast them around as you like. Currently this breaks the ctor/dtor stuff for me. I had the same problem and asked on stackoverflow

edit: Funnier error: I accidently called the delete destructor on the stack, which caused the the second call of the dtor to crash. Very funny indeed.

klemens-morgenstern commented 8 years ago

Arg, I checked the ctor/dtor stuff again, it seems that gcc never creats an allocating ctor. See here

Though the deleting-dtor is optimized, we could just remove the concept altogether.

Additionally, for some reason the destructor fails for me with mingw. No idea what's going on there.

klemens-morgenstern commented 8 years ago

I have an Idea on how to export the size, which also allows the declaration in the header, It might work, but does not definitely

#define BOOST_DLL_EXPORT_SIZE(Class)                    \
BOOST_DLL_EXPORT inline std::size_t size_of(Class&); \
std::size_t sizeof(Class&) { return sizeof(Class);}

The inline makes the symbol weak so that it can be defined several times. It also doesn't matter where the symbol is defined, since we can search for that.

If we add that, we could load the size and then implement loaded classes.

apolukhin commented 8 years ago

I've tuned the shared_library::get() method, now it can import member function pointers https://github.com/apolukhin/Boost.DLL/commit/73315c86cfc31afe5ba1d5a2debdafe8ded74a66. Just call lib.get< void(Foo::*)(int, short) >("member_function")

Not sure that everything still works well on MSVC, waiting for tests to cycle.

SergeyVSB commented 8 years ago

MSVC demangler should be implemented via __unDName - because UndecorateSymbol Name lacks of new features, like rvalue references. However, unDname have some errors for 64 bit, you dhould test it.

Example usage: https://github.com/SergeyVSB/unname/blob/master/unname/unname.cpp

Function is undocumented internal feature of MSVCRT, desription taken from WINE sources

klemens-morgenstern commented 8 years ago

That sounds good, thank you. I actually had the problems with the rvalue references. I'll try to implement that and see if it works with the lvalue refs.

@apolukhin Do you think it'd be a problem to use an undocumented function like that?

Also: we could get rid of the DbgHelp altogether, since this function is in the c-runtime.

klemens-morgenstern commented 8 years ago

Alright, I put that in, it worked locally and got the rvalue references. That's clearly the better way, since it does not require DbgHelp. I'm still waiting for CI, which has a 64-Bit MSVC.

SergeyVSB commented 8 years ago

@klemens-morgenstern The bad side of this solution is that unDName is available only as MSVCRT internal. But, as I understand, you need only native (for current compiler) mangling scheme - "alien" scheme will be useless because of ABI and even API incompatibility. So, the summary: unDName is for MSVC and Clang at Windows (as described at http://clang.llvm.org/docs/MSVCCompatibility.html it uses MSVC mangling on Windows - but on other side it has abi::__cxa_demangle) abi::__cxa_demangle is for GCC and Mingw mangling


Right way is use boost::core::demangle, but boost::core::demangle is implemented via abi::__cxa_demangle when possible (Clang and gcc), but fallback to nothing in other cases - I think, it should be corrected to __unDName fot MSVC

SergeyVSB commented 8 years ago

I'm using both of them because my code is supplemental function for fileinfo/PEviewer plugins fot Total Commander, which show all exported functions from PE files. So, I'm compiling it by MSVC, which provides me unDName and use copy of abi::cxa_demangle from https://github.com/llvm-mirror/libcxx

klemens-morgenstern commented 8 years ago

Yes, we only need that for the MSVC-Compiler, so the msvcrt is completely sufficient.

The __unDName is not needed for boost::core::demangle since typeid().name() already returns the demangled name on windows. Thats way we put it in another function.

I implemented that successfully yesterday, but I can't test it on a 64-Bit compiler because boost.filesystem seems to be broken. Locally on my 32-Bit version it worked quite fine and got the rvalue-ref stuff. Thanks!

SergeyVSB commented 8 years ago

The __unDName is not needed for boost::core::demangle since typeid().name() already returns the demangled name on windows.

Hmm. As I see in https://github.com/boostorg/core/blob/develop/include/boost/core/demangle.hpp it is just

inline std::string demangle( char const * name )
{
    return name;
}

for cxxabi missing compilers. Also, as I see, design of core::demangle is so thay it can work with any mangled name, not only ones prodided by typeid - if it is so, it should be not char * const argument but const std::type_info& instead.

On other hand, it is documented just as you say. So, for free demangling any name at runtime (which can't be getted as std::type_info by typeid()) it's still needed to use CRT internals.

What about providing this facility as non Boost.DLL detail internal, but as public interface?

klemens-morgenstern commented 8 years ago

Well, you actually could've a mangled typename from another source, e.g. a debug-output. The boost::core::demangle is rather low-level and you can actually use it inderectly via boost.type_index.

The demangle_symbol function is actually available in the boost::dll::experimentalnamespace since this commit. But you can't expect a pull of #19 before the release of boost 1.61, so that'll still take a while. But if you want to play around with the stuff, feel free to checkout my fork.

apolukhin commented 8 years ago

I've found a few things to improve:

auto s = make_shared<smart_library>("libfoo.so");
auto f = import_mangled<int()>(s, "ns::foo");
s->unload();
f(); // segmentation fault, s is unloaded
auto size_f = lib.get_function<std::size_t()>("space::my_plugin::size"); //get the size function
auto size = (*size_f)();

Last line must be auto size = size_f();

// libexample.so contains:
// namespace bar {
//     std::string foo(std::string&& s); // no extern "C"!
// }
auto foo = import_mangled<std::string(std::string&&)>("libexample.so", "bar::foo")
klemens-morgenstern commented 8 years ago

Arrg, I missed that I don't not need to copy the smart_library, but only the shared_library; because I didn't want to copy the whole outline each time you import a function. Should I remove the shared_ptr altogether?

apolukhin commented 8 years ago

I've just removed mine :)

There've been import<> and import_alias<> with overloads that accept shared_ptr<shared_library>

klemens-morgenstern commented 8 years ago

Yep, that is why I implemented them. I'll remove them then.

klemens-morgenstern commented 8 years ago

Just out of curiosity: where is the actual reference counting implemented for the windows version? I seem to miss it.

apolukhin commented 8 years ago

Ref counting is implemented in include/boost/dll/detail/windows/shared_library_impl.hpp

winapi::LoadLibraryExW and winapi::FreeLibrary do all the refcounting (it's hidden inside OS kernel)

P.S.: I'm absolutely behind all the possible schedules, so I'll take a look at the Boost.Process later (much later). Great thanks for the pull requests for the Boost.DLL library!

apolukhin commented 8 years ago

I'll take a deeper look soon, but seems that the shared_ptr versions of import_mangled still exist :(

klemens-morgenstern commented 8 years ago

Oh, sry, I missed them. The import_class was rather complicated.