Creating c++ DLL without static methods

2019-01-15 22:34发布

问题:

I am creating a DLL in C++. Here is an example:

namespace MathFuncs
{
  class MyMathFuncs
  {
    public:
        // Returns a + b
        static __declspec(dllexport) double Add(double a, double b);

        // Returns a - b
        static __declspec(dllexport) double Subtract(double a, double b);

        // Returns a * b
        static __declspec(dllexport) double Multiply(double a, double b);

        // Returns a / b
        // Throws DivideByZeroException if b is 0
        static __declspec(dllexport) double Divide(double a, double b);
    };
}

All methods are static and there are lot of limitations with static methods so my question is how can I implement the same without static methods? Do I always need to have static methods in DLL? I want to import this DLL in C# and IOS apps.

回答1:

As side note I did 1 experiment few days ago with mingw/c++ wich can be clarifying for you.

I had a Global reference counter for find out memory leaks in my program,

class ReferenceCounter /** other implementations details are omitted.*/
{
public:

static int GlobalReferenceCounter;

//version 1
static int getReferenceCount1() { return GlobalReferenceCounter;}

//verison 2
static int getReferenceCount2(); //same code of version 1 but moved into .cpp file
};

When compiling my library using reference counter into a DLL, then the variable is duplicated, 1 version is compiled into the DLL, and one version is compiled in client code.

When I ask istances of reference counted stuff from the factory methods of the DLL, only the reference counter inside the DLL is increased/reduced. When client code is using its own classes inherited from Ref Counter, then the client reference counter is increased/reduced.

So for check for memory leaks I must do at end of the program

assert(ReferenceCounter.getReferenceCount1() == 0);
assert(ReferenceCoutner.getReferenceCount2() == 0);

that's because in case of memory leak one of those values will be greater than 0. If first value greater than 1, then the memory leak is caused by unallocated user classes, if second value is greater than 0, then memory leak is caused by library classes.

Note that if leak is caused by unallocated library's classes, this is not necessarily a bug of the library, since user is still able to leak that classes, even if that should mean a lack of design in the library, since ideally everythin should be returned in proper smart pointers for safety.)

Of course you should specify that "GlobalReferenceCoutner" is duplicated in the documentation, else some unaware user can just think that 2 getters are redundant and will think you did some mistake. (if possible avoid to do something like that, is obscure and unclear)

That should also warn you that accessing static method through DLLs is highly unsafe. For example If In my class I wanted to have only 1 reference counter instead of 2 I had to do that for grant safety:

class ReferenceCounter
{
public:

static int GlobalReferenceCounter;

static void duplicate() { increaseGlobalCounter(); }

static void release() { decreaseGlobalCounter(); }

static int getGlobalCounter() { return privateGetGlobalCounter(); }

private:

static increaseGlobalCounter(); // implementation into Cpp file

static decreaseGlobalCounter(); // implementation into Cpp file

static privateGetGlobalCounter(); // implementation into Cpp file

};

doing that will grant you that the same value is used across DLL bounduaries and in User application. so instead of have 2 different variables here we use 1 variable (probably the GlobalCounter is still compiled into user executable, but no one is using it removing the "clone effect")



回答2:

You must use global, C-style methods. The reason for this is described here.

Basically it comes down to this: C functions translate well into DLL exports because C is "closer to the ground" in terms of language features. C translates more directly into machine code. C++ does a lot at the compiler level, giving you a lot of features that can't be used outside of a C++ environment. For this reason, your exported functions should follow a C style in order to function properly across DLL boundaries. That means no templates, no inline code, no non-POD classes or structs.

Consider this code:

extern "C"
{
    __declspec(dllexport) int GlobalFunc(int n)
    {
        return n;
    }

    namespace SomeNamespace
    {
        __declspec(dllexport) int NamespaceFunction(int n)
        {
            return n;
        }
    }

    class MyClass
    {
        __declspec(dllexport) int ClassNonStatic(int n)
        {
            return n;
        }
        __declspec(dllexport) static int ClassStatic(int n)
        {
            return n;
        }
    };

}

This results in the following DLL exported function names:

?ClassNonStatic@MyClass@@AAEHH@Z

?ClassStatic@MyClass@@CAHH@Z

GlobalFunc

NamespaceFunction

The ones with the funny naming are essentially incompatible with anything other than Visual Studio-built C++ projects. This is called name mangling, embedding some type info into the name itself as a workaround to the limitations of exported functions I'm talking about. You can technically use these functions externally, but it's fragile and relies on nuances of compiler-specific behavior.

The rule of thumb for exporting functions in a DLL is: Can you do this in C? If you can't, then it's almost certain you're going to cause problems.

Note here that even static class methods (which are essentially global) still have name mangling, even with extern "C". But free-standing functions in a namespace export without name mangling (though they lose the namespace scope).

You can begin to see why this rule of thumb makes sense.


If you want to export a class, let's follow the rule of thumb, and design the DLL interface as you would do in C. Here's an example. Let's take this C++ class:

    class Employee
    {
    private:
        std::string firstName;
        std::string lastName;

    public:
        void SetFirstName(std::string& s)
        {
            this->firstName = s;
        }
        void SetLastName(std::string& s)
        {
            this->lastName = s;
        }
        std::string GetFullName()
        {
            return this->firstName + " " + this->lastName;
        }
    };

You cannot just stick __declspec(dllexport) on this. You must provide a C interface for it, and export that. Like this:

    extern "C"
    {
        __declspec(dllexport) Employee* employee_Construct()
        {
            return new Employee();
        }

        __declspec(dllexport) void employee_Free(Employee* e)
        {
            delete e;
        }

        __declspec(dllexport) void employee_SetFirstName(Employee* e, char* s)
        {
            e->SetFirstName(std::string(s));
        }

        __declspec(dllexport) void employee_SetLastName(Employee* e, char* s)
        {
            e->SetLastName(std::string(s));
        }

        __declspec(dllexport) int employee_GetFullName(Employee* e, char* buffer, int bufferLen)
        {
            std::string fullName = e->GetFullName();
            if(buffer != 0)
                strncpy(buffer, fullName.c_str(), bufferLen);
            return fullName.length();
        }
    }

Then write another small wrapper on the C# side, and you have successfully marshaled this class.

Specifically for marshaling to C#, another option is to provide a COM interface to your class instead of a C interface. Essentially it's the same thing, but there are a lot of helper classes and special compiler support for adding COM support directly to C++ classes without writing separate wrappers. COM objects can be referenced directly by C#.

That won't help you with ios though...



标签: c# c++ ios dll