Search code examples
c++winapidllc++17c++14

Generate wrapper to any dll with help of macros/templates


For example I have name1.dll with methods:

  • void func1(int)
  • int func2(bool, char)

and name2.dll with methods:

  • std::string func1()
  • bool func2(int, int, int)

To work with them, I want to get some generated classes and their objects, respectively, using the following syntax

DECLARE_WRAPPER(
    Wrapper1,
    DECLARE_METHOD(func1, void(int))
    DECLARE_METHOD(func2, int(bool, char))
)

And

DECLARE_WRAPPER(
    Wrapper2,
    DECLARE_METHOD(func1, std::string())
    DECLARE_METHOD(func2, bool(int, int, int))
)

and use like this:

Wrapper1 w1("name1.dll");
w1 func1(6);
w1.func2(false, 'c');

Wrapper w2("name2.dll");
w2.func1();
w2.func2(6, 5, 7)

My C++ code so far for this purpose following:

#include <iostream>
#include <string>
#include <windows.h>

#define DECLARE_METHOD(name, ret, ...) \
    ret name(__VA_ARGS__) { \
        FARPROC proc = GetProcAddress(m_hDll, #name); \
        if (proc == nullptr) { \
            throw std::runtime_error("Function not found"); \
        } \
        va_list args; \
        va_start(args, __VA_ARGS__); \
        ret result = reinterpret_cast<ret(__cdecl*)(__VA_ARGS__)>(proc)(__VA_ARGS__); \
        va_end(args); \
        return result; \
    }

#define DECLARE_WRAPPER(wrapperName, ...) \
    class wrapperName { \
    public: \
        wrapperName(const std::string& dllPath) : m_hDll(nullptr) { \
            m_hDll = LoadLibraryA(dllPath.c_str()); \
        } \
        ~wrapperName() { \
            if (m_hDll) { \
                FreeLibrary(m_hDll); \
            } \
        } \
        __VA_ARGS__ \
    private: \
        HMODULE m_hDll; \
    };

DECLARE_WRAPPER(
Wrapper1,
DECLARE_METHOD(func1, void, int)
DECLARE_METHOD(func2, int, bool, char)
);

DECLARE_WRAPPER(
Wrapper2,
DECLARE_METHOD(func1, std::string)
DECLARE_METHOD(func2, bool, int, int, int)
);

int main() {
    const std::string dllPath1 = "name1.dll";
    const std::string dllPath2 = "name2.dll";

    Wrapper1 w1(dllPath1);
    w1.func1(6);
    int res = w1.func2(false, 'c');
    std::cout << "Wrapper1::func2 returned " << res << std::endl;

    Wrapper2 w2(dllPath2);
    std::string str = w2.func1();
    std::cout << "Wrapper2::func1 returned " << str << std::endl;
    bool b = w2.func2(6, 5, 7);
    std::cout << "Wrapper2::func2 returned " << b << std::endl;

    return 0;
}

But I have errors, and I can't make it work. How can this code be fixed? Or, am I trying to achieve something that can't be done in C++? Current version I can use is C++17.


Solution

  • I don't think you can use DECLARE_METHOD() inside of DECLARE_WRAPPER() the way you are doing. You will likely have to break up DECLARE_WRAPPER() into separate macros so you can then use DECLARE_METHOD()s in between them.

    However, DECLARE_METHOD() itself will certainly not work the way you have written it. It canont use __VA_ARGS__ to both declare the method parameters and to call the loaded proc(). And, it cannot pass __VA_ARGS__ to va_start() at all (not that it matters since you are not using args anyway). You should use C++-style variadic templates instead of C-style variadic macros.

    Try something more like this:

    #define DECLARE_METHOD(name, returnType) \
        template<typename... Args> \
        returnType name(Args&&... args) \
        { \
            using procType = returnType (__cdecl *)(Args...); \
            procType proc = reinterpret_cast<procType>(GetProcAddress(m_hDll, #name)); \
            if (proc == nullptr) { \
                throw std::runtime_error("Function not found"); \
            } \
            return proc(std::forward<Args>(args)...); \
        }
    
    #define DECLARE_WRAPPER_BEGIN(wrapperName) \
        class wrapperName { \
        public: \
            wrapperName(const std::string& dllPath) : m_hDll(nullptr) { \
                m_hDll = LoadLibraryA(dllPath.c_str()); \
                if (m_hDll == nullptr) \
                    throw std::runtime_error("DLL not found"); \
            } \
            ~wrapperName() { \
                FreeLibrary(m_hDll); \
            }
    
    #define DECLARE_WRAPPER_END() \
        private: \
            HMODULE m_hDll; \
        };
    
    DECLARE_WRAPPER_BEGIN(Wrapper1)
    DECLARE_METHOD(func1, void)
    DECLARE_METHOD(func2, int)
    DECLARE_WRAPPER_END()
    
    DECLARE_WRAPPER_BEGIN(Wrapper2)
    DECLARE_METHOD(func1, std::string)
    DECLARE_METHOD(func2, bool)
    DECLARE_WRAPPER_END()
    

    Online Demo


    On a side note, it is not safe for Wrapper2::func1() to pass a std::string across the DLL boundary. If the DLL function returns a char* that you want converted into a std::string, then your DECLARE_METHOD() macro will have to account for that, by having separate parameters to specify the two return types individually. For example:

    #define DECLARE_METHOD(name, dllReturnType, methReturnType) \
        template<typename... Args> \
        methReturnType name(Args&&... args) \
        { \
            using procType = dllReturnType (__cdecl *)(Args...); \
            procType proc = reinterpret_cast<procType>(GetProcAddress(m_hDll, #name)); \
            if (proc == nullptr) { \
                throw std::runtime_error("Function not found"); \
            } \
            return proc(std::forward<Args>(args)...); \
        }
    }
    
    DECLARE_WRAPPER_BEGIN(Wrapper1)
    DECLARE_METHOD(func1, void, void)
    DECLARE_METHOD(func2, int, int)
    DECLARE_WRAPPER_END()
    
    DECLARE_WRAPPER_BEGIN(Wrapper2)
    DECLARE_METHOD(func1, char*, std::string)
    DECLARE_METHOD(func2, bool, bool)
    DECLARE_WRAPPER_END()