使用 ODBC 驱动开发 C 和 C++ 应用程序

版本:18.7.1.1
日期:2026年9月7日

要从 C 或 C++ 调用 ODBC API,请包含 sql.hsqlext.hsqltypes.h,然后链接驱动管理器的导入库。 要使用 Microsoft ODBC Driver for SQL Server 在 ODBC 标准之上添加的 SQL Server 扩展,还应包含 msodbcsql.h,并在核心 ODBC 头文件之后包含它。

适用于:Microsoft ODBC 驱动18版,适用于Windows、Linux和macOS上的SQL Server。 版本17使用相同的头部名称,包含 170 安装路径和 msodbcsql17 库名。

头文件和库

该平台提供核心的 ODBC 头部和驱动程序管理器,而非驱动程序包。 在 Windows 上,它们是随 Windows SDK 一起出厂的。 在 Linux 和 macOS 上,它们是随 unixODBC 开发包一起预发的。 驱动程序 SDK 仅提供 msodbcsql.h 和批量复制导入库。

你打的是什么 Headers Windows操作系统 Linux macOS
ODBC API sql.hsqlext.hsqltypes.h odbc32.lib -lodbc -lodbc
ODBC API,Unicode 入口点 添加 sqlucode.h odbc32.lib -lodbc -lodbc
ODBC 安装程序 API 添加 odbcinst.h odbccp32.lib -lodbcinst -lodbcinst
SQL Server 驱动扩展 添加 msodbcsql.h 没有额外的图书馆 没有额外的图书馆 没有额外的图书馆
批量复制(bcp_*)函数 添加 msodbcsql.h msodbcsql18.lib -lmsodbcsql-18 -lmsodbcsql.18

批量复制链接名称因平台不同而不同,因为文件名不同。 在 Linux 上, -lmsodbcsql-18 通过 libmsodbcsql-18.so 中的 /usr/lib符号链接解析,链接器已经在搜索该链接,所以你不需要 -L。 在 macOS 上,驱动程序随附为 libmsodbcsql.18.dylib,这与 -lmsodbcsql.18 相匹配,但在 Apple silicon 机型上,Homebrew 的库目录不在默认搜索路径中。 链接批量复制功能时添加 -L$(brew --prefix)/lib

只有批量复制函数需要驱动程序自己的库。 连接属性、语句属性、列属性和 SQL Server 类型标识符都是宏和类型定义,因此包含msodbcsql.h它们就足够了。

安装程序 API 是与 ODBC API 不同的独立库。 在 Linux 或 macOS 上,调用像 SQLGetPrivateProfileString 这样的函数而不使用 -lodbcinst 会在链接时因未定义引用而失败,而不是在编译时失败。

要安装提供Linux和macOS核心头部的unixODBC开发包,请参见 “安装UnixODBC驱动管理器”。

在 Linux 和 macOS 上的 C 代码中,在包含 msodbcsql.h 之前先包含 wchar.h

msodbcsql.h 的 Linux 和 macOS 版本使用 wchar_t 声明了 Always Encrypted 密钥存储提供程序接口,但它们不包含定义此类型的头文件。 在 C++ 中, wchar_t 是关键字,因此 C++ 翻译单元无需额外首部即可构建。 在C语言中, wchar_t 是typedef,所以你需要在C翻译单元中首先包含 <wchar.h>

#include <wchar.h>

如果你未包含 <wchar.h>,编译器会报告来自 msodbcsql.h 内部的 unknown type name 'wchar_t' 错误。 在 Windows 上添加 include 是无害的,所以要把它加到共享源代码里,而不是放在平台守卫后面。

在核心 ODBC 头文件之后包含 msodbcsql.h

msodbcsql.h定义的驱动程序名称宏之外的所有内容都位于#ifdef ODBCVER块中,而sql.h定义了ODBCVER。 如果你先包含msodbcsql.h,预处理器就会跳过整个代码块,而该头文件不会产生任何作用。 编译器不会发出警告。

/* Correct order. */
#ifdef _WIN32
#include <windows.h>
#endif

#include <wchar.h>
#include <sql.h>
#include <sqlext.h>
#include <sqltypes.h>
#include <msodbcsql.h>

sql.h 之前包含 msodbcsql.h 会使 ODBCVER 块内的所有内容都处于未定义状态。 编译器会在使用处报告该错误,而不是在包含处:

order-wrong.c(7): error C2065: 'SQL_COPT_SS_BCP': undeclared identifier

在 Windows 上,你必须在 ODBC 头部前包含windows.h。 Windows SDK 中的 sqltypes.hsql.h 副本使用了 Windows 类型,例如 DWORDLONGmsodbcsql.h将其SQL Server结构包裹在pshpack8.hpoppack.h中。 没有 windows.h,构建会直接在 SDK 头文件本身中失败。

SDK文件安装地点

平台 msodbcsql.h 批量复制库
Windows操作系统 %ProgramFiles%\Microsoft SQL Server\Client SDK\ODBC\180\SDK\Include %ProgramFiles%\Microsoft SQL Server\Client SDK\ODBC\180\SDK\Lib\<architecture>\msodbcsql18.lib
Linux /opt/microsoft/msodbcsql18/include /opt/microsoft/msodbcsql18/lib64,带有一个 /usr/lib/libmsodbcsql-18.so 符号链接
macOS $(brew --prefix msodbcsql18)/include/msodbcsql18 $(brew --prefix)/lib/libmsodbcsql.18.dylib

在 Windows 上,文件夹Lib包含安装程序放置的每种处理器架构的子文件夹,例如 x64x86arm64或 。 将 Include 文件夹添加到编译器的包含路径中,并将 architecture 子文件夹添加到链接器的库路径中。

在 Linux 上,共享对象带有版本号,命名类似于 libmsodbcsql-18.6.so.2.1,且不带有 SONAME。 该软件包会安装一个指向它的 /usr/lib/libmsodbcsql-18.so,这使得 -lmsodbcsql-18 在不使用 -L 选项的情况下也能解析。 通过该符号链接进行链接,而不是直接指定带版本号的文件,这样驱动程序更新时就不会导致构建失败。

在 macOS 上,Homebrew 会安装到其自身的前缀路径下:在 Apple silicon 上为 /opt/homebrew,在 Intel 机型上为 /usr/local。 这两个前缀都是指向带版本号的 Cellar 目录的符号链接。 在构建脚本中使用 brew --prefix msodbcsql18brew --prefix unixodbc ,而不是硬编码其中任何一个。

路径中的数字对应驱动程序主版本号。 版本 17 在 Windows 上安装到 /opt/microsoft/msodbcsql17/,在 Linux 上安装到 ...\ODBC\170\SDK\,其导入库为 msodbcsql17.lib

关于各平台的完整文件清单,请参见系统需求、安装和驱动程序文件(Windows)、在Linux上安装ODBC驱动,以及在macOS上安装ODBC驱动

确认你的构建设置

该程序会根据首部编译,链接到驱动程序管理器,并列出驱动程序管理器能看到的驱动程序。 它无法连接,所以这把构建或注册问题和网络或凭证问题分开了。

#include <stdio.h>
#include <wchar.h>

#ifdef _WIN32
#include <windows.h>
#endif

#include <sql.h>
#include <sqlext.h>
#include <sqltypes.h>
#include <msodbcsql.h>

static void PrintDiagnostics(SQLSMALLINT handleType, SQLHANDLE handle)
{
    SQLCHAR state[6];
    SQLINTEGER native;
    SQLCHAR message[SQL_MAX_MESSAGE_LENGTH];
    SQLSMALLINT length;

    for (SQLSMALLINT record = 1;
         SQL_SUCCEEDED(SQLGetDiagRec(handleType, handle, record, state, &native,
                                     message, sizeof(message), &length));
         ++record)
    {
        fprintf(stderr, "  [%s] (%ld) %s\n", state, (long)native, message);
    }
}

int main(void)
{
    SQLHENV environment = SQL_NULL_HENV;
    SQLRETURN rc = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &environment);

    if (!SQL_SUCCEEDED(rc))
    {
        fprintf(stderr, "SQLAllocHandle for the environment failed.\n");
        return 1;
    }

    rc = SQLSetEnvAttr(environment, SQL_ATTR_ODBC_VERSION,
                       (SQLPOINTER)SQL_OV_ODBC3_80, 0);
    if (!SQL_SUCCEEDED(rc))
    {
        fprintf(stderr, "SQLSetEnvAttr for SQL_OV_ODBC3_80 failed.\n");
        PrintDiagnostics(SQL_HANDLE_ENV, environment);
        SQLFreeHandle(SQL_HANDLE_ENV, environment);
        return 1;
    }

    printf("Driver name from msodbcsql.h: %s\n", SQLODBC_DRIVER_NAME);
    printf("Installed drivers:\n");

    SQLCHAR description[256];
    SQLSMALLINT descriptionLength = 0;
    SQLUSMALLINT direction = SQL_FETCH_FIRST;

    while (SQL_SUCCEEDED(SQLDrivers(environment, direction,
                                    description, sizeof(description), &descriptionLength,
                                    NULL, 0, NULL)))
    {
        printf("  %s\n", description);
        direction = SQL_FETCH_NEXT;
    }

    SQLFreeHandle(SQL_HANDLE_ENV, environment);
    return 0;
}

把它做成一个狭义的角色程序。 SQLODBC_DRIVER_NAME 在定义了 UNICODE_UNICODE 时会展开为宽字符串,而使用 %sprintf 无法接受这种字符串。

cl /W4 /I "%ProgramFiles%\Microsoft SQL Server\Client SDK\ODBC\180\SDK\Include" odbc-build-check.c /link odbc32.lib

在 Windows 上,/W4报告了来自 Windows SDK 中 C4201: nonstandard extension used: nameless struct/union 副本的两个 sqlext.h 警告。 这些警告来自SDK头部,而不是你的代码,构建是成功的。

第一行显示的是编译进你的二进制文件中的驱动程序名称。 其余的都是驱动管理器自己的列表,所以你预期看到但没看到的驱动是注册问题,不是构建问题。 你的列表会有所不同,包括所有已安装的ODBC驱动,而不仅仅是SQL Server驱动:

Driver name from msodbcsql.h: ODBC Driver 18 for SQL Server
Installed drivers:
  SQL Server
  ODBC Driver 17 for SQL Server
  ODBC Driver 18 for SQL Server
  Microsoft Access Driver (*.mdb, *.accdb)
  Microsoft Excel Driver (*.xls, *.xlsx, *.xlsm, *.xlsb)
  Microsoft Access Text Driver (*.txt, *.csv)
  Microsoft Access dBASE Driver (*.dbf, *.ndx, *.mdx)

使用 SQLODBC_DRIVER_NAME 构建连接字符串,而不是使用字符串字面量。 该宏会跟踪编译时所针对的头文件,因此升级 SDK 后,只需在一处即可更新驱动程序名称。

msodbcsql.h 对 ODBC API 的新增功能

msodbcsql.h扩展了标准 ODBC API 并包含 SQL Server 的具体内容。 每个家族占据一个连续的数值范围,从一个基数开始计算。 这些范围在不同族之间并不独特,所以你传递值给的函数就是区分它们的关键。

家庭 基常数 Value
SQLSetConnectAttr 的连接属性 SQL_COPT_SS_BASE 1200
SQLSetStmtAttr 的语句属性 SQL_SOPT_SS_BASE 1225
SQLColAttribute 的列属性 SQL_CA_SS_BASE 1200
用于 SQLGetInfo 的信息类型 SQL_INFO_SS_FIRST 1199
SQLGetDiagField 的诊断字段 SQL_DIAG_SS_BASE -1150
诊断动态功能代码 SQL_DIAG_DFC_SS_BASE -200

该头文件还声明:

  • 身份验证属性(包括 SQL_COPT_SS_AUTHENTICATIONSQL_COPT_SS_ACCESS_TOKEN)包含 Microsoft Entra ID 设置和访问令牌。
  • 用于 ODBC 未定义的 SQL Server 类型的、范围为 -150 到 -199 的 SQL 类型标识符:SQL_SS_VARIANTSQL_SS_TIME2SQL_SS_VECTORSQL_SS_UDTSQL_SS_XMLSQL_SS_TABLESQL_SS_TIMESTAMPOFFSET。 这些表示一种 SQL 类型,因此,应在 ODBC 需要 SQL 类型的位置传入它们,例如 SQLBindParameterParameterType 参数。
  • 缓冲区侧有三种匹配 的C 类型: SQL_C_SS_TIME2、、 SQL_C_SS_TIMESTAMPOFFSETSQL_C_SS_VECTOR。 其他 SQL Server 类型绑定到标准的 ODBC C 类型,如 SQL_C_BINARYSQL_C_WCHAR,因此它们没有SQL_C_SS_*对应的。
  • SQL_C_SS_* 类型绑定到的结构有:SQL_SS_TIME2_STRUCTSQL_SS_TIMESTAMPOFFSET_STRUCTSQL_SS_VECTOR_STRUCT
  • 批量复制原型和宏,包括 bcp_initbcp_bindbcp_sendrowbcp_batchbcp_done和 。 BCP_ENCRYPT_ONBCP_ENCRYPT_OFFBCP_ENCRYPT_STRICT 选项仅在 Windows 标头中提供。

每个平台都随附自己的 msodbcsql.h,而且它们声明的符号并不完全相同。 SQLPERF 结构及用于填充该结构的性能连接属性(例如 SQL_COPT_SS_PERF_DATASQL_COPT_SS_PERF_QUERY)仅存在于 Windows 标头文件中。 Linux 和 macOS 的头部没有声明它们,驱动也不会收集这些平台的性能数据。 参见编程指南(Linux和macOS)。

对于这些属性对应的连接字符串关键字,请参见DSN和连接字符串关键字和属性。 有关 Microsoft Entra ID 设置,请参阅 将 Microsoft Entra ID 与 ODBC 驱动程序配合使用。 关于 向量 类型,请参见 向量数据类型

在异步执行和线程之间选择

一些ODBC函数可以同步或异步运行。 在同步模式下,驱动程序直到服务器接听后才会返回控制权。 在异步模式下,驱动程序会立即返回 SQL_STILL_EXECUTING ,应用程序会用相同的参数重复同一调用,直到获得不同的返回码。 任何其他返回码,包括 SQL_ERROR,表示操作已完成。

异步模式有两种形式,你会使用其中一种。 使用 SQL_ASYNC_MODE 调用 SQLGetInfo,以确定驱动程序支持哪一种。 如果驱动程序支持按语句控制,则返回 SQL_AM_CONNECTION;如果该设置适用于整个连接,则返回 SQL_AM_NONE;如果驱动程序根本不以异步方式运行函数,则返回 SQL_AM_STATEMENT

语句形式会对一个语句句柄启用异步模式。 连接中的每一个语句保持同步,因此你可以同时运行两种语句:

SQLSetStmtAttr(hStmt, SQL_ATTR_ASYNC_ENABLE,
               (SQLPOINTER)SQL_ASYNC_ENABLE_ON, SQL_IS_INTEGER);

如果 SQL_ASYNC_MODE 返回 SQL_AM_CONNECTION,则该语句属性为只读,并且此次调用将返回 SQL_ERROR,SQLSTATE 为 HYC00。 改用连接表单吧。

连接表单会在你分配的每个语句句柄上开启异步模式。 它是否也会影响已存在的句柄取决于驱动程序,因此请在分配任何语句句柄之前先进行设置:

SQLSetConnectAttr(hDbc, SQL_ATTR_ASYNC_ENABLE,
                  (SQLPOINTER)SQL_ASYNC_ENABLE_ON, SQL_IS_INTEGER);

如果某个函数仍在该连接的某个语句上异步执行,则该调用返回 SQL_ERROR,并带有 SQLSTATE HY010。 光标开着本身不会阻止通话。 传递 SQL_ASYNC_ENABLE_OFF 会让连接上的每个语句回到同步模式。

要查找驱动程序在同一连接上同时支持多少异步语句,调用 SQLGetInfoSQL_MAX_ASYNC_CONCURRENT_STATEMENTS。 适用于 SQL Server 的 Microsoft ODBC Driver 18 返回 1,因此,应按每个连接只能有一个未完成的异步操作来规划;如果需要超过这一数量,则打开更多连接或使用线程。 参见异步执行(轮询方法)。

线程则是保持多个操作运行的另一种方式。 ODBC 要求多线程操作系统上的驱动程序必须是线程安全的,因此一个线程可以发出阻塞性的 ODBC 调用,而其他线程则继续工作。 这样可以避免异步模式所需的轮询循环和重复函数调用。 为每个线程分配各自的语句句柄。 驱动程序很可能会同时序列化两个使用相同句柄的线程,所以共享一个线程会让你失去并发性。 参见多线程。 优先使用线程处理新代码,并在转换已经运行的异步代码前先衡量自己的工作负载。

在 Windows 上,驱动管理器也支持通知方法,可以消除轮询循环。 你将 Win32 事件与连接或语句句柄关联。 该函数仍然会立即返回 SQL_STILL_EXECUTING ,驱动程序管理器在操作完成时会发出事件信号。 在此模式下,轮询已被禁用:再次调用原始函数会返回 SQL_ERROR,并带有 SQLSTATE IM017。 改为调用 SQLCompleteAsync 来获取结果。 这需要驱动管理器版本 ODBC 3.81 及以上版本,驱动也必须支持该版本。 打电话SQLGetInfoSQL_ASYNC_NOTIFICATION确认一下。 你获得的值取决于应用程序声明的 ODBC 版本:对于适用于 SQL Server 的 Microsoft ODBC Driver 18,将 SQL_ATTR_ODBC_VERSION 设置为 SQL_OV_ODBC3_80 的应用程序会得到 SQL_ASYNC_NOTIFICATION_CAPABLE,而声明 SQL_OV_ODBC3 的应用程序则会从同一驱动程序获得 SQL_ASYNC_NOTIFICATION_NOT_CAPABLE。 在分配连接之前,请先声明 SQL_OV_ODBC3_80。 参见 异步执行(通知方法)通知方法示例

取消一项未完成的行动

SQLCancel 取消仍在语句柄上运行的操作。 从另一个线程中调用它,或在轮询循环中调用它,并传入该未完成调用的句柄。

只用 SQLCancel 来做这个。 要放弃一个你不想再读取的结果集,请改为调用 SQLCloseCursorSQLMoreResults

从 sqlncli.h 迁移到 msodbcsql.h

SQL Server 原生客户端已停用,因此使用它的应用程序应迁移到适用于 SQL Server 的 Microsoft ODBC 驱动程序。 API 是同一个 ODBC API,所以大部分工作涉及重命名构建输入和 连接字符串 中的驱动名称。

SQL Server Native Client Microsoft ODBC Driver 18 for SQL Server
sqlncli.h msodbcsql.h
sqlncli11.lib msodbcsql18.lib
sqlncli11.dll msodbcsql18.dll
Driver={SQL Server Native Client 11.0} Driver={ODBC Driver 18 for SQL Server}
SQLNCLI_VER SQLODBC_VER

msodbcsql.h头部仍然定义SQLNCLI_*宏的名称,所以使用它们的源代码会继续编译。 这些定义由 #ifndef __sqlncli_h__ 保护,这意味着你不能在同一个翻译单元中同时包含这两个头文件。 移除 sqlncli.h include。

有两点是无法延续的:

  • 返回链接服务器及其目录列表的分布式查询元数据 API 函数未在 msodbcsql.h 中声明。 它们是针对SQL Server原生客户端的专用的。
  • 版本18默认加密连接并验证服务器证书。 原生客户端则没有这样做。 一个在 Native Client 中可正常工作的连接字符串,在首次连接时可能会失败,直到你修复证书信任问题或显式设置 Encrypt。 参见 连接加密故障排除

关于版本17到18的其他变更,请参见 主要版本差异