Início rápido: Ligue e consulte com C++ e ODBC

Aplica-se a:SQL ServerBanco de Dados SQL do Azure

Neste quickstart, constróis uma aplicação de consola C++ no Windows, Linux ou macOS. A aplicação liga-se a uma AdventureWorksLT base de dados usando o Microsoft ODBC Driver 18 para SQL Server, associa um parâmetro de consulta, executa a consulta e lê as linhas de resultados.

Prerequisites

  • Microsoft ODBC Driver 18 para SQL Server. Instala o driver para Windows, Linux ou macOS.
  • Um compilador C++17 e os ficheiros de desenvolvimento ODBC da plataforma:
    • No sistema Windows, instale o Visual Studio 2022 ou as Ferramentas de Compilação para o Visual Studio 2022 com a carga de trabalho Desenvolvimento para ambiente de trabalho com C++. O SDK do Windows fornece os cabeçalhos ODBC e odbc32.lib.
    • No Linux, instala um compilador C++ e o pacote de desenvolvimento unixODBC para a tua distribuição. O pacote fornece os cabeçalhos ODBC e libodbc.
    • No macOS, instala as ferramentas de linha de comandos do Xcode e unixODBC a partir do Homebrew.
  • Uma base de dados no Base de Dados SQL do Azure ou SQL Server que contém os AdventureWorksLT dados de exemplo. Para Base de Dados SQL do Azure, selecione a fonte de dados de exemplo ao criar uma única base de dados. Para o SQL Server, restaura uma AdventureWorksLT cópia de segurança a partir de bases de dados de exemplo do AdventureWorks.

Verifique o condutor

Confirme que o gestor de drivers consegue encontrar o Microsoft ODBC Driver 18 para SQL Server.

Execute este comando no PowerShell:

Get-OdbcDriver -Name "ODBC Driver 18 for SQL Server"

Cada comando deve listar ODBC Driver 18 for SQL Server. Se o driver não estiver listado, reinstale-o antes de continuar.

Configurar a ligação

A aplicação lê a cadeia de ligação completa da ODBC_CONNECTION_STRING variável de ambiente. Não mostra a cadeia de ligação nem a aceita como argumento de linha de comandos.

Usa uma cadeia de ligação do Driver 18 que seja válida para a tua base de dados e método de autenticação. O seguinte exemplo de autenticação SQL funciona no Windows, Linux e macOS quando a base de dados permite autenticação SQL:

Driver={ODBC Driver 18 for SQL Server};Server=tcp:<server>,1433;Database=<database>;UID=<user_id>;PWD=<password>;Encrypt=yes;TrustServerCertificate=no;

Para opções de autenticação Microsoft Entra, veja Usar Microsoft Entra ID com o driver ODBC. Para todas as configurações suportadas, consulte DSN e palavras-chave e atributos da cadeia de ligação.

O Microsoft ODBC Driver 18 ativa a encriptação por defeito. Especifique Encrypt=yes para que o requisito da aplicação seja explícito. Mantém TrustServerCertificate=no em produção para que o driver valide o certificado do servidor. O certificado deve associar o nome e a cadeia do servidor a uma autoridade certificadora em que o cliente confie. Para orientações de configuração, veja Falhas de validação de certificados.

Atenção

TrustServerCertificate=yes ignora a validação do certificado. Use-o apenas para desenvolvimento local isolado enquanto configura um certificado em que o cliente confia. Não o uses na produção.

Define a variável ambiente sem adicionar a cadeia de ligação ao histórico do teu shell.

OpenDeveloper PowerShell para VS 2022. Execute estes comandos e depois cole a cadeia de ligação no prompt:

$secureConnectionString = Read-Host "ODBC connection string" -AsSecureString
$env:ODBC_CONNECTION_STRING = [System.Net.NetworkCredential]::new(
    "", $secureConnectionString).Password
$secureConnectionString = $null

Uma variável de ambiente mantém a cadeia de ligação fora do ficheiro de origem, mas o processo e os seus processos filhos podem lê-la. Para aplicações de produção, utilize a autenticação do Microsoft Entra sempre que possível e obtenha os segredos a partir de um repositório seguro em tempo de execução.

Criar o aplicativo

  1. Crie um diretório de projeto e altere para ele:

    New-Item -ItemType Directory odbc-quickstart
    Set-Location odbc-quickstart
    

  1. Crie um arquivo chamado odbc-quickstart.cpp com o seguinte código:

    #ifdef _WIN32
    #include <windows.h>
    #endif
    
    #include <sql.h>
    #include <sqlext.h>
    #include <sqltypes.h>
    
    #include <cstdlib>
    #include <iomanip>
    #include <iostream>
    #include <string>
    #include <string_view>
    
    std::string ReadEnvironmentVariable(const char* name)
    {
    #ifdef _WIN32
        char* value = nullptr;
        std::size_t length = 0;
        if (_dupenv_s(&value, &length, name) != 0 || value == nullptr)
            return {};
    
        std::string result(value);
        std::free(value);
        return result;
    #else
        const char* value = std::getenv(name);
        return value == nullptr ? std::string{} : value;
    #endif
    }
    
    bool PrintDiagnostics(SQLSMALLINT handleType, SQLHANDLE handle)
    {
        bool dataTruncated = false;
        SQLCHAR state[6];
        SQLINTEGER nativeError;
        SQLCHAR message[SQL_MAX_MESSAGE_LENGTH];
        SQLSMALLINT messageLength;
    
        for (SQLSMALLINT record = 1;
             SQL_SUCCEEDED(SQLGetDiagRec(handleType, handle, record, state,
                                         &nativeError, message, sizeof(message),
                                         &messageLength));
             ++record)
        {
            std::cerr << '[' << state << "] (" << nativeError << ") "
                      << message << '\n';
            if (std::string_view(reinterpret_cast<const char*>(state), 5) == "01004")
                dataTruncated = true;
        }
        return dataTruncated;
    }
    
    bool Succeeded(SQLRETURN result, SQLSMALLINT handleType, SQLHANDLE handle,
                   bool* dataTruncated = nullptr)
    {
        if (result == SQL_SUCCESS)
            return true;
    
        const bool diagnosticReportsTruncation = PrintDiagnostics(handleType, handle);
        if (dataTruncated != nullptr)
            *dataTruncated = diagnosticReportsTruncation;
        return result == SQL_SUCCESS_WITH_INFO;
    }
    
    struct OdbcHandles
    {
        SQLHENV environment = SQL_NULL_HENV;
        SQLHDBC connection = SQL_NULL_HDBC;
        SQLHSTMT statement = SQL_NULL_HSTMT;
    
        ~OdbcHandles()
        {
            if (statement != SQL_NULL_HSTMT)
                SQLFreeHandle(SQL_HANDLE_STMT, statement);
            if (connection != SQL_NULL_HDBC)
            {
                SQLDisconnect(connection);
                SQLFreeHandle(SQL_HANDLE_DBC, connection);
            }
            if (environment != SQL_NULL_HENV)
                SQLFreeHandle(SQL_HANDLE_ENV, environment);
        }
    };
    
    int main()
    {
        std::string connectionString =
            ReadEnvironmentVariable("ODBC_CONNECTION_STRING");
        if (connectionString.empty())
        {
            std::cerr << "Set ODBC_CONNECTION_STRING before running.\n";
            return 1;
        }
    
        OdbcHandles handles;
        SQLRETURN result = SQLAllocHandle(
            SQL_HANDLE_ENV, SQL_NULL_HANDLE, &handles.environment);
        if (!Succeeded(result, SQL_HANDLE_ENV, handles.environment))
        {
            std::cerr << "Unable to allocate an ODBC environment handle.\n";
            return 1;
        }
    
        result = SQLSetEnvAttr(
            handles.environment,
            SQL_ATTR_ODBC_VERSION,
            reinterpret_cast<SQLPOINTER>(SQL_OV_ODBC3_80),
            0);
        if (!Succeeded(result, SQL_HANDLE_ENV, handles.environment))
            return 1;
    
        result = SQLAllocHandle(
            SQL_HANDLE_DBC, handles.environment, &handles.connection);
        if (!Succeeded(result, SQL_HANDLE_ENV, handles.environment))
            return 1;
    
        result = SQLDriverConnect(
            handles.connection,
            nullptr,
            reinterpret_cast<SQLCHAR*>(connectionString.data()),
            SQL_NTS,
            nullptr,
            0,
            nullptr,
            SQL_DRIVER_NOPROMPT);
        if (!Succeeded(result, SQL_HANDLE_DBC, handles.connection))
            return 1;
    
        result = SQLAllocHandle(
            SQL_HANDLE_STMT, handles.connection, &handles.statement);
        if (!Succeeded(result, SQL_HANDLE_DBC, handles.connection))
            return 1;
    
        SQLINTEGER minimumProductId = 0;
        SQLLEN minimumProductIdLength = 0;
        result = SQLBindParameter(
            handles.statement,
            1,
            SQL_PARAM_INPUT,
            SQL_C_SLONG,
            SQL_INTEGER,
            10,
            0,
            &minimumProductId,
            0,
            &minimumProductIdLength);
        if (!Succeeded(result, SQL_HANDLE_STMT, handles.statement))
            return 1;
    
        SQLCHAR query[] =
            "SELECT TOP (5) ProductID, Name "
            "FROM SalesLT.Product "
            "WHERE ProductID > ? "
            "ORDER BY ProductID;";
        result = SQLExecDirect(handles.statement, query, SQL_NTS);
        if (!Succeeded(result, SQL_HANDLE_STMT, handles.statement))
            return 1;
    
        std::cout << "Product ID  Name\n"
                  << "----------  ----\n";
    
        while ((result = SQLFetch(handles.statement)) != SQL_NO_DATA)
        {
            if (!Succeeded(result, SQL_HANDLE_STMT, handles.statement))
                return 1;
    
            SQLINTEGER productId = 0;
            SQLLEN productIdLength = 0;
            SQLCHAR productName[256] = {};
            SQLLEN productNameLength = 0;
    
            result = SQLGetData(
                handles.statement, 1, SQL_C_SLONG, &productId,
                sizeof(productId), &productIdLength);
            if (!Succeeded(result, SQL_HANDLE_STMT, handles.statement))
                return 1;
    
            bool productNameTruncated = false;
            result = SQLGetData(
                handles.statement, 2, SQL_C_CHAR, productName,
                sizeof(productName), &productNameLength);
            if (!Succeeded(result, SQL_HANDLE_STMT, handles.statement,
                           &productNameTruncated))
                return 1;
    
            std::string productNameText;
            if (productNameLength == SQL_NULL_DATA)
            {
                productNameText = "<NULL>";
            }
            else if (productNameTruncated ||
                     productNameLength > static_cast<SQLLEN>(sizeof(productName) - 1))
            {
                std::cerr << "Product name was truncated; no partial value was printed.\n";
                return 1;
            }
            else if (productNameLength == SQL_NO_TOTAL)
            {
                std::cerr << "Product name length is unknown; no value was printed.\n";
                return 1;
            }
            else if (productNameLength < 0)
            {
                std::cerr << "The driver returned an invalid product-name length.\n";
                return 1;
            }
            else if (productNameLength == 0)
            {
                productNameText = "<empty>";
            }
            else
            {
                productNameText.assign(
                    reinterpret_cast<const char*>(productName),
                    static_cast<std::size_t>(productNameLength));
            }
    
            std::cout << std::left << std::setw(12) << productId
                      << productNameText << '\n';
        }
    
        return 0;
    }
    

A aplicação utiliza apenas a API ODBC standard, pelo que inclui os cabeçalhos do gestor de controladores e faz a ligação à biblioteca do gestor de controladores. A cadeia de ligação seleciona o Microsoft ODBC Driver 18 para SQL Server em tempo de execução.

SQL_DRIVER_NOPROMPT impede SQLDriverConnect a abertura de um diálogo de configuração. Se a cadeia de ligação estiver incompleta, a chamada devolve um erro e a aplicação imprime todos os registos de diagnóstico. A aplicação também imprime diagnósticos quando uma chamada ODBC verificada retorna SQL_SUCCESS_WITH_INFO, o que significa que a chamada foi bem-sucedida com um aviso.

A consulta associa 0 como parâmetro int do SQL Server, lê os primeiros cinco produtos de SalesLT.Product e obtém o ID e o nome de cada produto com SQLGetData. Para um nome de produto, a aplicação apresenta <NULL> para SQL_NULL_DATA e <empty> para um valor de comprimento zero. Se SQLGetData reportar SQLSTATE 01004, um comprimento desconhecido, ou um comprimento maior que o buffer, a aplicação reporta o problema e sai sem imprimir um nome parcial.

Compilar e executar o aplicativo

  1. Na mesma janela do PowerShell do Desenvolvedor, compila a aplicação:

    cl /std:c++17 /EHsc /W4 odbc-quickstart.cpp /link odbc32.lib
    
  2. Execute a aplicação:

    .\odbc-quickstart.exe
    

A saída padrão da aplicação é:

Product ID  Name
----------  ----
680         HL Road Frame - Black, 58
706         HL Road Frame - Red, 58
707         Sport-100 Helmet, Red
708         Sport-100 Helmet, Black
709         Mountain Bike Socks, M

Apague a cadeia de ligação quando terminar.

Remove-Item Env:\ODBC_CONNECTION_STRING