And*_*man 7 c c++ dll visual-studio
环境是Windows,MS Visual Studio 2012.如何在C(而不是C++)应用程序中使用我用C++编写的DLL库?在我的DLL中,我使用了名称空间,类等.例如,我写了两个简单的项目:
两者都是用C++编写的.任何人都可以向我展示一个用C编写的类似应用程序,它使用C++库吗?
谢谢.
cel*_*chk 18
解决方案是使用C++(作为DLL的一部分或作为单独的DLL)为您的DLL编写C接口,然后使用C代码中的C接口.
确保您的C接口包含所有功能extern "C".
编辑:正如SigTerm在评论中指出的那样,制作这些函数并不是一个好主意stdcall(它们不是默认的,但有些人会从WinAPI函数中复制它),因为这会在尝试使用其他语言的函数时造成麻烦.
以下是挑战,以及如何解决这些挑战:
通常,只需声明一个opaque,就可以在C中导出C++类struct ClassName*.但是,对于命名空间中的类,这是不可能的,因为C不知道命名空间的概念.有几种方法可以解决它:
简单的方法:只需将您的C接口分发并接受void指针,可能会被typedef伪装成实际类型.
优势:实施琐碎.您只需要在每个入口点进行类型转换.
缺点: C接口不是类型安全的.很容易将错误的指针传递给您的界面并搞砸了.
侵入方式:在C++库中,为要公开的每个C++类定义全局范围基类,并从中派生实际类.请注意,全局范围基类可能为空,因为static_cast在使用它之前,无论如何都要使用实际类型.
优点:易于实施,类型安全.
缺点:如果你有很多课程,很多工作.此外,如果您无法更改所有希望以此方式公开的类,则可能无法实现.此外,作为基类,您不能将它们分开,以便只有C客户端需要它们.每个C++客户端都将获得全局类,即使它没有包含C接口.
使用句柄:使用整数句柄,它实际上包含转换为int的指针.如果您的编译器支持它,请使用intptr_tfrom,stdint.h因为无论您使用什么平台,都可以保证足够大.否则,最好使用a long来保证安全.
优点:易于实现,比类型更安全,比void*任何处理过OS文件处理等低级功能的C程序员熟悉的界面风格.
缺点:不是很安全; 你仍然可以将错误类型的句柄传递给你的函数.
使用"封装"句柄:对于要公开的每种类型,在全局范围内定义一个C结构,该结构仅包含成员void*或整数句柄.
优点:相对容易实现(虽然不像void*原始句柄那样容易),类型安全(只要C代码不会弄乱struct成员).
缺点:可能使C编译器难以优化.但这在很大程度上取决于C编译器.
我的建议是使用封装的句柄,除非事实证明存在性能问题,在这种情况下我会使用整数句柄.
std::stringC代码无法使用std::string,你当然不希望将接口包装到C中(并且你的C接口用户不会喜欢你).因此,必须在C接口中使用char*/ char const*.
将字符串传递给库的情况很简单:只需传入一个char const*in,然后让构造函数std::string完成剩下的工作.
这是一个复杂的案例.你不能只返回结果,c_str因为它会在临时std::string被破坏时给你一个悬空指针.所以你基本上有以下选择:
存储静态std::string(因此在返回时不会释放),将函数调用的结果复制到该函数中,然后返回c_str).
优点:易于实现,保证为您提供完整的字符串.
缺点:它不是线程安全的,用户也必须记住立即将内容复制到某个地方,否则下一次调用该函数将使数据无效,甚至更糟的是指针.
在包装器函数中分配适当大小的字符数组,将字符串的内容复制到该数组中,并返回指向它的指针.
优点:保证返回完整的字符串(假设内存分配没有失败).
缺点:一个众所周知的问题是,在另一个DLL中释放内存而不是分配它的内存不能正常工作.因此,您必须提供一个释放接口,C接口的用户必须记住始终使用它而不是简单地释放内存.
让您的用户传递字符数组和长度,并使用strncpy将字符串数据复制到字符数组.
优点:您不必对字符串进行内存管理; 这是用户的可靠性.此外,如果用户希望将字符串写入特定位置,他可以简单地将地址传递给包装器而不是另外制作副本.
缺点:如果用户提供的数组不长,你会得到一个截断的字符串.
我的建议:鉴于跨DLL内存分配的问题,我会说使用第三种方式并接受截断字符串的风险(但请确保您有办法向用户报告错误).
enum班级中有一名成员这很简单:在C接口上提供相同的(名称除外)全局范围枚举定义.由于相同的enumdefinitios为标识符提供了相同的值,因此只需将类内枚举中的值转换为全局值并返回该值即可.唯一的问题是,无论何时更新类内枚举,您都必须记住还要更新类外枚举.
好吧,首先,这是一个糟糕的界面决定.处理它的方法与你在C++中实际应该处理这些向量的方式基本相同:为它们提供迭代器接口.
现在C++迭代器在C中不能很好地工作,但是在C中也没有理由遵循C++迭代器样式.
鉴于接口成员是std::vector,另一个选择是给出指向向量的第一个元素的指针.但是,下次调整矢量大小时,这些将会失效,因此这只适用于非常有限的情况.
print函数,它接受std::ostream一个对象的引用.当然std::ostream不适用于C代码.在C中,使用访问文件FILE*.我认为这里最好的选择是使用一个包含a的iostream类,FILE*并允许你从一个构造.我不知道这是否是由MSVC++提供的,但如果没有,Boost就有AFAIK这样的流.
然后,您的打印包装器将获取一个FILE*和一个对象句柄(或无效指针,或您选择用于在C中表示对象的任何选项),从FILE指针构造一个临时ostream对象,从句柄中提取指向该对象的指针,以及然后调用print指向的对象.
不要忘记捕获异常并将其转换为C可以处理的错误.最好的选择通常是返回错误代码.
以下是链接示例库的可能C接口标头
/* somelib_c_interface.h */
#ifndef SOMELIB_C_INTERFACE_H
#define SOMELIB_C_INTERFACE_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <stdlib.h>
/* typedef for C convenience */
typedef struct
{
intptr_t handle;
} some_namespace_info;
typedef struct
{
intptr_t handle;
} some_namespace_employee;
/* error codes for C interface */
typedef int errcode;
#define E_SOMELIB_OK 0 /* no error occurred */
#define E_SOMELIB_TRUNCATE -1 /* a string was created */
#define E_SOMELIB_OUT_OF_MEMORY -2 /* some operation was aborted due to lack of memory */
#define E_SOMELIB_UNKNOWN -100 /* an unknown error occurred */
/* construct an info object (new)
errcode some_namespace_info_new(some_namespace_info* info, char const* name, char const* number);
/* get rid of an info object (delete) */
errcode some_namespace_info_delete(some_namespace_info info);
/* Some_namespace::Info member functions */
errcode some_namespace_info_get_name(some_namespace_info info, char* buffer, size_t length);
errcode some_namespace_info_set_name(some_namespace_info info, char const* name);
errcode some_namespace_info_get_number(some_namespace_info info, char* buffer, size_t length);
errcode some_namespace_info_set_number(some_namespace_info info, char const* name);
/* the employee class */
/* replicated enum from employee */
enum some_namespace_employee_sex { male, female };
errcode some_namespace_employee_new(some_namespace_employee* employee,
char const* name, char const* surname, int age,
some_namespace_employee_sex sex);
errcode some_namespace_employee_delete(some_namespace_employee employee);
errcode some_namespace_employee_get_name(some_namespace_employee employee, char* buffer, size_t length);
errcode some_namespace_employee_set_name(some_namespace_employee employee, char const* name);
errcode some_namespace_employee_get_surname(some_namespace_employee employee, char* buffer, size_t length);
errcode some_namespace_employee_set_surname(some_namespace_employee employee, char const* name);
/* since ages cannot be negative, we can use an int return here
and define negative results as error codes, positive results as valid ages
(for consistency reason, it would probably be a better idea to not do this here,
but I just want to show another option which sometimes is useful */
int some_namespace_employee_get_age(some_namespace_employee employee);
errcode some_namespace_employee_set_age(some_namespace_employee employee, int age);
errcode some_namespace_employee_get_set(some_namespace_employee employee,
enum some_namespace_employee_sex* sex);
errcode some_namespace_employee_set_sex(some_namespace_employee employee,
enum some_namespace_employee_sex sex);
typedef struct
{
intptr_t handle;
} info_iter;
info_iter_delete(info_iter iterator);
some_namespace_info info_iter_get(info_iter iterator);
void info_iter_next(info_iter iterator);
bool info_iter_finished(info_iter iterator);
info_iter some_namespace_employee_phones(some_namespace_employee employee);
info_iter some_namespace_employee_emails(some_namespace_employee employee);
info_iter some_namespace_employee_sites(some_namespace_employee employee);
errcode some_namespace_print(FILE* dest, some_namespace_employee employee);
#ifdef __cplusplus
}
#endif
#endif // defined(SOMELIB_C_INTERFACE_H)
Run Code Online (Sandbox Code Playgroud)