外部"C" OO C++中与 pthreads 一起使用

Extern "C" Usage With Pthreads in OO C++

本文关键字:一起 pthreads C++ OO 外部 中与      更新时间:2023-10-16

我有一个处理与特定硬件的所有通信的类。硬件需要通过一个套接字更新数据,并在已知的(硬编码的)多播地址上广播其IP以进行握手。

结果,类产生两个线程,一个线程处理与硬件的直接通信,并根据类的成员结构更新数据。另一个线程持续监视多播地址,以查看IP是否已更改。

两个线程都作为类内部的静态成员函数实现。然而,为了使用pthread,我的理解是需要将它们声明为extern"c"。也就是说,根据我的研究,不可能将类的成员函数声明为extern"c"。我看到之前参与该项目的工程师编写的一些代码解决这个问题的方法是,线程函数实现被外部"c"包围。也就是说,在函数声明或定义中找不到extern "C",但整个函数定义被封装在extern "C"{}中。

:

extern "C" {
  // function goes here
}

我对这个实现有几个问题,因为我希望提高对代码的理解,并更好地进行多线程编程。

  1. 外部"C"实际上做任何给定它的实现方式?在c++源码中,extern "C"的作用是什么?但在这种情况下,它真的适用吗考虑到我们处理的函数是类的静态成员?

  2. 线程成员函数似乎通过执行reinterpret_cast和创建指向类的指针来绕过修改类成员变量的事实。这是常规做法吗,因为我看到那个演员时,我的红旗就升起了吗?

  3. 构造函数调用pthread_create()来启动线程。我计划做一些事情,比如添加一个静态成员变量,它只允许一个实例在任何给定的时间与硬件通信。也就是说,我正在考虑允许调用者拥有类的多个实例,但一次只"连接"一个实例。这是标准做法,还是我的思维方式有些"俗套"?在给定的时间只会有一个硬件连接,所以只需要在给定的时间有一个类实例运行。

谢谢你的帮助。此外,如果你只是想建议"老兄,就使用Boost线程,FTW",请省省力气。虽然我最终将转向一个线程库,但我的理解是Boost只是在POSIX系统上包装pthreads。因此,在使用库之前,我想先学习如何使用原始线程。虽然我的主要目标是按时交付,但没有人说我不能在此过程中学到任何东西。;)

编辑添加:

这里是一些源代码,描述了我所看到的清晰的问题。我仍然不能100%确定外部"C"在这种情况下做任何事情…

In my .h:

class MyClass
{
 private:
  static void* ThreadFunc(void* args);  // extern "C" not found in declaration
  static bool instance_;
};

In my .cpp:

MyClass::MyClass() {
  if (!instance_) {
    instance_ = true;
    // spawn threads here
  } else {
    // don't spawn threads and warn user
  }
}
extern "C" {
  void *MyClass::ThreadFunc(void* args) {  //definintion wrapped in extern C
    MyClass* myclass_ptr = static_cast<MyClass*>(args);
    // ... more code
    return static_cast<void*> 0;
  }

问题1

它实际上是适用的,因为我们正在处理的函数是一个类的静态成员?

适用但不是必需的。一个符合标准的实现(编译器)知道相关的类型信息,可以直接使用函数指针,执行隐式转换,或者失败并报错。

请记住,在指定语言链接时可以应用其他属性。这可能包括但不限于特定的呼叫约定。然而,标准将此留给实现。

从7.5/1美元

与具有语言链接的实体相关联的一些属性是特定的到每个实现,这里没有描述。例如,特定的语言链接可能与具有外部链接的对象和函数名称的特定表示形式相关联,或者与特定的调用约定相关联,等等。

通过指定语言链接,您可以让实现处理这些属性的细节。否则,您将需要使用语言扩展来指定诸如调用约定之类的内容。即使这样做了,也可能存在未知或无法通过特定于实现的扩展应用的其他属性。

语言链接还影响实体的类型,在本例中是函数。从7.5/1美元

具有不同语言联系的两个函数类型是不同的类型,即使它们在其他方面是相同的。

在您的示例中,指向静态成员函数的指针可转换为线程启动函数的函数指针类型。这就是为什么您的代码可以在不指定语言链接和不显式强制转换的情况下正常工作。这并不意味着你应该指定语言链接。如果您关心实现之间的可移植性,那么使用它是一个好主意。例如,一些实现使用fastcall作为默认的C调用约定,而其他实现使用cdecl作为默认的C调用约定。在没有语言链接说明符的情况下,代码将无法编译,至少无法使用符合标准的实现。

下面的示例演示了如果使用不同的调用约定,转换将如何失败。我选择使用调用约定,因为它们在示例中最容易表达。

#include <iostream>
#if defined(__GNUC__)
#define FASTCALL __attribute__((fastcall))
#elif defined(_MSC_VER)
#define FASTCALL __fastcall
#endif
extern "C" 
{
    typedef void (*FUNC1)(); // Default C calling convention
    typedef void (FASTCALL *FUNC2)();  // fastcall calling convention
    void do1(FUNC1 f) { f(); }
    void do2(FUNC2 f) { f(); }
}
struct X
{
    static void test() {}
};
int main()
{
    do1(X::test);   // Ok. Implicit conversion available
    do2(X::test);   // Fail! Incompatible types. No conversion available
}

问题# 2

通过执行reinterpret_cast并创建指向类的指针来修改类的成员变量。这是常规做法吗,因为我看到那个演员时,我的红旗就升起了吗?

使用强制转换是实现这一目的的唯一方法,但使用reinterpret_cast是不必要的,IMO绝对应该避免。传递给pthread_create的线程启动函数接受一个void指针作为它唯一的参数。static_cast是足够的,并且行为范围更窄。

问题# 3

我打算做一些事情,比如添加一个静态成员变量…

这个问题是主观的,你怎么处理完全取决于你自己。如果没有一些代码,很难判断你的实现是否是一个hack。

作为最后的提示…老兄!使用Boost !

不能声明静态成员函数extern C,但可以从另一个函数extern C调用静态成员函数。在定义线程函数的cpp文件中:

namespace {
extern "C" {
void* some_thread_func(void* arg) {
  return MyClass::static_thread_func(arg);
}
}}
// static member
// (You can omit this "middle-man" if "do_stuff()" is public.)
void* MyClass::static_thread_func(void* arg) {
  return static_cast<MyClass*>(arg)->do_stuff();
}
void MyClass::start_thread_func() {
  pthread_t thread;
  int error = pthread_create(&thread, NULL, some_thread_func, this);
  // handle errors, store "thread" somewhere appropriate.
  ...
}

多个实例/一个连接的问题是一个设计问题,我不知道什么设计真正适合你的程序的其余部分-但我要说的是,Singleton似乎反映了你的设计要求,有一个单一的设备来管理。

最后,不要用boost::thread,用std::thread;)