c++ - 如何在C应用程序中使用C++ DLL?

标签 c++ c visual-studio dll

环境是Windows,MS Visual Studio2012。如何在C(不是C++)应用程序中使用以C++编写的DLL库?在我的DLL中,我使用了 namespace ,类等。例如,我写了two simple projects:

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

  • 两者都是用C++编写的。有人可以向我展示使用C++库的用C编写的类似应用程序吗?

    谢谢。

    最佳答案

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

    确保您的C接口(interface)包含都是extern "C"的函数。

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

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

    您的类(class)在 namespace 中

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

  • 简单方法:只需让您的C接口(interface)分发并接受void指针,也许可以通过typedef伪装成实际的类型。

    优势:易于实现。您所需要做的就是在每个入口点进行类型转换。

    缺点: C接口(interface)将不是安全的类型。很容易将错误的指针传递给您的界面并使事情困惑。
  • 侵入性方式:在您的C++库中,为要公开的每个C++类定义全局范围基类,并从中派生实际的类。请注意,全局范围基类可能为空,因为在使用它之前,您始终会将static_cast更改为实际类型。

    优点:易于实现,类型安全。

    缺点:如果您有很多类(class),则需要很多工作。另外,如果您无法更改希望以这种方式顶部显示的所有类,则可能无法实现。另外,作为基类,您不能将它们分开,以便只有C客户端需要它们。每个C++客户端都将获取全局类,即使它不包含C接口(interface)也是如此。
  • 使用句柄:使用整数句柄,它实际上包含转换为int的指针。如果您的编译器支持,请使用intptr_t中的stdint.h,因为无论您在什么平台上,它都保证足够大。否则,为了安全起见,最好使用long

    优势:易于实现,类型安全性比void*略强,它是任何曾经处理过OS文件处理等底层功能的C程序员所熟悉的界面风格。

    缺点:不是很安全的类型;您仍然可以将错误类型的句柄传递给函数。
  • 使用“封装的”句柄:对于要公开的每种类型,请在全局范围内定义一个仅包含void*或整数句柄作为成员的C结构。

    优点:相对容易实现(尽管不如void*或raw句柄那么容易),类型安全(只要C代码不会与struct成员混淆)。

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

  • 我的建议应该使用封装的句柄,除非事实证明存在性能问题,在这种情况下,我将使用整数句柄。

    类接口(interface)接受并返回std::string
    C代码无法与std::string一起使用,并且您当然不想做将接口(interface)包装到C中的工作(您的C接口(interface)用户无论如何都不希望您这样做)。因此,必须在C接口(interface)中使用char*/char const*

    将字符串传递到您的库

    将字符串传递到库的情况很简单:您只需传递char const*,然后让std::string的构造函数完成其余工作即可。

    从库中返回字符串

    这是复杂的情况。您不能只返回c_str的结果,因为当临时std::string被破坏时,它将留下一个悬空的指针。因此,您基本上有以下选择:
  • 存储一个静态的std::string(这样它就不会在返回时被释放),将函数调用的结果复制到其中,然后返回c_str

    优点:易于实现,可以保证为您提供完整的字符串。

    缺点:这是不是线程安全的,用户还必须记住要立即将内容复制到某个地方,否则下一次调用该函数将使数据无效,甚至使指针无效。
  • 在包装函数中分配适当大小的字符数组,将字符串的内容复制到该数组中,并返回指向它的指针。

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

    缺点:这是一个众所周知的问题,即在另一个DLL中分配内存而不是分配它的DLL无法正常工作。因此,您必须提供一个释放接口(interface),并且C接口(interface)的用户必须记住要始终使用该接口(interface),而不是简单地释放内存。
  • 让您的用户传递字符数组和长度,然后使用strncpy将字符串数据复制到字符数组。

    优势:您不必对字符串进行内存管理;它可以为字符串提供内存管理。这就是用户的责任心。同样,如果用户希望将字符串写在特定的位置,则可以简单地将地址传递给包装器,而不用再进行复制。

    缺点:如果用户提供的数组没有足够的长度,您将得到截断的字符串。

  • 我的建议:鉴于跨DLL内存分配的问题,我会说使用第三种方法并接受字符串被截断的风险(但请确保您有一种向用户报告错误的方法)。

    您的一个类(class)中有一个enum成员

    那很容易:在C接口(interface)中提供一个相同的(名称除外)全局范围枚举定义。由于相同的enum definitios为标识符提供相同的值,因此只需将类内枚举中的值强制转换为全局枚举,然后再将其转换回即可。唯一的问题是,每当您更新类内枚举时,都必须记住也要更新类外枚举。

    您有std::vector类型的公共(public)成员

    好吧,首先,这是一个糟糕的接口(interface)决定。处理它的方式与您实际上应在C++中处理这些 vector 的方式基本相同:为它们提供一个迭代器接口(interface)。

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

    给定接口(interface)成员为std::vector,另一种选择是给出指向 vector 第一个元素的指针。但是,这些将在下次调整 vector 大小时失效,因此仅在非常有限的情况下才是一种选择。

    您有一个print函数,该函数需要std::ostream和对对象的引用。

    当然std::ostream对C代码不可用。在C语言中,使用FILE*访问文件。我认为最好的选择是使用iostream类,该类围绕FILE*并允许您从一个iot_code构造。我不知道MSVC++是否提供了该功能,但如果不提供,Boost具有AFAIK这样的流。

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

    附加注意事项:异常

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

    C接口(interface)的示例 header

    以下是链接的示例库的可能的C接口(interface) header
    /* 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)
    

    关于c++ - 如何在C应用程序中使用C++ DLL?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17393134/

    相关文章:

    c++ - Qt Label::setPixmap 不工作

    c++ - 如何在 Qt 中 sleep /暂停?

    c# - c#中的字母数字正则表达式

    C 中的复合运算符 (+=) 和 &&,奇怪的值

    c - sscanf 是否需要以空字符结尾的字符串作为输入?

    visual-studio - 使用 C# 智能感知的 Visual Studio 语言服务

    c++ - 在 C++ 中连接字符串时出错

    c++ - 基于泰勒级数的基于模板的 Sin() 实现产生不正确的结果 C++

    c++ - MFC 应用程序 : How can I can register my program in Add or Remove Programs (ARP)?

    visual-studio - Visual Studio缩进HTML标记内容