本文为译文,点击 此处查看原文。

在本文中,我将提供一个使用libclang的快速教程。我开始试用libclang,同时实现了一个面向c++的开源反射框架——Reflang。然后我开始欣赏它的开发人员所做的令人惊叹的工作。
请注意,我们将从一个程序开始,并将逐步添加代码。滑动到文章末尾查看完整的解决方案。

1. libclang?

您有听说过Clang吗?,它是一个很棒的C++(和其他C语言家族)编译器。不完全是编译器,还是LLVM编译器的前端。
你看,编译器有一个很难解决的问题,所以他们中的大多数人把它分成两个简单的问题:

  • 将一种编程语言(在我们的例子中是C++)转换为一些中间代码 —— 这称为前端,这正是Clang所做的。
  • 将上面的中间代码转换为机器码 —— 这称为后端代码。Clang为此使用了LLVM。

Clang的巧妙之处在于,它也被设计成一个库。有许多类型的应用程序必须真正理解代码 —— IDE、文档生成器、静态分析工具等等。libclang可以用来正确处理所有语言特性和边界情况,而不是每个函数都必须实现C++解析(这是一个非常困难的任务!)

2. libclang!

而且非常简单。真的。那些Clang的开发者真的做了很棒的工作。在这篇文章的其余部分,我们将使用它的C-API来探索以下代码:

// header.hpp
class MyClass{
public:
  int field;
  virtual void method() const = 0;

  static const int static_field;
  static int static_method();
};

3. 基本的例子

让我们看一个最简单的例子。下面的程序解析上述文件,并立即存在:

#include <iostream>
#include <clang-c/Index.h>  // This is libclang.
using namespace std;

int main(){
  CXIndex index = clang_createIndex(0, 0);
  CXTranslationUnit unit = clang_parseTranslationUnit(
    index,
    "header.hpp", nullptr, 0,
    nullptr, 0,
    CXTranslationUnit_None);
  if (unit == nullptr)
  {
    cerr << "Unable to parse translation unit. Quitting." << endl;
    exit(-1);
  }

  clang_disposeTranslationUnit(unit);
  clang_disposeIndex(index);
}

这里有许多0和nullptr — 这些允许我们做一些更高级的事情(比如传递argv和argc,使用内存文件,等等)。我们不深入讨论这些。
那么,在clang_parseTranslationUnit()成功完成之后,我们会得到什么呢?我们将会得到一个经过解析的抽象语法树(AST),可以遍历和检查它。这正是我们要做的。

4. Cursors(游标)

指向AST的指针在libclang术语中称为CursorsCursors可以有父Cursors和子Cursors。它也可以有相关的Cursors(比如参数的默认值、枚举项的显式值等)。

我们将使用的“entry point” cursor是表示翻译单元(TU)的cursor,TU是一个C++术语,表示包含所有#include代码的单个文件。要获得TU的cursor,我们将使用描述性非常强的clang_getTranslationUnitCursor()。现在我们有了一个cursor,我们可以使用它进行研究或迭代。

5. Visit children

任何cursor都有一种Kind,它表示cursor的本质。Kind可以是许多选项中的一个,如这里所示。以下是一些例子:

  /** \brief A C or C++ struct. */
  CXCursor_StructDecl                    = 2,
  /** \brief A C or C++ union. */
  CXCursor_UnionDecl                     = 3,
  /** \brief A C++ class. */
  CXCursor_ClassDecl                     = 4,
  /** \brief An enumeration. */
  CXCursor_EnumDecl                      = 5,

我们可以使用clang_getCursorKind()cursor获取Kind

现在让我们来查看TU的所有children:

  CXCursor cursor = clang_getTranslationUnitCursor(unit);
  clang_visitChildren(
    cursor,
    [](CXCursor c, CXCursor parent, CXClientData client_data)
    {
      cout << "Cursor kind: " << clang_getCursorKind(c) << endl;
      return CXChildVisit_Recurse;
    },
    nullptr);

第二个参数lambda是为每次访问的cursor调用的函数。在内部,我们总是返回CXChildVisit_Recurse(尽管存在其他选项),因为我们想要探索文件中的所有内容。
输出:

Cursor kind: 4
Cursor kind: 39
Cursor kind: 6
Cursor kind: 21
Cursor kind: 9
Cursor kind: 21

这有点神秘,需要我们来回跳转到Index.h。幸运的是,有一个内置函数可以将cursor Kind转换为字符串,但首先我们需要讨论libclang的字符串。

6. CXString

CXString是一种表示指向AST的指针的类型。要检索一个实际有用的字符串(例如const char *),必须调用clang_getCString(),它在内部增加一个ref-count,然后在完成时调用clang_disposeString()

因为我们要做很多这样的工作,让我们创建一个辅助函数:

ostream& operator<<(ostream& stream, const CXString& str)
{
  stream << clang_getCString(str);
  clang_disposeString(str);
  return stream;
}

7. 打印有意义的输出

现在我们可以提取字符串,让我们修改lambda来打印一些实际有用的东西:

  CXCursor cursor = clang_getTranslationUnitCursor(unit);
  clang_visitChildren(
    cursor,
    [](CXCursor c, CXCursor parent, CXClientData client_data)
    {
      cout << "Cursor '" << clang_getCursorSpelling(c) << "' of kind '"
        << clang_getCursorKindSpelling(clang_getCursorKind(c)) << "'\n";
      return CXChildVisit_Recurse;
    },
    nullptr);

输出:

Cursor 'MyClass' of kind 'ClassDecl'
Cursor '' of kind 'CXXAccessSpecifier'
Cursor 'field' of kind 'FieldDecl'
Cursor 'method' of kind 'CXXMethod'
Cursor 'static_field' of kind 'VarDecl'
Cursor 'static_method' of kind 'CXXMethod'

现在,这真是太棒了。

8. 一个更复杂的例子

我非常小心,没有在header.hpp中包含任何header。为什么?如果在header.hpp中添加#include <string>。输出文件大小为1.51MB。有没有因为编译器花了这么长时间而生气?这就是原因。阅读这样的文件是很有教育意义的,但是为了大家的利益,我就不在这里发表了。

相反,让我们解析以下文件:

enum class Cpp11Enum
{
  RED = 10,
  BLUE = 20
};

struct Wowza
{
  virtual ~Wowza() = default;
  virtual void foo(int i = 0) = 0;
};

struct Badabang : Wowza
{
  void foo(int) override;

  bool operator==(const Badabang& o) const;
};

template <typename T>
void bar(T&& t);

同样的程序对于这个文件的输出:

Cursor 'Cpp11Enum' of kind 'EnumDecl'
Cursor 'RED' of kind 'EnumConstantDecl'
Cursor '' of kind 'IntegerLiteral'
Cursor 'BLUE' of kind 'EnumConstantDecl'
Cursor '' of kind 'IntegerLiteral'
Cursor 'Wowza' of kind 'StructDecl'
Cursor '~Wowza' of kind 'CXXDestructor'
Cursor 'foo' of kind 'CXXMethod'
Cursor 'i' of kind 'ParmDecl'
Cursor '' of kind 'IntegerLiteral'
Cursor 'Badabang' of kind 'StructDecl'
Cursor 'struct Wowza' of kind 'C++ base class specifier'
Cursor 'struct Wowza' of kind 'TypeRef'
Cursor 'foo' of kind 'CXXMethod'
Cursor '' of kind 'attribute(override)'
Cursor '' of kind 'ParmDecl'
Cursor 'operator==' of kind 'CXXMethod'
Cursor 'o' of kind 'ParmDecl'
Cursor 'struct Badabang' of kind 'TypeRef'
Cursor 'bar' of kind 'FunctionTemplate'
Cursor 'T' of kind 'TemplateTypeParameter'
Cursor 't' of kind 'ParmDecl'
Cursor 'T' of kind 'TypeRef'

9. 总结

libclang很棒:

  • 它允许检查代码是否已从宏展开,并跳转到那里;
  • 它允许检查每个光标的位置(文件+行+列);
  • 它允许获取函数的参数名、类型和返回类型;
  • 它可以理解templates、templates、lambdas,以及C++中的所有东西。

我希望这篇简短的文章能让您感到好奇,并且您也将尝试探索这个神奇的API提供了什么。如果你有什么想要补充或询问的,请在下方留言!

10. 完整的代码

为了方便您,以下是我们今天实现的完整代码:

#include <iostream>
#include <clang-c/Index.h>
using namespace std;

ostream& operator<<(ostream& stream, const CXString& str){
  stream << clang_getCString(str);
  clang_disposeString(str);
  return stream;
}

int main(){
  CXIndex index = clang_createIndex(0, 0);
  CXTranslationUnit unit = clang_parseTranslationUnit(
    index,
    "header.hpp", nullptr, 0,
    nullptr, 0,
    CXTranslationUnit_None
  );
  if (unit == nullptr){
    cerr << "Unable to parse translation unit. Quitting." << endl;
    exit(-1);
  }

  CXCursor cursor = clang_getTranslationUnitCursor(unit);
  clang_visitChildren(
    cursor,
    [](CXCursor c, CXCursor parent, CXClientData client_data){
      cout << "Cursor '" << clang_getCursorSpelling(c) << "' of kind '"
        << clang_getCursorKindSpelling(clang_getCursorKind(c)) << "'\n";
      return CXChildVisit_Recurse;
    },
    nullptr
   );

  clang_disposeTranslationUnit(unit);
  clang_disposeIndex(index);
}
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐