创建没有静态方法的c++DLL
Creating c++ DLL without static methods
我正在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。
您必须使用全局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
那些命名有趣的项目本质上与VisualStudio构建的C++项目之外的任何项目都不兼容。这被称为名称篡改,将一些类型信息嵌入到名称本身,作为解决我所说的导出函数限制的方法。从技术上讲,您可以在外部使用这些函数,但它很脆弱,并且依赖于编译器特定行为的细微差别。
在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#端编写另一个小包装,您就成功地封送了这个类。
特别是对于封送处理到C#,另一个选项是为类提供COM接口,而不是C接口。本质上是一样的,但有很多辅助类和特殊的编译器支持,可以直接向C++类添加COM支持,而无需编写单独的包装器。COM对象可以被C#直接引用。
不过,这对你的ios没有帮助。。。
顺便说一句,我几天前用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,则内存泄露是由库类引起的。
请注意,如果泄漏是由未分配的库的类引起的,那么这不一定是库的错误,因为用户仍然可以泄漏这些类,即使这意味着库中缺乏设计,因为为了安全起见,理想情况下,所有内容都应该以适当的智能指针返回。)
当然,您应该在文档中指定"GlobalReferenceCutner"是重复的,否则一些不知情的用户可能会认为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边界和用户应用程序中使用相同的值。因此,我们使用1个变量(可能GlobalCounter仍被编译为用户可执行文件,但没有人使用它来消除"克隆效应")
- 尝试了解在导入的静态方法上使用删除方法时的错误
- 如何通过命名空间调用非静态方法
- 如何在没有实例的情况下获取非静态方法的类型?
- 调用从模板派生的类的静态方法,而不指定模板
- 如何在 c++ 中异步调用静态方法?
- C++ 将静态方法转换为简单方法
- 从另一个标头中的标头调用静态方法
- C++中静态方法的局部变量范围
- 如何启用使用另一个类的静态方法的模板函数的自动推导,从而消除冗长的调用方代码
- 使用本机 JNI 静态方法实现C++ Java 运行时错误
- 当只有静态方法受到影响时,如何解决C++中的链接器错误?
- 创建一个C++DLL以从C#DLL调用方法
- 静态变量在同一个翻译单元中被静态方法使用时是否保证被初始化?
- 为什么链接器报告全局函数的乘法定义符号,而不是类静态方法
- 从部分专用模板方法调用模板非静态方法
- 如何使用 c++ 在 cocos2dx 中定义非静态方法
- 使用 CMake 的静态方法链接错误
- 在子类上调用模板化静态方法时获取类的类型名
- 创建没有静态方法的c++DLL
- Lua包装类-通过DLL向Lua公开c++静态方法