无静态方法创buildc + + DLL

我正在C ++中创build一个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。

正如我注意到的,前几天我用了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中时,variables被复制,1个版本被编译到DLL中,一个版本被编译在客户端代码中。

当我从DLL的工厂方法请求引用计数的东西时,只有DLL内的引用计数器被增加/减less。 当客户代码使用从Ref Counterinheritance的自己的类时,客户端引用计数器会增加/减less。

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

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

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

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

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

这也应该警告你通过DLL访问静态方法是非常不安全的。 例如,如果在我的class级,我想只有一个参考计数器而不是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个不同的variables,而是使用1个variables(可能GlobalCounter仍被编译成用户可执行文件,但没有人使用它去除“克隆效应”)

你必须使用全局的C风格的方法。 这里描述的原因。

基本上归结到这一点: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构build的C ++项目不兼容。 这就是所谓的名称改造,将一些types信息embedded到名称本身中,作为我正在讨论的导出函数的局限性的解决方法。 您可以在技术上从外部使用这些函数,但它很脆弱,并且依赖于编译器特定行为的细微差别。

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

注意,甚至静态类方法(实质上是全局的)仍然有名称改变,即使使用extern "C" 。 但是,名称空间导出中的独立函数没有名称变形(尽pipe它们丢失了名称空间范围)。

你可以开始明白为什么这个经验法则是有道理的。


如果你想导出一个类,让我们按照经验法则,像在C中一样deviseDLL接口。下面是一个例子。 我们来看看这个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#端编写另一个小包装器,并且已经成功编组了这个类。

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

这不会帮助你与ios虽然…