如何在C应用程序中使用我的C ++ DLL?

时间:2013-06-30 17:46:08

标签: c++ c visual-studio dll

环境是Windows,MS Visual Studio 2012.如何在C(非C ++)应用程序中使用用C ++编写的DLL库?在我的DLL中,我使用了名称空间,类等。例如,我写了two simple projects

  • DLL项目
  • EXE项目(使用DLL)

两者都是用C ++编写的。任何人都可以向我展示一个用C语言编写的使用C ++库的类似应用程序吗?

谢谢。

2 个答案:

答案 0 :(得分:18)

解决方法是在C ++中为您的DLL编写一个C接口(作为DLL的一部分,或作为单独的DLL),然后使用C代码中的C接口。

确保您的C接口包含全部为extern "C"的函数。

编辑:正如SigTerm在评论中指出的那样,制作函数stdcall并不是一个好主意(默认情况下它们不是,但有些人从WinAPI函数复制它们) ,因为在尝试使用其他语言的函数时会造成麻烦。

以下是挑战,以及如何解决这些挑战:

您的类位于命名空间

通常,只需声明一个不透明的struct ClassName*,就可以在C中导出C ++类。但是,对于命名空间中的类,这是不可能的,因为C不知道命名空间的概念。有几种方法可以解决它:

  • 简单的方法:只需将C接口分发并接受void指针,可能会被typedef伪装成实际类型。

    优势:实施琐碎。您只需要在每个入口点进行类型转换。

    缺点: C接口不是类型安全的。很容易将错误的指针传递给你的界面并搞砸了。

  • 侵入式方法:在C ++库中,为要公开的每个C ++类定义全局范围基类,并从中派生实际的类。请注意,全局范围基类可能为空,因为在使用它之前,无论如何都要static_cast到实际类型。

    优势:易于实施,类型安全。

    缺点:如果您有很多课程,那么很多工作。此外,如果您无法更改所有希望以此方式曝光的类,则可能无法实现。另外,作为基类,您不能将它们分开,以便只有C客户端需要它们。每个C ++客户端都将获得全局类,即使它没有包含C接口。

  • 使用句柄:使用整数句柄,它实际上包含转换为int的指针。如果您的编译器支持它,请使用intptr_t中的stdint.h,因为无论您使用什么平台,它都可以保证足够大。否则,最好使用long来保证安全。

    优势:易于实现,比void*更加类型安全,这是一个熟悉的界面风格,适用于曾经处理过低级功能(如操作系统文件处理)的任何C程序员。

    缺点:不是很安全;你仍然可以将错误类型的句柄传递给你的函数。

  • 使用"封装"句柄:对于要公开的每种类型,在全局范围内定义一个C结构,其中只包含void*或整数句柄的成员。

    优势:相对容易实现(虽然不像void*或原始句柄那么容易),类型安全(只要C代码不会弄乱结构成员)

    缺点:可能使C编译器难以优化。但这在很大程度上取决于C编译器。

我的建议将使用封装的句柄,除非结果出现性能问题,在这种情况下我会使用整数句柄。

类接口接受并返回std::string

C代码无法与std::string一起使用,您当然不希望将接口包装到C语言中(并且您的C接口用户会反正不喜欢你)。因此,必须在C接口中使用char* / char const*

将字符串传递到您的库

将字符串传递到您的库的情况很简单:您只需传递char const*并让std::string的构造函数完成剩下的工作。

从库中返回字符串

这是一个复杂的案例。您不能只返回c_str的结果,因为当临时std::string被破坏时,它将为您留下悬空指针。所以你基本上有以下选择:

  • 存储静态std::string(因此在返回时不会被释放),将函数调用的结果复制到其中,然后返回c_str)。

    优势:易于实施,保证为您提供完整的字符串。

    缺点: 非线程安全,用户也必须记住立即将内容复制到某处,否则下次调用该函数会使数据无效,甚至更糟的是指针。

  • 在包装函数中分配适当大小的字符数组,将字符串的内容复制到该数组中,并返回指向它的指针。

    优势:您可以保证返回完整的字符串(假设内存分配没有失败)。

    缺点:一个众所周知的问题是,释放另一个DLL中的内存比分配它的内存不能正常工作。因此,您必须提供一个释放接口,C接口的用户必须记住始终使用它而不是简单地释放内存。

  • 让您的用户传递一个字符数组和一个长度,并使用strncpy将字符串数据复制到字符数组。

    优势:您不必对字符串进行内存管理;这是用户的可信赖性。此外,如果用户希望将字符串写入特定位置,他只需将地址传递给包装器,而不是另外制作副本。

    缺点:如果用户提供的数组不长,您将获得截断的字符串。

我的建议:考虑到跨DLL内存分配的问题,我说要使用第三种方式并接受截断字符串的风险(但要确保你有办法向用户报告错误。

您的某个班级中有一名成员enum

这很简单:在C接口上提供相同的(名称除外)全局范围枚举定义。由于相同的enum definitios为标识符提供相同的值,因此只需将类内枚举中的值转换为全局值并返回该值即可完美运行。唯一的问题是,无论何时更新类内枚举,您都必须记住还要更新类外枚举。

您有类型为std :: vector

的公共成员

嗯,首先,无论如何,这是一个糟糕的界面决定。处理它的方法基本上和你实际应该在C ++中处理这些向量一样:为它们提供一个迭代器接口。

现在C ++迭代器在C中不能很好地工作,但是在C中也没有理由坚持使用C ++迭代器样式。

鉴于接口成员是std::vector,另一个选择是给出指向vector的第一个元素的指针。但是,下次调整矢量大小时,这些将会失效,因此只有非常有限的情况才会有选项。

您有一个print函数,它接受std::ostream和对象的引用。

当然{C}代码无法使用std::ostream。在C中,使用FILE*访问文件。我认为这里最好的选择是使用一个包含FILE*的iostream类,并允许你从一个构造。我不知道这是否是由MSVC ++提供的,但如果没有,Boost就有AFAIK这样的流。

然后你的打印包装器将获取一个FILE*和一个对象句柄(或无效指针,或者你选择用于在C中表示对象的任何选项),从FILE指针构造一个临时的ostream对象,提取指针从句柄到对象,然后在指向对象上调用print

其他考虑因素:例外

不要忘记捕获异常并将其转换为C可以处理的错误。最好的选择通常是返回错误代码。

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)

答案 1 :(得分:2)

您必须创建C包装函数,它可以将不透明指针传递给您的类实例 这似乎是一个合理的例子: http://www.daniweb.com/software-development/cpp/threads/355990/strategy-for-consuming-c-dlls-in-c

从链接引用:

typedef Foo* FOO_HDL;
extern "C" { 
__declspec(dllexport) FOO_HDL FooCreate() {
  return new Foo();
};
__declspec(dllexport) void FooDestroy(FOO_HDL obj) {
  delete obj;
};
__declspec(dllexport) double FooGetValue(FOO_HDL obj) {
  return obj->GetValue();
};
__declspec(dllexport) void FooSetValue(FOO_HDL obj, double NewValue) {
  obj->SetValue(NewValue);
};
};