不使用静态方法创建c ++ DLL

时间:2012-09-07 08:03:23

标签: c# c++ ios dll

我正在用C ++创建一个DLL。这是一个例子:

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);
    };
}

所有方法都是静态的,静态方法有很多限制,所以我的问题是如何在没有静态方法的情况下实现相同的方法?我总是需要在DLL中使用静态方法吗?我想在C#和IOS应用程序中导入此DLL。

2 个答案:

答案 0 :(得分:5)

您必须使用全局C风格的方法。原因是described here

基本上它归结为:C函数很好地转换为DLL导出,因为C在语言特征方面“更接近地面”。 C可以更直接地转换为机器代码。 C ++在编译器级别上做了很多工作,为您提供了许多在C ++环境之外无法使用的功能。因此,导出的函数应遵循C样式,以便跨DLL边界正常运行。这意味着没有模板,没有内联代码,没有非POD类或结构。

考虑以下代码:

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;
        }
    };

}

这导致以下DLL导出的函数名称:

  

?ClassNonStatic @ @@ MyClass的AAEHH @ Z

     

?ClassStatic @ @@ MyClass的CAHH @ Z

     

GlobalFunc

     

NamespaceFunction

具有有趣命名的那些与Visual Studio构建的C ++项目之外的任何内容基本上都不兼容。这称为name mangling,将一些类型信息嵌入到名称本身中,作为我正在谈论的导出函数限制的解决方法。从技术上讲,您可以在外部使用这些函数,但它很脆弱,并且依赖于编译器特定行为的细微差别。

在DLL中导出函数的经验法则是:你能在C中执行此操作吗?如果你不能,那么几乎可以肯定你会导致问题。

请注意,即使使用extern "C",即使是静态类方法(基本上是全局的)仍然会出现名称错误。但是名称空间导出中的独立函数没有名称错误(尽管它们失去了命名空间范围)。

你可以开始明白为什么这个经验法则有意义。


如果你想导出一个类,让我们按照经验法则,像在C中那样设计DLL接口。这是一个例子。我们来看看这个C ++类:

    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;
        }
    };

你不能只坚持__declspec(dllexport)。您必须为它提供C接口,然后将其导出。像这样:

    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();
        }
    }

然后在C#端写另一个小包装器,你已成功marshaled这个类。

特别是对于C#的编组,另一个选择是为您的类而不是C接口提供COM接口。本质上它是一样的,但有很多辅助类和特殊的编译器支持直接向C ++类添加COM支持而无需编写单独的包装器。 COM对象可以由C#直接引用。

虽然这对ios没有帮助...

答案 1 :(得分:1)

作为旁注,我几天前用mingw / c ++进行了一次实验,可以为你澄清。

我有一个全局参考计数器,用于查找程序中的内存泄漏,

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
};

使用引用计数器将我的库编译成DLL时,变量是重复的,1个版本被编译到DLL中,一个版本在客户端代码中编译。

当我从DLL的工厂方法询问引用计数的东西时,只增加/减少DLL内部的引用计数器。当客户端代码使用自己继承自Ref Counter的类时,则会增加/减少客户端引用计数器。

因此,为了检查内存泄漏,我必须在程序结束时进行

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

这是因为在内存泄漏的情况下,其中一个值将大于0.如果第一个值大于1,则内存泄漏是由未分配的用户类引起的,如果第二个值大于0,则内存泄漏是由图书馆课程引起的。

请注意,如果泄漏是由未分配的库的类引起的,那么这不一定是库的错误,因为用户仍然能够泄漏该类,即使这应该意味着库中缺少设计,因为理想情况下每个都是应该用适当的智能指针返回以确保安全。)

当然你应该指定“GlobalReferenceCoutner”在文档中重复,否则一些不知情的用户可以认为2个getter是多余的并且会认为你做错了。 (如果可能的话,避免做那样的事情,模糊不清)

这也应该警告你,通过DLL访问静态方法是非常不安全的。例如,如果在我的班级中我想只有1个参考计数器而不是2个我必须这样做才能获得安全性:

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

};

这样做会授予您在DLL边界和User应用程序中使用相同的值。所以我们不是在这里使用2个不同的变量,而是使用1个变量(可能GlobalCounter仍然被编译成用户可执行文件,但是没有人使用它来删除“克隆效果”)

相关问题