PostgreSQL 中文参考文档
🌐 英文原版 官方 ↗

34.13. C++ 应用程序支持 #

34.13.1. 主变量的作用域
34.13.2. 使用外部 C 模块开发 C++ 应用

ECPG 对 C++ 应用程序有一些有限的支持。本节描述一些注意事项。

ecpg 预处理器接收一个用 C(或类 C 语言)编写的、 内嵌 SQL 命令的输入文件,把这些嵌入式 SQL 命令转换为 C 语言代码块, 最终生成一个 .c 文件。当在 C++ 下使用时, ecpg 生成的 C 语言代码块所用的库函数的头文件声明 会被包裹在 extern "C" { ... } 块中,因此它们应当 能够在 C++ 中无缝工作。

不过一般而言,ecpg 预处理器只理解 C,它不会处理 C++ 语言的特殊语法和保留字。因此,C++ 应用代码中使用了 C++ 特有的复杂特性的某些嵌入式 SQL 代码,可能无法被正确预处理, 或者可能无法按预期工作。

在 C++ 应用中使用嵌入式 SQL 代码的一种安全方式是:把 ECPG 调用 隐藏在一个 C 模块中,由 C++ 应用代码调用该模块来访问数据库,并将它 与其余的 C++ 代码一起链接。相关内容请参见 Section 34.13.2。

34.13.1. 主变量的作用域 #

ecpg 预处理器理解 C 中变量的作用域。在 C 语言中, 这相当简单,因为变量的作用域基于其代码块。然而在 C++ 中, 类的成员变量是在与声明位置不同的代码块中被引用的,因此 ecpg 预处理器无法理解类成员变量的作用域。

例如,在下面的情形中,ecpg 预处理器无法在 test 方法中找到变量 dbname 的任何声明,因此会发生错误。

class TestCpp
{
    EXEC SQL BEGIN DECLARE SECTION;
    char dbname[1024];
    EXEC SQL END DECLARE SECTION;

  public:
    TestCpp();
    void test();
    ~TestCpp();
};

TestCpp::TestCpp()
{
    EXEC SQL CONNECT TO testdb1;
    EXEC SQL SELECT pg_catalog.set_config('search_path', '', false); EXEC SQL COMMIT;
}

void Test::test()
{
    EXEC SQL SELECT current_database() INTO :dbname;
    printf("current_database = %s\n", dbname);
}

TestCpp::~TestCpp()
{
    EXEC SQL DISCONNECT ALL;
}

这段代码会产生类似这样的错误:

ecpg test_cpp.pgc
test_cpp.pgc:28: ERROR: variable "dbname" is not declared

为了避免这种作用域问题,可以修改 test 方法,让它使用一个局部变量作为中间存储。但这种做法只是一种蹩脚的 变通,因为它会让代码变得难看并降低性能。

void TestCpp::test()
{
    EXEC SQL BEGIN DECLARE SECTION;
    char tmp[1024];
    EXEC SQL END DECLARE SECTION;

    EXEC SQL SELECT current_database() INTO :tmp;
    strlcpy(dbname, tmp, sizeof(tmp));

    printf("current_database = %s\n", dbname);
}

34.13.2. 使用外部 C 模块开发 C++ 应用 #

如果你理解了 ecpg 预处理器在 C++ 中的这些技术 局限,你可能会得出这样的结论:在链接阶段把 C 目标文件与 C++ 目标文件链接起来,让 C++ 应用能够使用 ECPG 的特性,可能比直接在 C++ 代码中编写某些嵌入式 SQL 命令更好。本节通过一个简单的例子 介绍一种把部分嵌入式 SQL 命令与 C++ 应用代码分离的方法。 在这个例子中,应用程序用 C++ 实现,而连接 PostgreSQL 服务器则使用 C 和 ECPG。

需要创建三种文件:一个 C 文件 (*.pgc)、一个头文件和一个 C++ 文件:

test_mod.pgc #

一个以 C 编写、用于执行嵌入式 SQL 命令的子例程模块。 它将被预处理器转换为 test_mod.c。

#include "test_mod.h"
#include <stdio.h>

void
db_connect()
{
    EXEC SQL CONNECT TO testdb1;
    EXEC SQL SELECT pg_catalog.set_config('search_path', '', false); EXEC SQL COMMIT;
}

void
db_test()
{
    EXEC SQL BEGIN DECLARE SECTION;
    char dbname[1024];
    EXEC SQL END DECLARE SECTION;

    EXEC SQL SELECT current_database() INTO :dbname;
    printf("current_database = %s\n", dbname);
}

void
db_disconnect()
{
    EXEC SQL DISCONNECT ALL;
}

test_mod.h #

一个头文件,包含 C 模块(test_mod.pgc) 中函数的声明。test_cpp.cpp 会包含它。该文件必须在声明周围有一个 extern "C" 块,因为它将从 C++ 模块进行链接。

#ifdef __cplusplus
extern "C" {
#endif

void db_connect();
void db_test();
void db_disconnect();

#ifdef __cplusplus
}
#endif

test_cpp.cpp #

应用的主代码,包括 main 例程,以及本例中的一个 C++ 类。

#include "test_mod.h"

class TestCpp
{
  public:
    TestCpp();
    void test();
    ~TestCpp();
};

TestCpp::TestCpp()
{
    db_connect();
}

void
TestCpp::test()
{
    db_test();
}

TestCpp::~TestCpp()
{
    db_disconnect();
}

int
main(void)
{
    TestCpp *t = new TestCpp();

    t->test();
    return 0;
}

构建该应用的步骤如下。运行 ecpg 把 test_mod.pgc 转换为 test_mod.c,再用 C 编译器编译 test_mod.c 生成 test_mod.o:

ecpg -o test_mod.c test_mod.pgc
cc -c test_mod.c -o test_mod.o

接下来,用 C++ 编译器编译 test_cpp.cpp 生成 test_cpp.o:

c++ -c test_cpp.cpp -o test_cpp.o

最后,使用 C++ 编译器驱动程序把这些目标文件 (test_cpp.o 和 test_mod.o)链接成一个可执行文件:

c++ test_cpp.o test_mod.o -lecpg -o test_cpp