Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1350 lines
37 KiB

  1. //-----------------------------------------------------------------------------
  2. //
  3. // Microsoft Windows
  4. // Copyright (C) Microsoft Corporation, 1992 - 1995.
  5. //
  6. // File: crowprov.cxx
  7. //
  8. // Contents: IProvider implementation for ADSI rowsets
  9. //
  10. // Functions:
  11. //
  12. // Notes:
  13. //
  14. //
  15. // History: 07/10/96 | RenatoB | Created, lifted most from EricJ code
  16. //-----------------------------------------------------------------------------
  17. // Includes
  18. #include "oleds.hxx"
  19. HRESULT
  20. PackLargeInteger(
  21. LARGE_INTEGER *plargeint,
  22. PVARIANT pVarDestObject
  23. );
  24. HRESULT
  25. PackDNWithBinary(
  26. PADS_DN_WITH_BINARY pDNWithBinary,
  27. PVARIANT pVarDestObject
  28. );
  29. HRESULT
  30. PackDNWithString(
  31. PADS_DN_WITH_STRING pDNWithString,
  32. PVARIANT pVarDestObject
  33. );
  34. //+---------------------------------------------------------------------------
  35. //
  36. // Function: CreateRowProvider
  37. //
  38. // Synopsis: @mfunc Creates and initializes a Row provider .
  39. //
  40. //----------------------------------------------------------------------------
  41. HRESULT
  42. CRowProvider::CreateRowProvider(
  43. IDirectorySearch * pDSSearch,
  44. LPWSTR pszFilter,
  45. LPWSTR * ppszAttrs,
  46. DWORD cAttrs,
  47. DBORDINAL cColumns, // count of the rowset's columns
  48. DBCOLUMNINFO * rgInfo, // array of cColumns DBCOLUMNINFO's
  49. OLECHAR* pStringsBuffer, // the names of the columns are here
  50. REFIID riid,
  51. BOOL * pMultiValued,
  52. BOOL fADSPathPresent,
  53. CCredentials * pCredentials,
  54. void ** ppvObj // the created Row provider
  55. )
  56. {
  57. HRESULT hr;
  58. CRowProvider * pRowProvider = NULL;
  59. if( ppvObj )
  60. *ppvObj = NULL;
  61. else
  62. BAIL_ON_FAILURE( hr = E_INVALIDARG );
  63. pRowProvider = new CRowProvider();
  64. if( pRowProvider == NULL )
  65. BAIL_ON_FAILURE( hr = E_OUTOFMEMORY );
  66. //
  67. //initialize rowprovider with the search filter and the columnsinfo
  68. //
  69. hr = pRowProvider->FInit(
  70. pDSSearch,
  71. pszFilter,
  72. ppszAttrs,
  73. cAttrs,
  74. cColumns,
  75. rgInfo,
  76. pStringsBuffer,
  77. pMultiValued,
  78. fADSPathPresent,
  79. pCredentials
  80. );
  81. if( FAILED(hr) ) {
  82. delete pRowProvider;
  83. BAIL_ON_FAILURE( hr );
  84. }
  85. //
  86. // This interface pointer is embedded in the pRowProvider.
  87. //
  88. pDSSearch = NULL;
  89. hr = pRowProvider->QueryInterface( riid, ppvObj);
  90. if( FAILED(hr) ) {
  91. delete pRowProvider;
  92. BAIL_ON_FAILURE( hr );
  93. }
  94. pRowProvider->Release();
  95. RRETURN( S_OK );
  96. error:
  97. if( pDSSearch )
  98. pDSSearch->Release();
  99. RRETURN( hr );
  100. }
  101. //+---------------------------------------------------------------------------
  102. //
  103. // Function: CRowProvider::CRowProvider
  104. //
  105. //----------------------------------------------------------------------------
  106. CRowProvider::CRowProvider()
  107. :
  108. _pMalloc (NULL),
  109. _cColumns (0),
  110. _ColInfo (NULL),
  111. _pwchBuf (NULL),
  112. _hSearchHandle (NULL),
  113. _pdbSearchCol (NULL),
  114. _pDSSearch (NULL),
  115. _pMultiValued (NULL),
  116. _fADSPathPresent (FALSE),
  117. _iAdsPathIndex (0),
  118. _pCredentials (NULL)
  119. {
  120. }
  121. //+---------------------------------------------------------------------------
  122. //
  123. // Function: CRowProvider::~CRowProvider
  124. //
  125. //----------------------------------------------------------------------------
  126. CRowProvider::~CRowProvider()
  127. {
  128. ULONG i;
  129. if( _hSearchHandle != NULL )
  130. _pDSSearch->CloseSearchHandle(_hSearchHandle);
  131. if( _pDSSearch != NULL )
  132. _pDSSearch->Release();
  133. // Release the memory allocated for columns and ColumnsInfo
  134. if (_pMalloc != NULL) {
  135. if( _pdbSearchCol != NULL ) {
  136. _pMalloc->Free((void*)_pdbSearchCol);
  137. }
  138. if( _ColInfo != NULL )
  139. _pMalloc->Free(_ColInfo);
  140. if( _pwchBuf != NULL )
  141. _pMalloc->Free(_pwchBuf);
  142. _pMalloc->Release();
  143. }
  144. if( _pMultiValued ) {
  145. FreeADsMem(_pMultiValued);
  146. }
  147. if( _pCredentials )
  148. delete _pCredentials;
  149. };
  150. //+---------------------------------------------------------------------------
  151. //
  152. // Function: CRowProvider::Finit
  153. //
  154. //----------------------------------------------------------------------------
  155. STDMETHODIMP
  156. CRowProvider::FInit(
  157. IDirectorySearch * pDSSearch,
  158. LPWSTR pszFilter,
  159. LPWSTR * ppszAttrs,
  160. DWORD cAttrs,
  161. DBORDINAL cColumns,
  162. DBCOLUMNINFO * rgInfo,
  163. OLECHAR * pStringsBuffer,
  164. BOOL * pMultiValued,
  165. BOOL fADSPathPresent,
  166. CCredentials * pCredentials)
  167. {
  168. HRESULT hr;
  169. ULONG i;
  170. ULONG cChars, cCharDispl;
  171. //
  172. // Asserts
  173. //
  174. ADsAssert(cColumns);
  175. ADsAssert(rgInfo);
  176. ADsAssert(pDSSearch);
  177. _cColumns= cColumns;
  178. hr = CoGetMalloc(MEMCTX_TASK, &_pMalloc);
  179. BAIL_ON_FAILURE( hr );
  180. _ColInfo = (DBCOLUMNINFO*)_pMalloc->Alloc((size_t)(cColumns *sizeof(DBCOLUMNINFO)));
  181. if( _ColInfo == NULL )
  182. BAIL_ON_FAILURE( hr=E_OUTOFMEMORY );
  183. memcpy(_ColInfo, rgInfo, (size_t)(cColumns * sizeof(DBCOLUMNINFO)));
  184. cChars = _pMalloc->GetSize(pStringsBuffer);
  185. _pwchBuf = (WCHAR*)_pMalloc->Alloc(cChars);
  186. if( _pwchBuf == NULL )
  187. BAIL_ON_FAILURE( hr=E_OUTOFMEMORY );
  188. memcpy(_pwchBuf, (void*)pStringsBuffer , cChars);
  189. for (i=0; i<_cColumns; i++) {
  190. if( rgInfo[i].pwszName ) {
  191. cCharDispl = (ULONG)(rgInfo[i].pwszName - pStringsBuffer);
  192. _ColInfo[i].pwszName = _pwchBuf + cCharDispl;
  193. _ColInfo[i].columnid.uName.pwszName = _pwchBuf + cCharDispl;
  194. }
  195. }
  196. // We have adspath at the end of the attribute list.
  197. _fADSPathPresent = fADSPathPresent ;
  198. //Store credentials if non-NULL.
  199. if( pCredentials ) {
  200. //We don't expect that _pCredentials is already non-NULL
  201. ADsAssert(_pCredentials == NULL);
  202. _pCredentials = new CCredentials(*pCredentials);
  203. if( !_pCredentials )
  204. BAIL_ON_FAILURE( hr=E_OUTOFMEMORY );
  205. }
  206. if( _fADSPathPresent == FALSE )
  207. cAttrs++;
  208. //
  209. // Create _pdbSearchCol, a member containing an array
  210. // of DB_SEARCH_COLUMN.
  211. // Reason for this is that trowset.cpp sometimes asks
  212. // for GetColumn twice: one to get the size of the column
  213. // and one to get the data.
  214. // Since OLEDP copies data, we do not want to have two copies
  215. // around
  216. //
  217. _pdbSearchCol = (PDB_SEARCH_COLUMN)_pMalloc->Alloc((ULONG)((cColumns + 1)*sizeof(DB_SEARCH_COLUMN)));
  218. if( _pdbSearchCol == NULL ) {
  219. hr = E_OUTOFMEMORY;
  220. goto error;
  221. }
  222. _pDSSearch = pDSSearch;
  223. hr = _pDSSearch->ExecuteSearch(
  224. pszFilter,
  225. ppszAttrs,
  226. cAttrs,
  227. &_hSearchHandle
  228. );
  229. BAIL_ON_FAILURE( hr );
  230. _pMultiValued = pMultiValued;
  231. RRETURN( hr );
  232. error:
  233. if( _pMalloc != NULL ) {
  234. if( _pdbSearchCol != NULL ) {
  235. _pMalloc->Free((void*)_pdbSearchCol);
  236. _pdbSearchCol= NULL;
  237. };
  238. if( _ColInfo != NULL ) {
  239. _pMalloc->Free(_ColInfo);
  240. _ColInfo = NULL;
  241. }
  242. if( _pwchBuf != NULL ) {
  243. _pMalloc->Free(_pwchBuf);
  244. _pwchBuf = NULL;
  245. }
  246. _pMalloc->Release();
  247. _pMalloc = NULL;
  248. };
  249. if (_hSearchHandle != NULL)
  250. _pDSSearch->CloseSearchHandle(_hSearchHandle);
  251. _hSearchHandle = NULL;
  252. _pDSSearch = NULL;
  253. RRETURN( hr );
  254. }
  255. //+---------------------------------------------------------------------------
  256. //
  257. // Function: CRowProvider::QueryInterface
  258. //
  259. //----------------------------------------------------------------------------
  260. STDMETHODIMP
  261. CRowProvider::QueryInterface(
  262. REFIID riid,
  263. LPVOID * ppv)
  264. {
  265. if( !ppv )
  266. RRETURN( E_INVALIDARG );
  267. if( riid == IID_IUnknown
  268. || riid == IID_IRowProvider )
  269. *ppv = (IRowProvider FAR *) this;
  270. else if( riid == IID_IColumnsInfo )
  271. *ppv = (IColumnsInfo FAR *) this;
  272. else {
  273. *ppv = NULL;
  274. return E_NOINTERFACE;
  275. }
  276. AddRef();
  277. RRETURN( S_OK );
  278. }
  279. //-----------------------------------------------------------------------------
  280. //
  281. // Function: CRowProvider::NextRow
  282. //
  283. // Synopsis: Advance to the next row.
  284. //
  285. // Called by: Client.
  286. // Called when: To advance to next row.
  287. // This sets the "current" row.
  288. // Initially the "current" row is prior to the first actual row.
  289. // (Which means This must be called prior to the first GetColumn call.)
  290. //
  291. //----------------------------------------------------------------------------
  292. STDMETHODIMP
  293. CRowProvider::NextRow()
  294. {
  295. HRESULT hr;
  296. ULONG i;
  297. DWORD dwType, dwExtError = ERROR_SUCCESS;
  298. VARTYPE vType = VT_NULL;
  299. const int ERROR_BUF_SIZE = 512;
  300. const int NAME_BUF_SIZE = 128;
  301. WCHAR ErrorBuf[ERROR_BUF_SIZE];
  302. WCHAR NameBuf[NAME_BUF_SIZE];
  303. do {
  304. // Clear the ADSI extended error, so that after the call to GetNextRow,
  305. // we can safely check if an extended error was set.
  306. ADsSetLastError(ERROR_SUCCESS, NULL, NULL);
  307. dwExtError = ERROR_SUCCESS;
  308. //
  309. // read the next row
  310. //
  311. hr = _pDSSearch->GetNextRow(
  312. _hSearchHandle
  313. );
  314. // we should treat SIZE_LIMIT_EXCEEDED error message as
  315. // S_ADS_NOMORE_ROWS
  316. // in the future, we might want to return this error message
  317. // to the user under non-paged search situation
  318. if (LIMIT_EXCEEDED_ERROR(hr))
  319. hr = S_ADS_NOMORE_ROWS;
  320. BAIL_ON_FAILURE( hr );
  321. if (hr == S_ADS_NOMORE_ROWS)
  322. {
  323. // check if more results are likely (pagedTimeLimit search). If so,
  324. // we will keep trying till a row is obtained.
  325. hr = ADsGetLastError(&dwExtError, ErrorBuf, ERROR_BUF_SIZE,
  326. NameBuf, NAME_BUF_SIZE);
  327. BAIL_ON_FAILURE(hr);
  328. if (dwExtError != ERROR_MORE_DATA)
  329. // we really have no more data
  330. RRETURN(DB_S_ENDOFROWSET);
  331. }
  332. } while(ERROR_MORE_DATA == dwExtError);
  333. //
  334. //read all the columnsinto _pdbSearchCol leaving the bookmark column
  335. //
  336. for (i=1; i<_cColumns; i++) {
  337. hr = _pDSSearch->GetColumn(
  338. _hSearchHandle,
  339. _ColInfo[i].pwszName,
  340. &(_pdbSearchCol[i].adsColumn)
  341. );
  342. if (FAILED(hr) && hr != E_ADS_COLUMN_NOT_SET)
  343. goto error;
  344. if (hr == E_ADS_COLUMN_NOT_SET ||
  345. _pdbSearchCol[i].adsColumn.dwNumValues == 0) {
  346. _pdbSearchCol[i].dwStatus = DBSTATUS_S_ISNULL;
  347. _pdbSearchCol[i].dwType = DBTYPE_EMPTY;
  348. _pdbSearchCol[i].dwLength = 0;
  349. hr = S_OK;
  350. }
  351. else if (_ColInfo[i].wType == (DBTYPE_VARIANT | DBTYPE_BYREF)) {
  352. _pdbSearchCol[i].dwStatus = DBSTATUS_S_OK;
  353. _pdbSearchCol[i].dwType = _ColInfo[i].wType;
  354. _pdbSearchCol[i].dwLength = sizeof(VARIANT);
  355. }
  356. else if ((ULONG) _pdbSearchCol[i].adsColumn.dwADsType >= g_cMapADsTypeToDBType ||
  357. g_MapADsTypeToDBType[_pdbSearchCol[i].adsColumn.dwADsType].wType == DBTYPE_NULL) {
  358. _pdbSearchCol[i].dwStatus = DBSTATUS_E_CANTCONVERTVALUE;
  359. _pdbSearchCol[i].dwType = DBTYPE_EMPTY;
  360. _pdbSearchCol[i].dwLength = 0;
  361. }
  362. else {
  363. _pdbSearchCol[i].dwStatus = DBSTATUS_S_OK;
  364. _pdbSearchCol[i].dwType = g_MapADsTypeToDBType[_pdbSearchCol[i].adsColumn.dwADsType].wType;
  365. switch (_pdbSearchCol[i].dwType & ~DBTYPE_BYREF) {
  366. case DBTYPE_WSTR:
  367. _pdbSearchCol[i].dwLength =
  368. (wcslen( _pdbSearchCol[i].adsColumn.pADsValues[0].CaseIgnoreString)) *
  369. sizeof (WCHAR);
  370. break;
  371. case DBTYPE_BYTES:
  372. if(_pdbSearchCol[i].adsColumn.dwADsType == ADSTYPE_OCTET_STRING)
  373. _pdbSearchCol[i].dwLength =
  374. _pdbSearchCol[i].adsColumn.pADsValues[0].OctetString.dwLength;
  375. else if(_pdbSearchCol[i].adsColumn.dwADsType ==
  376. ADSTYPE_NT_SECURITY_DESCRIPTOR)
  377. _pdbSearchCol[i].dwLength =
  378. _pdbSearchCol[i].adsColumn.pADsValues[0].SecurityDescriptor.dwLength;
  379. else if(_pdbSearchCol[i].adsColumn.dwADsType ==
  380. ADSTYPE_PROV_SPECIFIC)
  381. _pdbSearchCol[i].dwLength =
  382. _pdbSearchCol[i].adsColumn.pADsValues[0].ProviderSpecific.dwLength;
  383. break;
  384. default:
  385. _pdbSearchCol[i].dwLength = g_MapADsTypeToDBType[_pdbSearchCol[i].adsColumn.dwADsType].ulSize;
  386. }
  387. }
  388. }
  389. if ((FALSE == _fADSPathPresent))
  390. {
  391. hr = _pDSSearch->GetColumn(
  392. _hSearchHandle,
  393. L"AdsPath",
  394. &(_pdbSearchCol[i].adsColumn)
  395. );
  396. if FAILED(hr)
  397. goto error;
  398. }
  399. RRETURN(hr);
  400. error:
  401. RRETURN(hr);
  402. }
  403. //-----------------------------------------------------------------------------
  404. //
  405. // Function: CRowProvider::GetColumn
  406. //
  407. // Synopsis: @mfunc Get a column value.
  408. //
  409. // We only provide a ptr to the value -- retained in our memory
  410. // space.
  411. //
  412. // Called by: Client.
  413. // Called when: After NextRow, once for each column.
  414. //
  415. //----------------------------------------------------------------------------
  416. STDMETHODIMP
  417. CRowProvider::GetColumn(
  418. ULONG iCol,
  419. DBSTATUS *pdbStatus,
  420. ULONG *pdwLength,
  421. BYTE *pbData
  422. )
  423. {
  424. DBTYPE columnType = 0;
  425. DBSTATUS dbStatus_temp = 0;
  426. BOOL is_Ref = FALSE;
  427. HRESULT hr = S_OK;
  428. ADsAssert( 1 <= iCol && iCol <= _cColumns );
  429. //
  430. // Note that the caller gives us a ptr to where to put the data.
  431. // We can fill in dwStatus, dwLength.
  432. // For pbData, we assume this is a ptr to where we are to write our ptr.
  433. //
  434. columnType = _ColInfo[iCol].wType;
  435. if ((columnType & DBTYPE_ARRAY) || (columnType & DBTYPE_VECTOR) ) {
  436. if (pdbStatus != NULL)
  437. *pdbStatus= DBSTATUS_E_UNAVAILABLE;
  438. if (pdwLength != NULL)
  439. *pdwLength = 0;
  440. RRETURN(DB_S_ERRORSOCCURRED);
  441. }
  442. if (pdwLength!= NULL)
  443. *pdwLength = _pdbSearchCol[iCol].dwLength;
  444. dbStatus_temp = _pdbSearchCol[iCol].dwStatus;
  445. if (columnType & DBTYPE_BYREF)
  446. is_Ref = TRUE;
  447. columnType &= (~DBTYPE_BYREF);
  448. if (pbData != NULL && dbStatus_temp == DBSTATUS_S_OK) {
  449. switch (columnType) {
  450. case DBTYPE_BOOL:
  451. * (VARIANT_BOOL*) pbData = _pdbSearchCol[iCol].adsColumn.pADsValues[0].Boolean ?
  452. VARIANT_TRUE: VARIANT_FALSE;
  453. break;
  454. case DBTYPE_I4:
  455. * (DWORD*) pbData = _pdbSearchCol[iCol].adsColumn.pADsValues[0].Integer;
  456. break;
  457. case DBTYPE_WSTR:
  458. *(WCHAR**)pbData = _pdbSearchCol[iCol].adsColumn.pADsValues[0].CaseIgnoreString;
  459. break;
  460. case DBTYPE_BYTES:
  461. if(_pdbSearchCol[iCol].adsColumn.dwADsType ==
  462. ADSTYPE_OCTET_STRING)
  463. *(BYTE**)pbData = _pdbSearchCol[iCol].adsColumn.pADsValues[0].OctetString.lpValue;
  464. else if(_pdbSearchCol[iCol].adsColumn.dwADsType ==
  465. ADSTYPE_NT_SECURITY_DESCRIPTOR)
  466. *(BYTE**)pbData = _pdbSearchCol[iCol].adsColumn.pADsValues[0].SecurityDescriptor.lpValue;
  467. else if(_pdbSearchCol[iCol].adsColumn.dwADsType ==
  468. ADSTYPE_PROV_SPECIFIC)
  469. *(BYTE**)pbData = _pdbSearchCol[iCol].adsColumn.pADsValues[0].ProviderSpecific.lpValue;
  470. break;
  471. case DBTYPE_DATE:
  472. {
  473. double date = 0;
  474. hr = SystemTimeToVariantTime(
  475. &_pdbSearchCol[iCol].adsColumn.pADsValues[0].UTCTime,
  476. &date);
  477. if( FAILED(hr) )
  478. if (pdbStatus != NULL)
  479. *pdbStatus= DBSTATUS_E_CANTCONVERTVALUE;
  480. BAIL_ON_FAILURE(hr);
  481. *(double*)pbData = date;
  482. break;
  483. }
  484. case DBTYPE_VARIANT:
  485. if (_pMultiValued[iCol] == FALSE) {
  486. PVARIANT pVariant = (PVARIANT) AllocADsMem(sizeof(VARIANT));
  487. if (!pVariant) {
  488. if (pdbStatus != NULL)
  489. *pdbStatus= DBSTATUS_E_CANTCONVERTVALUE;
  490. hr = E_OUTOFMEMORY;
  491. BAIL_ON_FAILURE(hr);
  492. }
  493. if(_pdbSearchCol[iCol].adsColumn.dwADsType ==
  494. ADSTYPE_LARGE_INTEGER)
  495. hr = PackLargeInteger(
  496. &_pdbSearchCol[iCol].adsColumn.pADsValues[0].LargeInteger, pVariant);
  497. else if(_pdbSearchCol[iCol].adsColumn.dwADsType ==
  498. ADSTYPE_DN_WITH_BINARY)
  499. hr = PackDNWithBinary(_pdbSearchCol[iCol].adsColumn.pADsValues[0].pDNWithBinary, pVariant);
  500. else if(_pdbSearchCol[iCol].adsColumn.dwADsType ==
  501. ADSTYPE_DN_WITH_STRING)
  502. hr = PackDNWithString(_pdbSearchCol[iCol].adsColumn.pADsValues[0].pDNWithString, pVariant);
  503. if( FAILED(hr) )
  504. if (pdbStatus != NULL)
  505. *pdbStatus= DBSTATUS_E_CANTCONVERTVALUE;
  506. BAIL_ON_FAILURE(hr);
  507. *((PVARIANT*)pbData) = pVariant;
  508. }
  509. else {
  510. hr = CopyADs2VariantArray(
  511. &_pdbSearchCol[iCol].adsColumn,
  512. (PVARIANT *) pbData
  513. );
  514. if (hr == E_ADS_CANT_CONVERT_DATATYPE) {
  515. dbStatus_temp= DBSTATUS_E_UNAVAILABLE;
  516. break;
  517. }
  518. if( FAILED(hr) )
  519. if (pdbStatus != NULL)
  520. *pdbStatus= DBSTATUS_E_CANTCONVERTVALUE;
  521. BAIL_ON_FAILURE(hr);
  522. }
  523. break;
  524. default:
  525. dbStatus_temp= DBSTATUS_E_UNAVAILABLE;
  526. break;
  527. };
  528. };
  529. if (pdbStatus == 0)
  530. RRETURN(S_OK);
  531. if (pdbStatus != NULL)
  532. *pdbStatus = dbStatus_temp;
  533. if (dbStatus_temp == DBSTATUS_S_OK || dbStatus_temp == DBSTATUS_S_ISNULL)
  534. RRETURN(S_OK);
  535. else
  536. RRETURN(DB_S_ERRORSOCCURRED);
  537. error:
  538. RRETURN(hr);
  539. }
  540. HRESULT CRowProvider::GetIndex(
  541. IColumnsInfo* pColumnsInfo,
  542. LPWSTR lpwszColName,
  543. int& iIndex
  544. )
  545. {
  546. #if (!defined(BUILD_FOR_NT40))
  547. HRESULT hr = S_OK;
  548. int iColumn;
  549. DBCOLUMNINFO* pColumnInfo = NULL;
  550. DBORDINAL cColumns = 0;
  551. OLECHAR* pStringsBuffer = NULL;
  552. iIndex = 0;
  553. hr = pColumnsInfo->GetColumnInfo(&cColumns, &pColumnInfo, &pStringsBuffer);
  554. BAIL_ON_FAILURE(hr);
  555. for(iColumn = 0; iColumn < cColumns; iColumn++)
  556. {
  557. if(pColumnInfo[iColumn].pwszName == NULL || lpwszColName == NULL)
  558. continue;
  559. if(!_wcsicmp(pColumnInfo[iColumn].pwszName, lpwszColName))
  560. {
  561. iIndex = iColumn;
  562. break;
  563. }
  564. }
  565. if (pColumnInfo)
  566. _pMalloc->Free((void*)pColumnInfo);
  567. if (pStringsBuffer)
  568. _pMalloc->Free((void*)pStringsBuffer);
  569. RRETURN(S_OK);
  570. error:
  571. if (pColumnInfo)
  572. _pMalloc->Free((void*)pColumnInfo);
  573. if (pStringsBuffer)
  574. _pMalloc->Free((void*)pStringsBuffer);
  575. RRETURN(hr);
  576. #else
  577. RRETURN(E_FAIL);
  578. #endif
  579. }
  580. STDMETHODIMP CRowProvider::GetURLFromHROW(
  581. HROW hRow,
  582. LPOLESTR *ppwszURL,
  583. IRowset* pRowset
  584. )
  585. {
  586. #if (!defined(BUILD_FOR_NT40))
  587. HRESULT hr = S_OK;
  588. auto_rel<IAccessor> pAccessor;
  589. auto_rel<IColumnsInfo> pColumnsInfo;
  590. HACCESSOR hAccessor = NULL;
  591. DBBINDING Bindings[1];
  592. CComVariant varData;
  593. if ((NULL == hRow) || (NULL == ppwszURL) || (NULL == pRowset))
  594. RRETURN(E_INVALIDARG);
  595. *ppwszURL = NULL;
  596. // If adspath is in the list of columns selected
  597. // return that value.
  598. if (_fADSPathPresent)
  599. {
  600. VariantInit(&varData);
  601. Bindings[0].dwPart = DBPART_VALUE;
  602. Bindings[0].dwMemOwner = DBMEMOWNER_CLIENTOWNED;
  603. Bindings[0].eParamIO = DBPARAMIO_NOTPARAM;
  604. Bindings[0].wType = DBTYPE_VARIANT;
  605. Bindings[0].pTypeInfo = NULL;
  606. Bindings[0].obValue = 0;
  607. Bindings[0].bPrecision = 0;
  608. Bindings[0].bScale = 0;
  609. Bindings[0].cbMaxLen = sizeof(VARIANT);
  610. Bindings[0].pObject = NULL;
  611. Bindings[0].pBindExt = NULL;
  612. Bindings[0].dwFlags = 0;
  613. hr = pRowset->QueryInterface(IID_IAccessor, (void**)&pAccessor);
  614. BAIL_ON_FAILURE(hr);
  615. if (_iAdsPathIndex == 0)
  616. {
  617. hr = pRowset->QueryInterface(__uuidof(IColumnsInfo), (void **)&pColumnsInfo);
  618. BAIL_ON_FAILURE(hr);
  619. hr = GetIndex(pColumnsInfo, L"AdsPath", _iAdsPathIndex);
  620. if (0 == _iAdsPathIndex)
  621. hr = E_UNEXPECTED;
  622. BAIL_ON_FAILURE(hr);
  623. }
  624. Bindings[0].iOrdinal = _iAdsPathIndex;
  625. Bindings[0].obValue = NULL;
  626. hr = pAccessor->CreateAccessor(DBACCESSOR_ROWDATA,sizeof(Bindings)/sizeof(Bindings[0]) , Bindings, 0, &hAccessor, NULL);
  627. BAIL_ON_FAILURE(hr);
  628. hr = pRowset->GetData(hRow, hAccessor, &varData);
  629. BAIL_ON_FAILURE(hr);
  630. ADsAssert(varData.vt == VT_BSTR);
  631. ADsAssert(varData.bstrVal);
  632. // allocate the string and copy data
  633. *ppwszURL = (LPWSTR ) _pMalloc->Alloc(sizeof(WCHAR) * (wcslen(varData.bstrVal) + 1));
  634. if (NULL == *ppwszURL)
  635. {
  636. hr = E_OUTOFMEMORY;
  637. BAIL_ON_FAILURE(hr);
  638. }
  639. wcscpy(*ppwszURL, varData.bstrVal);
  640. if (hAccessor)
  641. pAccessor->ReleaseAccessor(hAccessor, NULL);
  642. }
  643. else
  644. {
  645. ADS_CASE_IGNORE_STRING padsPath;
  646. hr = ((CRowset *)pRowset)->GetADsPathFromHROW(hRow, &padsPath);
  647. BAIL_ON_FAILURE(hr);
  648. *ppwszURL = (LPWSTR ) _pMalloc->Alloc(sizeof(WCHAR) *
  649. (wcslen(padsPath) +1));
  650. if (NULL == *ppwszURL)
  651. {
  652. hr = E_OUTOFMEMORY;
  653. BAIL_ON_FAILURE(hr);
  654. }
  655. wcscpy(*ppwszURL, padsPath);
  656. }
  657. RRETURN(S_OK);
  658. error:
  659. if (hAccessor)
  660. pAccessor->ReleaseAccessor(hAccessor, NULL);
  661. RRETURN(hr);
  662. #else
  663. RRETURN(E_FAIL);
  664. #endif
  665. }
  666. //+---------------------------------------------------------------------------
  667. //
  668. // Function: CRowProvider::GetColumnInfo
  669. //
  670. // Synopsis: @mfunc Get Column Info.
  671. //
  672. //----------------------------------------------------------------------------
  673. STDMETHODIMP
  674. CRowProvider::GetColumnInfo(
  675. DBORDINAL * pcColumns,
  676. DBCOLUMNINFO ** pprgInfo,
  677. WCHAR ** ppStringsBuffer
  678. )
  679. {
  680. DBORDINAL i;
  681. ULONG cChars, cCharDispl;
  682. HRESULT hr = S_OK;
  683. DBCOLUMNINFO * prgInfo = NULL;
  684. WCHAR * pStrBuffer = NULL;
  685. //
  686. // Asserts
  687. //
  688. ADsAssert(_pMalloc);
  689. ADsAssert(_cColumns);
  690. ADsAssert(_ColInfo);
  691. ADsAssert(_pwchBuf);
  692. if( pcColumns )
  693. *pcColumns = 0;
  694. if( pprgInfo )
  695. *pprgInfo = NULL;
  696. if( ppStringsBuffer )
  697. *ppStringsBuffer = NULL;
  698. if( (pcColumns == NULL) || (pprgInfo == NULL) || (ppStringsBuffer == NULL) )
  699. BAIL_ON_FAILURE( hr=E_INVALIDARG );
  700. prgInfo = (DBCOLUMNINFO*)_pMalloc->Alloc((ULONG)(_cColumns * sizeof(DBCOLUMNINFO)));
  701. if( prgInfo == NULL )
  702. BAIL_ON_FAILURE( hr=E_OUTOFMEMORY );
  703. memcpy(prgInfo, _ColInfo, (size_t)(_cColumns * sizeof(DBCOLUMNINFO)));
  704. cChars = _pMalloc->GetSize(_pwchBuf);
  705. pStrBuffer = (WCHAR*)_pMalloc->Alloc(cChars);
  706. if( pStrBuffer == NULL )
  707. BAIL_ON_FAILURE( hr=E_OUTOFMEMORY );
  708. memcpy(pStrBuffer, (void*)_pwchBuf , cChars);
  709. for (i=1; i<_cColumns; i++) {
  710. cCharDispl = (ULONG)(_ColInfo[i].pwszName - _pwchBuf);
  711. prgInfo[i].pwszName = pStrBuffer + cCharDispl;
  712. prgInfo[i].columnid.uName.pwszName = pStrBuffer + cCharDispl;
  713. };
  714. *pcColumns = _cColumns;
  715. *pprgInfo = prgInfo;
  716. *ppStringsBuffer = pStrBuffer;
  717. RRETURN( S_OK );
  718. error:
  719. if( !prgInfo )
  720. _pMalloc->Free(prgInfo);
  721. if( pStrBuffer != NULL )
  722. _pMalloc->Free(pStrBuffer);
  723. RRETURN( hr );
  724. };
  725. //+---------------------------------------------------------------------------
  726. //
  727. // Function: CRowProvider::MapColumnIDs
  728. //
  729. // Synopsis: @mfunc Map Column IDs.
  730. //
  731. //----------------------------------------------------------------------------
  732. STDMETHODIMP
  733. CRowProvider::MapColumnIDs(
  734. DBORDINAL cColumnIDs,
  735. const DBID rgColumnIDs[],
  736. DBORDINAL rgColumns[]
  737. )
  738. {
  739. ULONG found = 0;
  740. DBORDINAL i;
  741. DBORDINAL cValidCols = 0;
  742. //
  743. // No Column IDs are set when GetColumnInfo returns ColumnsInfo structure.
  744. // Hence, any value of ID will not match with any column
  745. //
  746. DBORDINAL iCol;
  747. //
  748. // No-Op if cColumnIDs is 0
  749. //
  750. if( cColumnIDs == 0 )
  751. RRETURN( S_OK );
  752. // Spec-defined checks.
  753. // Note that this guarantees we can access rgColumnIDs[] in loop below.
  754. // (Because we'll just fall through.)
  755. if( cColumnIDs && (!rgColumnIDs || !rgColumns) )
  756. RRETURN( E_INVALIDARG );
  757. //
  758. // Set the columns ordinals to invalid values
  759. //
  760. for (iCol=0; iCol < cColumnIDs; iCol++) {
  761. // Initialize
  762. rgColumns[iCol] = DB_INVALIDCOLUMN;
  763. //
  764. // The columnid with the Bookmark or the same name
  765. //
  766. if( rgColumnIDs[iCol].eKind == DBKIND_GUID_PROPID &&
  767. rgColumnIDs[iCol].uGuid.guid == DBCOL_SPECIALCOL &&
  768. rgColumnIDs[iCol].uName.ulPropid == 2 ) {
  769. rgColumns[iCol] = 0;
  770. cValidCols++;
  771. continue;
  772. }
  773. //
  774. // The columnid with the Column Name
  775. //
  776. if( rgColumnIDs[iCol].eKind == DBKIND_NAME &&
  777. rgColumnIDs[iCol].uName.pwszName ) {
  778. //
  779. // Find the name in the list of Attributes
  780. //
  781. for (ULONG iOrdinal=0; iOrdinal < _cColumns; iOrdinal++) {
  782. if( _ColInfo[iOrdinal].columnid.eKind == DBKIND_NAME &&
  783. !_wcsicmp(_ColInfo[iOrdinal].columnid.uName.pwszName,
  784. rgColumnIDs[iCol].uName.pwszName) ) {
  785. rgColumns[iCol] = iOrdinal;
  786. cValidCols++;
  787. break;
  788. }
  789. }
  790. }
  791. }
  792. if( cValidCols == 0 )
  793. RRETURN( DB_E_ERRORSOCCURRED );
  794. else if( cValidCols < cColumnIDs )
  795. RRETURN( DB_S_ERRORSOCCURRED );
  796. else
  797. RRETURN( S_OK );
  798. }
  799. STDMETHODIMP
  800. CRowProvider::CopyADs2VariantArray(
  801. PADS_SEARCH_COLUMN pADsColumn,
  802. PVARIANT *ppVariant
  803. )
  804. {
  805. SAFEARRAY *aList = NULL;
  806. SAFEARRAYBOUND aBound;
  807. VARTYPE vType = VT_NULL;
  808. HRESULT hr = S_OK;
  809. ULONG i;
  810. PVARIANT pVariant = NULL, pVarArray = NULL;
  811. ADsAssert(ppVariant);
  812. *ppVariant = NULL;
  813. aBound.lLbound = 0;
  814. aBound.cElements = pADsColumn->dwNumValues;
  815. pVariant = (PVARIANT) AllocADsMem(sizeof(VARIANT));
  816. if (!pVariant) {
  817. RRETURN(E_OUTOFMEMORY);
  818. }
  819. if ((ULONG) pADsColumn->dwADsType >= g_cMapADsTypeToVarType ||
  820. (vType = g_MapADsTypeToVarType[pADsColumn->dwADsType]) == VT_NULL) {
  821. BAIL_ON_FAILURE(hr = E_ADS_CANT_CONVERT_DATATYPE);
  822. }
  823. aList = SafeArrayCreate( VT_VARIANT, 1, &aBound );
  824. if (aList == NULL)
  825. BAIL_ON_FAILURE(hr = E_OUTOFMEMORY);
  826. hr = SafeArrayAccessData( aList, (void **) &pVarArray );
  827. if (FAILED(hr)) {
  828. SafeArrayDestroy( aList );
  829. goto error;
  830. }
  831. for (i=0; i<aBound.cElements; i++) {
  832. (V_VT(pVarArray+i)) = vType;
  833. switch (vType) {
  834. case VT_I4:
  835. V_I4(pVarArray+i) = pADsColumn->pADsValues[i].Integer;
  836. break;
  837. case VT_DISPATCH:
  838. if(pADsColumn->dwADsType == ADSTYPE_LARGE_INTEGER)
  839. hr = PackLargeInteger(&pADsColumn->pADsValues[i].LargeInteger,
  840. pVarArray+i);
  841. else if(pADsColumn->dwADsType == ADSTYPE_DN_WITH_BINARY)
  842. hr = PackDNWithBinary(pADsColumn->pADsValues[i].pDNWithBinary,
  843. pVarArray+i);
  844. else if(pADsColumn->dwADsType == ADSTYPE_DN_WITH_STRING)
  845. hr = PackDNWithString(pADsColumn->pADsValues[i].pDNWithString,
  846. pVarArray+i);
  847. BAIL_ON_FAILURE(hr);
  848. break;
  849. case VT_BOOL:
  850. V_I4(pVarArray+i) = pADsColumn->pADsValues[i].Boolean ?
  851. VARIANT_TRUE: VARIANT_FALSE;
  852. break;
  853. case VT_BSTR:
  854. hr = ADsAllocString (
  855. pADsColumn->pADsValues[i].CaseIgnoreString,
  856. &(V_BSTR(pVarArray+i))
  857. );
  858. if (FAILED(hr)) {
  859. SafeArrayUnaccessData( aList );
  860. SafeArrayDestroy( aList );
  861. goto error;
  862. }
  863. break;
  864. case VT_DATE:
  865. {
  866. double date = 0;
  867. hr = SystemTimeToVariantTime(
  868. &pADsColumn->pADsValues[i].UTCTime,
  869. &date);
  870. BAIL_ON_FAILURE(hr);
  871. V_DATE(pVarArray+i)= date;
  872. break;
  873. }
  874. case (VT_UI1 | VT_ARRAY):
  875. VariantInit(pVarArray+i);
  876. if(pADsColumn->dwADsType == ADSTYPE_OCTET_STRING)
  877. hr = BinaryToVariant(
  878. pADsColumn->pADsValues[i].OctetString.dwLength,
  879. pADsColumn->pADsValues[i].OctetString.lpValue,
  880. pVarArray+i);
  881. else if(pADsColumn->dwADsType == ADSTYPE_NT_SECURITY_DESCRIPTOR)
  882. hr = BinaryToVariant(
  883. pADsColumn->pADsValues[i].SecurityDescriptor.dwLength,
  884. pADsColumn->pADsValues[i].SecurityDescriptor.lpValue,
  885. pVarArray+i);
  886. else if(pADsColumn->dwADsType == ADSTYPE_PROV_SPECIFIC)
  887. hr = BinaryToVariant(
  888. pADsColumn->pADsValues[i].ProviderSpecific.dwLength,
  889. pADsColumn->pADsValues[i].ProviderSpecific.lpValue,
  890. pVarArray+i);
  891. BAIL_ON_FAILURE(hr);
  892. break;
  893. default:
  894. SafeArrayUnaccessData( aList );
  895. SafeArrayDestroy( aList );
  896. BAIL_ON_FAILURE(hr = E_ADS_CANT_CONVERT_DATATYPE);
  897. break;
  898. }
  899. }
  900. SafeArrayUnaccessData( aList );
  901. V_VT((PVARIANT)pVariant) = VT_ARRAY | VT_VARIANT;
  902. V_ARRAY((PVARIANT)pVariant) = aList;
  903. *ppVariant = pVariant;
  904. RRETURN(S_OK);
  905. error:
  906. if (pVariant) {
  907. FreeADsMem(pVariant);
  908. }
  909. RRETURN(hr);
  910. }
  911. HRESULT
  912. PackLargeInteger(
  913. LARGE_INTEGER *plargeint,
  914. PVARIANT pVarDestObject
  915. )
  916. {
  917. HRESULT hr = S_OK;
  918. IADsLargeInteger * pLargeInteger = NULL;
  919. IDispatch * pDispatch = NULL;
  920. hr = CoCreateInstance(
  921. CLSID_LargeInteger,
  922. NULL,
  923. CLSCTX_INPROC_SERVER,
  924. IID_IADsLargeInteger,
  925. (void **) &pLargeInteger);
  926. BAIL_ON_FAILURE(hr);
  927. hr = pLargeInteger->put_LowPart(plargeint->LowPart);
  928. BAIL_ON_FAILURE(hr);
  929. hr = pLargeInteger->put_HighPart(plargeint->HighPart);
  930. BAIL_ON_FAILURE(hr);
  931. hr = pLargeInteger->QueryInterface(
  932. IID_IDispatch,
  933. (void **) &pDispatch
  934. );
  935. BAIL_ON_FAILURE(hr);
  936. V_VT(pVarDestObject) = VT_DISPATCH;
  937. V_DISPATCH(pVarDestObject) = pDispatch;
  938. error:
  939. if (pLargeInteger) {
  940. pLargeInteger->Release();
  941. }
  942. RRETURN(hr);
  943. }
  944. HRESULT
  945. PackDNWithBinary(
  946. PADS_DN_WITH_BINARY pDNWithBinary,
  947. PVARIANT pVarDestObject
  948. )
  949. {
  950. HRESULT hr;
  951. IADsDNWithBinary *pIADsDNWithBinary = NULL;
  952. BSTR bstrTemp = NULL;
  953. SAFEARRAYBOUND aBound;
  954. SAFEARRAY *aList = NULL;
  955. CHAR HUGEP *pArray = NULL;
  956. IDispatch *pIDispatch = NULL;
  957. if( (NULL == pDNWithBinary) || (NULL == pVarDestObject) )
  958. BAIL_ON_FAILURE(hr = E_INVALIDARG);
  959. hr = CoCreateInstance(
  960. CLSID_DNWithBinary,
  961. NULL,
  962. CLSCTX_INPROC_SERVER,
  963. IID_IADsDNWithBinary,
  964. (void **) &pIADsDNWithBinary
  965. );
  966. BAIL_ON_FAILURE(hr);
  967. if (pDNWithBinary->pszDNString) {
  968. hr = ADsAllocString(pDNWithBinary->pszDNString, &bstrTemp);
  969. BAIL_ON_FAILURE(hr);
  970. //
  971. // Put the value in the object - we can only set BSTR's
  972. //
  973. hr = pIADsDNWithBinary->put_DNString(bstrTemp);
  974. BAIL_ON_FAILURE(hr);
  975. }
  976. aBound.lLbound = 0;
  977. aBound.cElements = pDNWithBinary->dwLength;
  978. aList = SafeArrayCreate( VT_UI1, 1, &aBound );
  979. if ( aList == NULL )
  980. {
  981. hr = E_OUTOFMEMORY;
  982. BAIL_ON_FAILURE(hr);
  983. }
  984. hr = SafeArrayAccessData( aList, (void HUGEP * FAR *) &pArray );
  985. BAIL_ON_FAILURE(hr);
  986. memcpy( pArray, pDNWithBinary->lpBinaryValue, aBound.cElements );
  987. SafeArrayUnaccessData( aList );
  988. V_VT(pVarDestObject) = VT_ARRAY | VT_UI1;
  989. V_ARRAY(pVarDestObject) = aList;
  990. hr = pIADsDNWithBinary->put_BinaryValue(*pVarDestObject);
  991. VariantClear(pVarDestObject);
  992. BAIL_ON_FAILURE(hr);
  993. hr = pIADsDNWithBinary->QueryInterface(
  994. IID_IDispatch,
  995. (void **) &pIDispatch
  996. );
  997. BAIL_ON_FAILURE(hr);
  998. V_VT(pVarDestObject) = VT_DISPATCH;
  999. V_DISPATCH(pVarDestObject) = pIDispatch;
  1000. error:
  1001. if(pIADsDNWithBinary)
  1002. pIADsDNWithBinary->Release();
  1003. if (bstrTemp)
  1004. ADsFreeString(bstrTemp);
  1005. RRETURN(hr);
  1006. }
  1007. HRESULT
  1008. PackDNWithString(
  1009. PADS_DN_WITH_STRING pDNWithString,
  1010. PVARIANT pVarDestObject
  1011. )
  1012. {
  1013. HRESULT hr;
  1014. IADsDNWithString *pIADsDNWithString = NULL;
  1015. BSTR bstrDNVal = NULL;
  1016. BSTR bstrStrVal = NULL;
  1017. IDispatch *pIDispatch;
  1018. if( (NULL == pDNWithString) || (NULL == pVarDestObject) )
  1019. BAIL_ON_FAILURE(hr = E_INVALIDARG);
  1020. hr = CoCreateInstance(
  1021. CLSID_DNWithString,
  1022. NULL,
  1023. CLSCTX_INPROC_SERVER,
  1024. IID_IADsDNWithString,
  1025. (void **) &pIADsDNWithString
  1026. );
  1027. BAIL_ON_FAILURE(hr);
  1028. if (pDNWithString->pszDNString) {
  1029. hr = ADsAllocString(pDNWithString->pszDNString, &bstrDNVal);
  1030. BAIL_ON_FAILURE(hr);
  1031. hr = pIADsDNWithString->put_DNString(bstrDNVal);
  1032. BAIL_ON_FAILURE(hr);
  1033. }
  1034. if (pDNWithString->pszStringValue) {
  1035. hr = ADsAllocString(
  1036. pDNWithString->pszStringValue,
  1037. &bstrStrVal
  1038. );
  1039. BAIL_ON_FAILURE(hr);
  1040. hr = pIADsDNWithString->put_StringValue(bstrStrVal);
  1041. BAIL_ON_FAILURE(hr);
  1042. }
  1043. hr = pIADsDNWithString->QueryInterface(
  1044. IID_IDispatch,
  1045. (void **) &pIDispatch
  1046. );
  1047. BAIL_ON_FAILURE(hr);
  1048. V_VT(pVarDestObject) = VT_DISPATCH;
  1049. V_DISPATCH(pVarDestObject) = pIDispatch;
  1050. error:
  1051. if(pIADsDNWithString)
  1052. pIADsDNWithString->Release();
  1053. if (bstrDNVal) {
  1054. ADsFreeString(bstrDNVal);
  1055. }
  1056. if (bstrStrVal) {
  1057. ADsFreeString(bstrStrVal);
  1058. }
  1059. RRETURN(hr);
  1060. }
  1061. HRESULT
  1062. CRowProvider::SeekToNextRow(void)
  1063. {
  1064. HRESULT hr;
  1065. DWORD dwExtError = ERROR_SUCCESS;
  1066. const int ERROR_BUF_SIZE = 512;
  1067. const int NAME_BUF_SIZE = 128;
  1068. WCHAR ErrorBuf[ERROR_BUF_SIZE];
  1069. WCHAR NameBuf[NAME_BUF_SIZE];
  1070. do {
  1071. // Clear the ADSI extended error, so that after the call to GetNextRow,
  1072. // we can safely check if an extended error was set.
  1073. ADsSetLastError(ERROR_SUCCESS, NULL, NULL);
  1074. dwExtError = ERROR_SUCCESS;
  1075. //
  1076. // read the next row
  1077. //
  1078. hr = _pDSSearch->GetNextRow(
  1079. _hSearchHandle
  1080. );
  1081. // we should treat SIZE_LIMIT_EXCEEDED error message as
  1082. // S_ADS_NOMORE_ROWS
  1083. // in the future, we might want to return this error message
  1084. // to the user under non-paged search situation
  1085. if (LIMIT_EXCEEDED_ERROR(hr))
  1086. hr = S_ADS_NOMORE_ROWS;
  1087. BAIL_ON_FAILURE( hr );
  1088. if (hr == S_ADS_NOMORE_ROWS)
  1089. {
  1090. // check if more results are likely (pagedTimeLimit search). If so,
  1091. // we will keep trying till a row is obtained.
  1092. hr = ADsGetLastError(&dwExtError, ErrorBuf, ERROR_BUF_SIZE,
  1093. NameBuf, NAME_BUF_SIZE);
  1094. BAIL_ON_FAILURE(hr);
  1095. if (dwExtError != ERROR_MORE_DATA)
  1096. // we really have no more data
  1097. RRETURN(S_ADS_NOMORE_ROWS);
  1098. }
  1099. } while(ERROR_MORE_DATA == dwExtError);
  1100. error:
  1101. RRETURN(hr);
  1102. }
  1103. HRESULT
  1104. CRowProvider::SeekToPreviousRow(void)
  1105. {
  1106. RRETURN( _pDSSearch->GetPreviousRow(_hSearchHandle) );
  1107. }