阅读(4599) (14)

PostgreSQL C++ 应用

2021-08-20 17:00:35 更新
35.13.1. 主变量的可见范围
35.13.2. 使用外部 C 模块的 C++ 应用开发

ECPG 对于 C++ 应用提供了有限的支持。这一节描述了一些忠告。

ecpg预处理器采用一个用 C(或者类似 C 的东西)和嵌入式 SQL 命令编写的输入文件,把嵌入式 SQL 命令转换成 C 语言块,并且最终产生一个.c文件。在 C++ 下使用时,因此它们应该能在 C++ 中无缝地使用。

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

使用 C++ 应用中嵌入式 SQL 代码的安全方法是把 ECPG 调用隐藏在一个 C 模块中,C++ 应用代码会调用它来访问数据库,还要把它和剩余的 C++ 代码链接起来。详见第 35.13.2 节。

35.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);
}

35.13.2. 使用外部 C 模块的 C++ 应用开发

如果你理解了 C++ 中ecpg预处理器的这些技术限制,你可能已经知道在链接阶段把 C 对象和 C++ 对象链接起来让 C++ 应用能使用 ECPG 特性比直接在 C++ 代码中写一些嵌入式 SQL 命令要更好。这一节用一个简单的例子描述了一种将嵌入式 SQL 命令从 C++ 应用代码中独立出去的方法。在这个例子中,应用由 C++ 实现,而 C 和 ECPG 被用来连接到 PostgreSQL 服务器。

需要创建三种文件:一个 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;
}

要构建该应用,按以下步骤处理。通过运行ecpgtest_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.otest_mod.o)成为一个可执行文件:

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