이 퀵스타트에서는 Visual Studio 2022 및 이후 버전으로 Windows C++ 콘솔 애플리케이션을 구축합니다. 이 애플리케이션은 Azure SQL Database, Microsoft Fabric의 SQL 데이터베이스, 또는 Microsoft OLE DB Driver 19 for SQL Server를 사용하는 SQL Server에 연결됩니다. 샘플 데이터에 대해 AdventureWorksLT 매개변수화된 쿼리를 실행하고 결과를 검증합니다.
Prerequisites
- Visual Studio 2022 및 이후 버전에서는 C++ 기반 데스크톱 개발 작업이 제공됩니다. 이 워크로드는 이 퀵스타트에 사용되는 x64 네이티브 툴 명령 프롬프트를 설치합니다. 열기 방법에 대해서는 ' 64비트 호스팅 개발자 명령 프롬프트 사용하기'를 참조하세요.
- Microsoft OLE DB Driver 19 for SQL Server. x64 드라이버를 설치하고 클라이언트 컴포넌트와 소프트웨어 개발 키트(SDK)를 모두 선택하세요.
SQL 데이터베이스 만들기
다음 플랫폼 중 하나에서 SQL 데이터베이스를 생성하거나 연결하세요:
이 퀵스타트에서는 AdventureWorksLT 샘플 데이터를 선택하거나 불러오세요.
SQL Server 컨테이너의 경우, 컨테이너를 만들고 샘플 데이터를 한 가지 명령어로 불러옵니다:
sqlcmd create mssql --accept-eula --using https://aka.ms/AdventureWorksLT.bak
기존 SQL Server 인스턴스의 경우, AdventureWorks 샘플 데이터베이스에서 백업을 AdventureWorksLT 복원하세요.
이 퀵스타트에 포함된 OLE DB 클라이언트 애플리케이션은 Windows에서 실행됩니다. SQL Server 컨테이너는 다른 지원되는 호스트에서 실행할 수 있습니다.
운전자를 확인해
Visual Studio 버전용 x64 네이티브 툴 명령 프롬프트를 열고 다음 명령을 실행하세요:
Important
이 퀵스타트의 명령어는 명령 프롬프트 문법을 사용합니다. 프롬프트가 >로 끝나는 x64 Native Tools 명령 프롬프트에서 실행하세요. 프롬프트가 PS로 시작하는 PowerShell에서는 실행하지 마세요.
reg query HKCR\MSOLEDBSQL19
명령어는 등록 MSOLEDBSQL19 된 제공자를 표시합니다.
연결 설정
x64 네이티브 도구 명령 프롬프트에서 OLEDB_CONNECTION_STRING을(를) 설정합니다. 애플리케이션은 환경에서 연결 문자열을 읽고 표시하지 않습니다.
Azure SQL Database 또는 Fabric의 SQL 데이터베이스는 Microsoft Entra 인터랙티브 인증을 사용하세요. 플레이스홀더를 서버, 데이터베이스, 그리고 SQL 리소스의 Microsoft Entra 사용자 ID로 대체하세요:
set "OLEDB_CONNECTION_STRING=Provider=MSOLEDBSQL19;Data Source=tcp:<server>,1433;Initial Catalog=<database>;Authentication=ActiveDirectoryInteractive;User ID=<user_id>;Use Encryption for Data=Mandatory;Trust Server Certificate=false;"
Fabric의 SQL 데이터베이스의 경우, 사용자 ID에는 데이터베이스 항목에 대한 Read 권한이 필요합니다. SQL 인증은 지원되지 않습니다. 자세한 내용은 Microsoft Fabric의 SQL 데이터베이스 인증을 참조 하세요.
Windows 인증을 지원하는 기존 SQL Server 인스턴스의 경우, 다음을 사용Integrated Security=SSPI하세요:
set "OLEDB_CONNECTION_STRING=Provider=MSOLEDBSQL19;Data Source=tcp:<server>,1433;Initial Catalog=<database>;Integrated Security=SSPI;Use Encryption for Data=Mandatory;Trust Server Certificate=false;"
SQL 인증을 받는 SQL Server 인스턴스나 컨테이너의 경우, 다음을 사용Authentication=SqlPassword하세요:
set "OLEDB_USER_ID=<user_id>"
set "OLEDB_PASSWORD=<password>"
set "OLEDB_CONNECTION_STRING=Provider=MSOLEDBSQL19;Data Source=tcp:<server>,1433;Initial Catalog=<database>;Authentication=SqlPassword;User ID=%OLEDB_USER_ID%;Password=%OLEDB_PASSWORD%;Use Encryption for Data=Mandatory;Trust Server Certificate=false;"
SQL Server 인증서는 서버 이름과 체인을 Windows 클라이언트가 신뢰하는 인증 기관(CA)과 일치해야 합니다. SQL Server 컨테이너의 경우, 컨테이너 내에서 전송 계층 보안(TLS)을 설정하고, 애플리케이션을 실행하기 전에 Windows 클라이언트에서 발행 CA를 등록하세요. 자세한 내용은 'Linux에서 SQL Server on Linux 연결 암호화 및 연결 암호화를 위한 SQL Server 데이터베이스 엔진 구성'을 참조하세요.
애플리케이션 만들기
프로젝트 디렉터리 만들기:
mkdir oledb-quickstart cd oledb-quickstart다음 코드를 사용하여
oledb-quickstart.cpp라는 파일을 만듭니다.#include <windows.h> #include <oledb.h> #include <msdasc.h> #include <msoledbsql.h> #include <cstddef> #include <iomanip> #include <iostream> #include <string> template <typename T> void Release(T*& pointer) { if (pointer != nullptr) { pointer->Release(); pointer = nullptr; } } struct ParameterData { DBSTATUS status; DBLENGTH length; LONG value; }; constexpr std::size_t productNameCharacters = 51; struct RowData { DBSTATUS productIdStatus; DBLENGTH productIdLength; LONG productId; DBSTATUS nameStatus; DBLENGTH nameLength; wchar_t name[productNameCharacters]; }; std::wstring ReadEnvironmentVariable(const wchar_t* name) { const DWORD length = GetEnvironmentVariableW(name, nullptr, 0); if (length == 0) return {}; std::wstring value(length, L'\0'); const DWORD copied = GetEnvironmentVariableW( name, value.data(), length); if (copied == 0 || copied >= length) return {}; value.resize(copied); return value; } int wmain() { const std::wstring connectionString = ReadEnvironmentVariable(L"OLEDB_CONNECTION_STRING"); if (connectionString.empty()) { std::wcerr << L"Set OLEDB_CONNECTION_STRING before running.\n"; return 1; } HRESULT result = CoInitializeEx(nullptr, COINIT_MULTITHREADED); if (FAILED(result)) { std::wcerr << L"COM initialization failed: 0x" << std::hex << result << L'\n'; return 1; } IDataInitialize* dataInitialize = nullptr; IDBInitialize* dbInitialize = nullptr; IDBCreateSession* createSession = nullptr; IDBCreateCommand* createCommand = nullptr; ICommandText* commandText = nullptr; ICommandWithParameters* commandParameters = nullptr; IAccessor* parameterAccessor = nullptr; IRowset* rowset = nullptr; IAccessor* rowAccessor = nullptr; HACCESSOR parameterHandle = DB_NULL_HACCESSOR; HACCESSOR rowHandle = DB_NULL_HACCESSOR; HROW* rows = nullptr; DBCOUNTITEM rowCount = 0; bool initialized = false; do { result = CoCreateInstance( CLSID_MSDAINITIALIZE, nullptr, CLSCTX_INPROC_SERVER, IID_IDataInitialize, reinterpret_cast<void**>(&dataInitialize)); if (FAILED(result)) break; result = dataInitialize->GetDataSource( nullptr, CLSCTX_INPROC_SERVER, connectionString.c_str(), IID_IDBInitialize, reinterpret_cast<IUnknown**>(&dbInitialize)); if (FAILED(result)) break; result = dbInitialize->Initialize(); if (FAILED(result)) break; initialized = true; result = dbInitialize->QueryInterface( IID_IDBCreateSession, reinterpret_cast<void**>(&createSession)); if (FAILED(result)) break; result = createSession->CreateSession( nullptr, IID_IDBCreateCommand, reinterpret_cast<IUnknown**>(&createCommand)); if (FAILED(result)) break; result = createCommand->CreateCommand( nullptr, IID_ICommandText, reinterpret_cast<IUnknown**>(&commandText)); if (FAILED(result)) break; result = commandText->SetCommandText( DBGUID_DBSQL, const_cast<wchar_t*>( L"SELECT TOP (5) ProductID, Name " L"FROM SalesLT.Product " L"WHERE ProductID > ? " L"ORDER BY ProductID;")); if (FAILED(result)) break; result = commandText->QueryInterface( IID_ICommandWithParameters, reinterpret_cast<void**>(&commandParameters)); if (FAILED(result)) break; DB_UPARAMS parameterOrdinal = 1; wchar_t parameterType[] = L"int"; DBPARAMBINDINFO parameterInfo = {}; parameterInfo.pwszDataSourceType = parameterType; parameterInfo.ulParamSize = sizeof(LONG); parameterInfo.dwFlags = DBPARAMFLAGS_ISINPUT; parameterInfo.bPrecision = 10; result = commandParameters->SetParameterInfo( 1, ¶meterOrdinal, ¶meterInfo); if (FAILED(result)) break; result = commandText->QueryInterface( IID_IAccessor, reinterpret_cast<void**>(¶meterAccessor)); if (FAILED(result)) break; DBBINDING parameterBinding = {}; parameterBinding.iOrdinal = 1; parameterBinding.obStatus = offsetof(ParameterData, status); parameterBinding.obLength = offsetof(ParameterData, length); parameterBinding.obValue = offsetof(ParameterData, value); parameterBinding.dwPart = DBPART_STATUS | DBPART_LENGTH | DBPART_VALUE; parameterBinding.dwMemOwner = DBMEMOWNER_CLIENTOWNED; parameterBinding.eParamIO = DBPARAMIO_INPUT; parameterBinding.cbMaxLen = sizeof(LONG); parameterBinding.wType = DBTYPE_I4; parameterBinding.bPrecision = 10; DBBINDSTATUS parameterBindStatus = DBBINDSTATUS_OK; result = parameterAccessor->CreateAccessor( DBACCESSOR_PARAMETERDATA, 1, ¶meterBinding, sizeof(ParameterData), ¶meterHandle, ¶meterBindStatus); if (FAILED(result) || parameterBindStatus != DBBINDSTATUS_OK) { if (SUCCEEDED(result)) result = E_FAIL; break; } ParameterData parameter = { DBSTATUS_S_OK, sizeof(LONG), 0 }; DBPARAMS parameters = { ¶meter, 1, parameterHandle }; result = commandText->Execute( nullptr, IID_IRowset, ¶meters, nullptr, reinterpret_cast<IUnknown**>(&rowset)); if (FAILED(result)) break; result = rowset->QueryInterface( IID_IAccessor, reinterpret_cast<void**>(&rowAccessor)); if (FAILED(result)) break; DBBINDING rowBindings[2] = {}; rowBindings[0].iOrdinal = 1; rowBindings[0].obStatus = offsetof(RowData, productIdStatus); rowBindings[0].obLength = offsetof(RowData, productIdLength); rowBindings[0].obValue = offsetof(RowData, productId); rowBindings[0].dwPart = DBPART_STATUS | DBPART_LENGTH | DBPART_VALUE; rowBindings[0].dwMemOwner = DBMEMOWNER_CLIENTOWNED; rowBindings[0].eParamIO = DBPARAMIO_NOTPARAM; rowBindings[0].cbMaxLen = sizeof(LONG); rowBindings[0].wType = DBTYPE_I4; rowBindings[0].bPrecision = 10; rowBindings[1].iOrdinal = 2; rowBindings[1].obStatus = offsetof(RowData, nameStatus); rowBindings[1].obLength = offsetof(RowData, nameLength); rowBindings[1].obValue = offsetof(RowData, name); rowBindings[1].dwPart = DBPART_STATUS | DBPART_LENGTH | DBPART_VALUE; rowBindings[1].dwMemOwner = DBMEMOWNER_CLIENTOWNED; rowBindings[1].eParamIO = DBPARAMIO_NOTPARAM; rowBindings[1].cbMaxLen = productNameCharacters * sizeof(wchar_t); rowBindings[1].wType = DBTYPE_WSTR; DBBINDSTATUS rowBindStatus[2] = { DBBINDSTATUS_OK, DBBINDSTATUS_OK }; result = rowAccessor->CreateAccessor( DBACCESSOR_ROWDATA, 2, rowBindings, sizeof(RowData), &rowHandle, rowBindStatus); if (FAILED(result) || rowBindStatus[0] != DBBINDSTATUS_OK || rowBindStatus[1] != DBBINDSTATUS_OK) { if (SUCCEEDED(result)) result = E_FAIL; break; } DBCOUNTITEM productsPrinted = 0; while (true) { result = rowset->GetNextRows( DB_NULL_HCHAPTER, 0, 1, &rowCount, &rows); if (FAILED(result) || rowCount == 0) break; RowData row = {}; result = rowset->GetData(rows[0], rowHandle, &row); if (FAILED(result) || row.productIdStatus != DBSTATUS_S_OK || row.productIdLength != sizeof(LONG) || row.nameStatus != DBSTATUS_S_OK || row.nameLength == 0 || row.nameLength % sizeof(wchar_t) != 0 || row.nameLength >= sizeof(row.name)) { result = E_FAIL; break; } row.name[row.nameLength / sizeof(wchar_t)] = L'\0'; if (productsPrinted == 0) { std::wcout << L"Connected with MSOLEDBSQL19.\n\n"; std::wcout << std::left << std::setw(12) << L"Product ID" << L"Name\n"; std::wcout << std::setw(12) << L"----------" << L"----\n"; } std::wcout << std::left << std::setw(12) << row.productId << row.name << L'\n'; ++productsPrinted; result = rowset->ReleaseRows( rowCount, rows, nullptr, nullptr, nullptr); CoTaskMemFree(rows); rows = nullptr; rowCount = 0; if (FAILED(result)) break; } if (FAILED(result)) break; if (productsPrinted == 0) { result = E_FAIL; break; } } while (false); if (rows != nullptr) { if (rowset != nullptr && rowCount != 0) rowset->ReleaseRows(rowCount, rows, nullptr, nullptr, nullptr); CoTaskMemFree(rows); } if (rowHandle != DB_NULL_HACCESSOR && rowAccessor != nullptr) rowAccessor->ReleaseAccessor(rowHandle, nullptr); if (parameterHandle != DB_NULL_HACCESSOR && parameterAccessor != nullptr) parameterAccessor->ReleaseAccessor(parameterHandle, nullptr); Release(rowAccessor); Release(rowset); Release(parameterAccessor); Release(commandParameters); Release(commandText); Release(createCommand); Release(createSession); if (initialized) dbInitialize->Uninitialize(); Release(dbInitialize); Release(dataInitialize); CoUninitialize(); if (FAILED(result)) { std::wcerr << L"OLE DB operation failed: 0x" << std::hex << result << L'\n'; return 1; } return 0; }
연결 문자열은 IDataInitialize::GetDataSource에 전달됩니다. 이 API는 공백 키워드 이름 Use Encryption for Data 과 Trust Server Certificate를 사용합니다. 연결 문자열은 암호화를 요청하고 인증서 검증을 요구합니다.
쿼리는 물음표를 매개변수 표시로 사용합니다. 애플리케이션은 최소 제품 ID 0 를 SQL Server int로 바인딩하고, 에서 최대 5행SalesLT.Product을 읽은 후 제품 ID와 이름을 출력합니다.
애플리케이션 빌드 및 실행
동일한 x64 Native Tools 명령 프롬프트에서 설치된 OLE DB SDK 헤더를 찾아 그 디렉터리를 다음 위치에
OLEDB_INCLUDE저장하세요:for /f "delims=" %i in ('where /r "%ProgramFiles%\Microsoft SQL Server\Client SDK\OLEDB" msoledbsql.h') do for %j in ("%~dpi.") do set "OLEDB_INCLUDE=%~fj"선택한 디렉터리를 표시하고, 헤더가 포함되어 있는지 확인하세요:
echo %OLEDB_INCLUDE% dir "%OLEDB_INCLUDE%\msoledbsql.h"애플리케이션을 컴파일합니다.
cl /std:c++17 /EHsc /W4 /I"%OLEDB_INCLUDE%" oledb-quickstart.cpp /link ole32.lib oleaut32.lib애플리케이션을 실행합니다.
oledb-quickstart.exe현재 명령 프롬프트에서 연결 문자열을 지우세요:
set OLEDB_CONNECTION_STRING= set OLEDB_USER_ID= set OLEDB_PASSWORD=
제품 행은 AdventureWorksLT 버전에 따라 다를 수 있습니다. 출력은 다음 예제와 유사합니다.
Connected with MSOLEDBSQL19.
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