1 | // Licensed to the .NET Foundation under one or more agreements. |
2 | // The .NET Foundation licenses this file to you under the MIT license. |
3 | // See the LICENSE file in the project root for more information. |
4 | //***************************************************************************** |
5 | // RWUtil.cpp |
6 | // |
7 | |
8 | // |
9 | // contains utility code to MD directory |
10 | // |
11 | //***************************************************************************** |
12 | #include "stdafx.h" |
13 | #include "metadata.h" |
14 | #include "rwutil.h" |
15 | #include "utsem.h" |
16 | #include "../inc/mdlog.h" |
17 | |
18 | //***************************************************************************** |
19 | // Helper methods |
20 | //***************************************************************************** |
21 | void |
22 | Unicode2UTF( |
23 | LPCWSTR wszSrc, // The string to convert. |
24 | __out_ecount(cbDst) |
25 | LPUTF8 szDst, // Buffer for the output UTF8 string. |
26 | int cbDst) // Size of the buffer for UTF8 string. |
27 | { |
28 | int cchSrc = (int)wcslen(wszSrc); |
29 | int cchRet; |
30 | |
31 | cchRet = WszWideCharToMultiByte( |
32 | CP_UTF8, |
33 | 0, |
34 | wszSrc, |
35 | cchSrc + 1, |
36 | szDst, |
37 | cbDst, |
38 | NULL, |
39 | NULL); |
40 | |
41 | if (cchRet == 0) |
42 | { |
43 | _ASSERTE_MSG(FALSE, "Converting unicode string to UTF8 string failed!" ); |
44 | szDst[0] = '\0'; |
45 | } |
46 | } // Unicode2UTF |
47 | |
48 | |
49 | HRESULT HENUMInternal::CreateSimpleEnum( |
50 | DWORD tkKind, // kind of token that we are iterating |
51 | ULONG ridStart, // starting rid |
52 | ULONG ridEnd, // end rid |
53 | HENUMInternal **ppEnum) // return the created HENUMInternal |
54 | { |
55 | HENUMInternal *pEnum; |
56 | HRESULT hr = NOERROR; |
57 | |
58 | // Don't create an empty enum. |
59 | if (ridStart >= ridEnd) |
60 | { |
61 | *ppEnum = 0; |
62 | goto ErrExit; |
63 | } |
64 | |
65 | pEnum = new (nothrow) HENUMInternal; |
66 | |
67 | // check for out of memory error |
68 | if (pEnum == NULL) |
69 | IfFailGo( E_OUTOFMEMORY ); |
70 | |
71 | memset(pEnum, 0, sizeof(HENUMInternal)); |
72 | pEnum->m_tkKind = tkKind; |
73 | pEnum->m_EnumType = MDSimpleEnum; |
74 | pEnum->u.m_ulStart = pEnum->u.m_ulCur = ridStart; |
75 | pEnum->u.m_ulEnd = ridEnd; |
76 | pEnum->m_ulCount = ridEnd - ridStart; |
77 | |
78 | *ppEnum = pEnum; |
79 | ErrExit: |
80 | return hr; |
81 | |
82 | } // CreateSimpleEnum |
83 | |
84 | |
85 | //***************************************************************************** |
86 | // Helper function to destroy Enumerator |
87 | //***************************************************************************** |
88 | void HENUMInternal::DestroyEnum( |
89 | HENUMInternal *pmdEnum) |
90 | { |
91 | if (pmdEnum == NULL) |
92 | return; |
93 | |
94 | if (pmdEnum->m_EnumType == MDDynamicArrayEnum) |
95 | { |
96 | TOKENLIST *pdalist; |
97 | pdalist = (TOKENLIST *) &(pmdEnum->m_cursor); |
98 | |
99 | // clear the embedded dynamic array before we delete the enum |
100 | pdalist->Clear(); |
101 | } |
102 | delete pmdEnum; |
103 | } // DestroyEnum |
104 | |
105 | |
106 | //***************************************************************************** |
107 | // Helper function to destroy Enumerator if the enumerator is empty |
108 | //***************************************************************************** |
109 | void HENUMInternal::DestroyEnumIfEmpty( |
110 | HENUMInternal **ppEnum) // reset the enumerator pointer to NULL if empty |
111 | { |
112 | |
113 | if (*ppEnum == NULL) |
114 | return; |
115 | |
116 | _ASSERTE((*ppEnum)->m_EnumType != MDCustomEnum); |
117 | |
118 | if ((*ppEnum)->m_ulCount == 0) |
119 | { |
120 | HENUMInternal::DestroyEnum(*ppEnum); |
121 | *ppEnum = NULL; |
122 | } |
123 | } // DestroyEnumIfEmpty |
124 | |
125 | |
126 | void HENUMInternal::ClearEnum( |
127 | HENUMInternal *pmdEnum) |
128 | { |
129 | if (pmdEnum == NULL) |
130 | return; |
131 | |
132 | if (pmdEnum->m_EnumType == MDDynamicArrayEnum) |
133 | { |
134 | TOKENLIST *pdalist; |
135 | pdalist = (TOKENLIST *) &(pmdEnum->m_cursor); |
136 | |
137 | // clear the embedded dynamic array before we delete the enum |
138 | pdalist->Clear(); |
139 | } |
140 | } // ClearEnum |
141 | |
142 | |
143 | //***************************************************************************** |
144 | // Helper function to iterate the enum |
145 | //***************************************************************************** |
146 | bool HENUMInternal::EnumNext( |
147 | HENUMInternal *phEnum, // [IN] the enumerator to retrieve information |
148 | mdToken *ptk) // [OUT] token to scope the search |
149 | { |
150 | _ASSERTE(phEnum && ptk); |
151 | _ASSERTE(phEnum->m_EnumType != MDCustomEnum); |
152 | |
153 | if (phEnum->u.m_ulCur >= phEnum->u.m_ulEnd) |
154 | return false; |
155 | |
156 | if ( phEnum->m_EnumType == MDSimpleEnum ) |
157 | { |
158 | *ptk = phEnum->u.m_ulCur | phEnum->m_tkKind; |
159 | phEnum->u.m_ulCur++; |
160 | } |
161 | else |
162 | { |
163 | TOKENLIST *pdalist = (TOKENLIST *)&(phEnum->m_cursor); |
164 | |
165 | _ASSERTE( phEnum->m_EnumType == MDDynamicArrayEnum ); |
166 | *ptk = *( pdalist->Get(phEnum->u.m_ulCur++) ); |
167 | } |
168 | return true; |
169 | } // EnumNext |
170 | |
171 | //***************************************************************************** |
172 | // Number of items in the enumerator. |
173 | //***************************************************************************** |
174 | HRESULT HENUMInternal::GetCount( |
175 | HENUMInternal *phEnum, // [IN] the enumerator to retrieve information |
176 | ULONG *pCount) // ]OUT] the index of the desired item |
177 | { |
178 | // Check for empty enum. |
179 | if (phEnum == 0) |
180 | return S_FALSE; |
181 | |
182 | _ASSERTE(phEnum->m_EnumType != MDCustomEnum); |
183 | |
184 | |
185 | *pCount = phEnum->u.m_ulEnd - phEnum->u.m_ulStart; |
186 | return S_OK; |
187 | } |
188 | |
189 | //***************************************************************************** |
190 | // Get a specific element. |
191 | //***************************************************************************** |
192 | HRESULT HENUMInternal::GetElement( |
193 | HENUMInternal *phEnum, // [IN] the enumerator to retrieve information |
194 | ULONG ix, // ]IN] the index of the desired item |
195 | mdToken *ptk) // [OUT] token to fill |
196 | { |
197 | // Check for empty enum. |
198 | if (phEnum == 0) |
199 | return S_FALSE; |
200 | |
201 | if (ix > (phEnum->u.m_ulEnd - phEnum->u.m_ulStart)) |
202 | return S_FALSE; |
203 | |
204 | if ( phEnum->m_EnumType == MDSimpleEnum ) |
205 | { |
206 | *ptk = (phEnum->u.m_ulStart + ix) | phEnum->m_tkKind; |
207 | } |
208 | else |
209 | { |
210 | TOKENLIST *pdalist = (TOKENLIST *)&(phEnum->m_cursor); |
211 | |
212 | _ASSERTE( phEnum->m_EnumType == MDDynamicArrayEnum ); |
213 | *ptk = *( pdalist->Get(ix) ); |
214 | } |
215 | |
216 | return S_OK; |
217 | } |
218 | |
219 | //***************************************************************************** |
220 | // Helper function to fill output token buffers given an enumerator |
221 | //***************************************************************************** |
222 | HRESULT HENUMInternal::EnumWithCount( |
223 | HENUMInternal *pEnum, // enumerator |
224 | ULONG cMax, // max tokens that caller wants |
225 | mdToken rTokens[], // output buffer to fill the tokens |
226 | ULONG *pcTokens) // number of tokens fill to the buffer upon return |
227 | { |
228 | ULONG cTokens; |
229 | HRESULT hr = NOERROR; |
230 | |
231 | // Check for empty enum. |
232 | if (pEnum == 0) |
233 | { |
234 | if (pcTokens) |
235 | *pcTokens = 0; |
236 | return S_FALSE; |
237 | } |
238 | |
239 | // we can only fill the minimun of what caller asked for or what we have left |
240 | cTokens = min ( (pEnum->u.m_ulEnd - pEnum->u.m_ulCur), cMax); |
241 | |
242 | if (pEnum->m_EnumType == MDSimpleEnum) |
243 | { |
244 | |
245 | // now fill the output |
246 | for (ULONG i = 0; i < cTokens; i ++, pEnum->u.m_ulCur++) |
247 | { |
248 | rTokens[i] = TokenFromRid(pEnum->u.m_ulCur, pEnum->m_tkKind); |
249 | } |
250 | |
251 | } |
252 | else |
253 | { |
254 | // cannot be any other kind! |
255 | _ASSERTE( pEnum->m_EnumType == MDDynamicArrayEnum ); |
256 | |
257 | // get the embedded dynamic array |
258 | TOKENLIST *pdalist = (TOKENLIST *)&(pEnum->m_cursor); |
259 | |
260 | for (ULONG i = 0; i < cTokens; i ++, pEnum->u.m_ulCur++) |
261 | { |
262 | rTokens[i] = *( pdalist->Get(pEnum->u.m_ulCur) ); |
263 | } |
264 | } |
265 | |
266 | if (pcTokens) |
267 | *pcTokens = cTokens; |
268 | |
269 | if (cTokens == 0) |
270 | hr = S_FALSE; |
271 | return hr; |
272 | } // EnumWithCount |
273 | |
274 | |
275 | //***************************************************************************** |
276 | // Helper function to fill output token buffers given an enumerator |
277 | // This is a variation that takes two output arrays. The tokens in the |
278 | // enumerator are interleaved, one for each array. This is currently used by |
279 | // EnumMethodImpl which needs to return two arrays. |
280 | //***************************************************************************** |
281 | HRESULT HENUMInternal::EnumWithCount( |
282 | HENUMInternal *pEnum, // enumerator |
283 | ULONG cMax, // max tokens that caller wants |
284 | mdToken rTokens1[], // first output buffer to fill the tokens |
285 | mdToken rTokens2[], // second output buffer to fill the tokens |
286 | ULONG *pcTokens) // number of tokens fill to each buffer upon return |
287 | { |
288 | ULONG cTokens; |
289 | HRESULT hr = NOERROR; |
290 | |
291 | // cannot be any other kind! |
292 | _ASSERTE( pEnum->m_EnumType == MDDynamicArrayEnum ); |
293 | |
294 | // Check for empty enum. |
295 | if (pEnum == 0) |
296 | { |
297 | if (pcTokens) |
298 | *pcTokens = 0; |
299 | return S_FALSE; |
300 | } |
301 | |
302 | // Number of tokens must always be a multiple of 2. |
303 | _ASSERTE(! ((pEnum->u.m_ulEnd - pEnum->u.m_ulCur) % 2) ); |
304 | |
305 | // we can only fill the minimun of what caller asked for or what we have left |
306 | cTokens = min ( (pEnum->u.m_ulEnd - pEnum->u.m_ulCur), cMax * 2); |
307 | |
308 | // get the embedded dynamic array |
309 | TOKENLIST *pdalist = (TOKENLIST *)&(pEnum->m_cursor); |
310 | |
311 | for (ULONG i = 0; i < (cTokens / 2); i++) |
312 | { |
313 | rTokens1[i] = *( pdalist->Get(pEnum->u.m_ulCur++) ); |
314 | rTokens2[i] = *( pdalist->Get(pEnum->u.m_ulCur++) ); |
315 | } |
316 | |
317 | if (pcTokens) |
318 | *pcTokens = cTokens / 2; |
319 | |
320 | if (cTokens == 0) |
321 | hr = S_FALSE; |
322 | return hr; |
323 | } // EnumWithCount |
324 | |
325 | |
326 | //***************************************************************************** |
327 | // Helper function to create HENUMInternal |
328 | //***************************************************************************** |
329 | HRESULT HENUMInternal::CreateDynamicArrayEnum( |
330 | DWORD tkKind, // kind of token that we are iterating |
331 | HENUMInternal **ppEnum) // return the created HENUMInternal |
332 | { |
333 | HENUMInternal *pEnum; |
334 | HRESULT hr = NOERROR; |
335 | TOKENLIST *pdalist; |
336 | |
337 | pEnum = new (nothrow) HENUMInternal; |
338 | |
339 | // check for out of memory error |
340 | if (pEnum == NULL) |
341 | IfFailGo( E_OUTOFMEMORY ); |
342 | |
343 | memset(pEnum, 0, sizeof(HENUMInternal)); |
344 | pEnum->m_tkKind = tkKind; |
345 | pEnum->m_EnumType = MDDynamicArrayEnum; |
346 | |
347 | // run the constructor in place |
348 | pdalist = (TOKENLIST *) &(pEnum->m_cursor); |
349 | ::new (pdalist) TOKENLIST; |
350 | |
351 | *ppEnum = pEnum; |
352 | ErrExit: |
353 | return hr; |
354 | |
355 | } // _CreateDynamicArrayEnum |
356 | |
357 | |
358 | |
359 | //***************************************************************************** |
360 | // Helper function to init HENUMInternal |
361 | //***************************************************************************** |
362 | void HENUMInternal::InitDynamicArrayEnum( |
363 | HENUMInternal *pEnum) // HENUMInternal to be initialized |
364 | { |
365 | TOKENLIST *pdalist; |
366 | |
367 | memset(pEnum, 0, sizeof(HENUMInternal)); |
368 | pEnum->m_EnumType = MDDynamicArrayEnum; |
369 | pEnum->m_tkKind = (DWORD) -1; |
370 | |
371 | // run the constructor in place |
372 | pdalist = (TOKENLIST *) &(pEnum->m_cursor); |
373 | ::new (pdalist) TOKENLIST; |
374 | } // CreateDynamicArrayEnum |
375 | |
376 | |
377 | //***************************************************************************** |
378 | // Helper function to init HENUMInternal |
379 | //***************************************************************************** |
380 | void HENUMInternal::InitSimpleEnum( |
381 | DWORD tkKind, // kind of token that we are iterating |
382 | ULONG ridStart, // starting rid |
383 | ULONG ridEnd, // end rid |
384 | HENUMInternal *pEnum) // HENUMInternal to be initialized |
385 | { |
386 | pEnum->m_EnumType = MDSimpleEnum; |
387 | pEnum->m_tkKind = tkKind; |
388 | pEnum->u.m_ulStart = pEnum->u.m_ulCur = ridStart; |
389 | pEnum->u.m_ulEnd = ridEnd; |
390 | pEnum->m_ulCount = ridEnd - ridStart; |
391 | |
392 | } // InitSimpleEnum |
393 | |
394 | |
395 | |
396 | |
397 | //***************************************************************************** |
398 | // Helper function to init HENUMInternal |
399 | //***************************************************************************** |
400 | HRESULT HENUMInternal::AddElementToEnum( |
401 | HENUMInternal *pEnum, // return the created HENUMInternal |
402 | mdToken tk) // token value to be stored |
403 | { |
404 | HRESULT hr = NOERROR; |
405 | TOKENLIST *pdalist; |
406 | mdToken *ptk; |
407 | |
408 | pdalist = (TOKENLIST *) &(pEnum->m_cursor); |
409 | |
410 | { |
411 | // TODO: Revisit this violation. |
412 | CONTRACT_VIOLATION(ThrowsViolation); |
413 | ptk = ((mdToken *)pdalist->Append()); |
414 | } |
415 | if (ptk == NULL) |
416 | IfFailGo( E_OUTOFMEMORY ); |
417 | *ptk = tk; |
418 | |
419 | // increase the count |
420 | pEnum->m_ulCount++; |
421 | pEnum->u.m_ulEnd++; |
422 | ErrExit: |
423 | return hr; |
424 | |
425 | } // _AddElementToEnum |
426 | |
427 | |
428 | |
429 | |
430 | |
431 | //***************************************************************************** |
432 | // find a token in the tokenmap. |
433 | //***************************************************************************** |
434 | MDTOKENMAP::~MDTOKENMAP() |
435 | { |
436 | if (m_pMap) |
437 | m_pMap->Release(); |
438 | } // MDTOKENMAP::~MDTOKENMAP() |
439 | |
440 | HRESULT MDTOKENMAP::Init( |
441 | IUnknown *pImport) // The import that this map is for. |
442 | { |
443 | HRESULT hr; // A result. |
444 | IMetaDataTables *pITables=0; // Table information. |
445 | ULONG cRows; // Count of rows in a table. |
446 | ULONG cTotal; // Running total of rows in db. |
447 | TOKENREC *pRec; // A TOKENREC record. |
448 | mdToken tkTable; // Token kind for a table. |
449 | |
450 | hr = pImport->QueryInterface(IID_IMetaDataTables, (void**)&pITables); |
451 | if (hr == S_OK) |
452 | { |
453 | // Determine the size of each table. |
454 | cTotal = 0; |
455 | for (ULONG ixTbl=0; ixTbl<TBL_COUNT; ++ixTbl) |
456 | { |
457 | // Where does this table's data start. |
458 | m_TableOffset[ixTbl] = cTotal; |
459 | // See if this table has tokens. |
460 | tkTable = CMiniMdRW::GetTokenForTable(ixTbl); |
461 | if (tkTable == (ULONG) -1) |
462 | { |
463 | // It doesn't have tokens, so we won't see any tokens for the table. |
464 | } |
465 | else |
466 | { // It has tokens, so we may see a token for every row. |
467 | pITables->GetTableInfo(ixTbl, 0, &cRows, 0,0,0); |
468 | // Safe: cTotal += cRows |
469 | if (!ClrSafeInt<ULONG>::addition(cTotal, cRows, cTotal)) |
470 | { |
471 | IfFailGo(COR_E_OVERFLOW); |
472 | } |
473 | } |
474 | } |
475 | m_TableOffset[TBL_COUNT] = cTotal; |
476 | m_iCountIndexed = cTotal; |
477 | // Attempt to allocate space for all of the possible remaps. |
478 | if (!AllocateBlock(cTotal)) |
479 | IfFailGo(E_OUTOFMEMORY); |
480 | // Note that no sorts are needed. |
481 | m_sortKind = Indexed; |
482 | // Initialize entries to "not found". |
483 | for (ULONG i=0; i<cTotal; ++i) |
484 | { |
485 | pRec = Get(i); |
486 | pRec->SetEmpty(); |
487 | } |
488 | } |
489 | #if defined(_DEBUG) |
490 | if (SUCCEEDED(pImport->QueryInterface(IID_IMetaDataImport, (void**)&m_pImport))) |
491 | { |
492 | // Ok, here's a pretty nasty workaround. We're going to make a big assumption here |
493 | // that we're owned by the pImport, and so we don't need to keep a refcount |
494 | // on the pImport object. |
495 | // |
496 | // If we did, we'd create a circular reference and neither this object nor |
497 | // the RegMeta would be freed. |
498 | m_pImport->Release(); |
499 | |
500 | } |
501 | |
502 | |
503 | |
504 | #endif |
505 | |
506 | ErrExit: |
507 | if (pITables) |
508 | pITables->Release(); |
509 | return hr; |
510 | } // HRESULT MDTOKENMAP::Init() |
511 | |
512 | HRESULT MDTOKENMAP::EmptyMap() |
513 | { |
514 | int nCount = Count(); |
515 | for (int i=0; i<nCount; ++i) |
516 | { |
517 | Get(i)->SetEmpty(); |
518 | } |
519 | |
520 | return S_OK; |
521 | }// HRESULT MDTOKENMAP::Clear() |
522 | |
523 | |
524 | //***************************************************************************** |
525 | // find a token in the tokenmap. |
526 | //***************************************************************************** |
527 | bool MDTOKENMAP::Find( |
528 | mdToken tkFind, // [IN] the token value to find |
529 | TOKENREC **ppRec) // [OUT] point to the record found in the dynamic array |
530 | { |
531 | int lo,mid,hi; // binary search indices. |
532 | TOKENREC *pRec = NULL; |
533 | |
534 | if (m_sortKind == Indexed && TypeFromToken(tkFind) != mdtString) |
535 | { |
536 | // Get the entry. |
537 | ULONG ixTbl = CMiniMdRW::GetTableForToken(tkFind); |
538 | if(ixTbl == (ULONG) -1) |
539 | return false; |
540 | ULONG iRid = RidFromToken(tkFind); |
541 | if((m_TableOffset[ixTbl] + iRid) > m_TableOffset[ixTbl+1]) |
542 | return false; |
543 | pRec = Get(m_TableOffset[ixTbl] + iRid - 1); |
544 | // See if it has been set. |
545 | if (pRec->IsEmpty()) |
546 | return false; |
547 | // Verify that it is what we think it is. |
548 | _ASSERTE(pRec->m_tkFrom == tkFind); |
549 | *ppRec = pRec; |
550 | return true; |
551 | } |
552 | else |
553 | { // Shouldn't be any unsorted records, and table must be sorted in proper ordering. |
554 | _ASSERTE( m_iCountTotal == m_iCountSorted && |
555 | (m_sortKind == SortByFromToken || m_sortKind == Indexed) ); |
556 | _ASSERTE( (m_iCountIndexed + m_iCountTotal) == (ULONG)Count() ); |
557 | |
558 | // Start with entire table. |
559 | lo = m_iCountIndexed; |
560 | hi = Count() - 1; |
561 | |
562 | // While there are rows in the range... |
563 | while (lo <= hi) |
564 | { // Look at the one in the middle. |
565 | mid = (lo + hi) / 2; |
566 | |
567 | pRec = Get(mid); |
568 | |
569 | // If equal to the target, done. |
570 | if (tkFind == pRec->m_tkFrom) |
571 | { |
572 | *ppRec = Get(mid); |
573 | return true; |
574 | } |
575 | |
576 | // If middle item is too small, search the top half. |
577 | if (pRec->m_tkFrom < tkFind) |
578 | lo = mid + 1; |
579 | else // but if middle is to big, search bottom half. |
580 | hi = mid - 1; |
581 | } |
582 | } |
583 | |
584 | // Didn't find anything that matched. |
585 | return false; |
586 | } // bool MDTOKENMAP::Find() |
587 | |
588 | |
589 | |
590 | //***************************************************************************** |
591 | // remap the token |
592 | //***************************************************************************** |
593 | HRESULT MDTOKENMAP::Remap( |
594 | mdToken tkFrom, |
595 | mdToken *ptkTo) |
596 | { |
597 | HRESULT hr = NOERROR; |
598 | TOKENREC *pRec; |
599 | |
600 | // Remap nil to same thing (helps because System.Object has no base class.) |
601 | if (IsNilToken(tkFrom)) |
602 | { |
603 | *ptkTo = tkFrom; |
604 | return hr; |
605 | } |
606 | |
607 | if ( Find(tkFrom, &pRec) ) |
608 | { |
609 | *ptkTo = pRec->m_tkTo; |
610 | } |
611 | else |
612 | { |
613 | _ASSERTE( !" Bad lookup map!" ); |
614 | hr = META_E_BADMETADATA; |
615 | } |
616 | return hr; |
617 | } // HRESULT MDTOKENMAP::Remap() |
618 | |
619 | |
620 | |
621 | //***************************************************************************** |
622 | // find a token in the tokenmap. |
623 | //***************************************************************************** |
624 | HRESULT MDTOKENMAP::InsertNotFound( |
625 | mdToken tkFind, |
626 | bool fDuplicate, |
627 | mdToken tkTo, |
628 | TOKENREC **ppRec) |
629 | { |
630 | HRESULT hr = NOERROR; |
631 | int lo, mid, hi; // binary search indices. |
632 | TOKENREC *pRec; |
633 | |
634 | // If possible, validate the input. |
635 | _ASSERTE(!m_pImport || m_pImport->IsValidToken(tkFind)); |
636 | |
637 | if (m_sortKind == Indexed && TypeFromToken(tkFind) != mdtString) |
638 | { |
639 | // Get the entry. |
640 | ULONG ixTbl = CMiniMdRW::GetTableForToken(tkFind); |
641 | _ASSERTE(ixTbl != (ULONG) -1); |
642 | ULONG iRid = RidFromToken(tkFind); |
643 | _ASSERTE((m_TableOffset[ixTbl] + iRid) <= m_TableOffset[ixTbl+1]); |
644 | pRec = Get(m_TableOffset[ixTbl] + iRid - 1); |
645 | // See if it has been set. |
646 | if (!pRec->IsEmpty()) |
647 | { // Verify that it is what we think it is. |
648 | _ASSERTE(pRec->m_tkFrom == tkFind); |
649 | } |
650 | // Store the data. |
651 | pRec->m_tkFrom = tkFind; |
652 | pRec->m_isDuplicate = fDuplicate; |
653 | pRec->m_tkTo = tkTo; |
654 | pRec->m_isFoundInImport = false; |
655 | // Return the result. |
656 | *ppRec = pRec; |
657 | } |
658 | else |
659 | { // Shouldn't be any unsorted records, and table must be sorted in proper ordering. |
660 | _ASSERTE( m_iCountTotal == m_iCountSorted && |
661 | (m_sortKind == SortByFromToken || m_sortKind == Indexed) ); |
662 | |
663 | if ((Count() - m_iCountIndexed) > 0) |
664 | { |
665 | // Start with entire table. |
666 | lo = m_iCountIndexed; |
667 | hi = Count() - 1; |
668 | |
669 | // While there are rows in the range... |
670 | while (lo < hi) |
671 | { // Look at the one in the middle. |
672 | mid = (lo + hi) / 2; |
673 | |
674 | pRec = Get(mid); |
675 | |
676 | // If equal to the target, done. |
677 | if (tkFind == pRec->m_tkFrom) |
678 | { |
679 | *ppRec = Get(mid); |
680 | goto ErrExit; |
681 | } |
682 | |
683 | // If middle item is too small, search the top half. |
684 | if (pRec->m_tkFrom < tkFind) |
685 | lo = mid + 1; |
686 | else // but if middle is to big, search bottom half. |
687 | hi = mid - 1; |
688 | } |
689 | _ASSERTE(hi <= lo); |
690 | pRec = Get(lo); |
691 | |
692 | if (tkFind == pRec->m_tkFrom) |
693 | { |
694 | if (tkTo == pRec->m_tkTo && fDuplicate == pRec->m_isDuplicate) |
695 | { |
696 | *ppRec = pRec; |
697 | } |
698 | else |
699 | { |
700 | _ASSERTE(!"inconsistent token has been added to the table!" ); |
701 | IfFailGo( E_FAIL ); |
702 | } |
703 | } |
704 | |
705 | if (tkFind < pRec->m_tkFrom) |
706 | { |
707 | // insert before lo; |
708 | pRec = Insert(lo); |
709 | } |
710 | else |
711 | { |
712 | // insert after lo |
713 | pRec = Insert(lo + 1); |
714 | } |
715 | } |
716 | else |
717 | { |
718 | // table is empty |
719 | pRec = Insert(m_iCountIndexed); |
720 | } |
721 | |
722 | |
723 | // If pRec == NULL, return E_OUTOFMEMORY |
724 | IfNullGo(pRec); |
725 | |
726 | m_iCountTotal++; |
727 | m_iCountSorted++; |
728 | |
729 | *ppRec = pRec; |
730 | |
731 | // initialize the record |
732 | pRec->m_tkFrom = tkFind; |
733 | pRec->m_isDuplicate = fDuplicate; |
734 | pRec->m_tkTo = tkTo; |
735 | pRec->m_isFoundInImport = false; |
736 | } |
737 | |
738 | ErrExit: |
739 | return hr; |
740 | } // HRESULT MDTOKENMAP::InsertNotFound() |
741 | |
742 | |
743 | //***************************************************************************** |
744 | // find a "to" token in the tokenmap. Now that we are doing the ref to def optimization, |
745 | // we might have several from tokens map to the same to token. We need to return a range of index |
746 | // instead.... |
747 | //***************************************************************************** |
748 | bool MDTOKENMAP::FindWithToToken( |
749 | mdToken tkFind, // [IN] the token value to find |
750 | int *piPosition) // [OUT] return the first from-token that has the matching to-token |
751 | { |
752 | int lo, mid, hi; // binary search indices. |
753 | TOKENREC *pRec; |
754 | TOKENREC *pRec2; |
755 | |
756 | // This makes sure that no insertions take place between calls to FindWithToToken. |
757 | // We want to avoid repeated sorting of the table. |
758 | _ASSERTE(m_sortKind != SortByToToken || m_iCountTotal == m_iCountSorted); |
759 | |
760 | // If the map is sorted with From tokens, change it to be sorted with To tokens. |
761 | if (m_sortKind != SortByToToken) |
762 | SortTokensByToToken(); |
763 | |
764 | // Start with entire table. |
765 | lo = 0; |
766 | hi = Count() - 1; |
767 | |
768 | // While there are rows in the range... |
769 | while (lo <= hi) |
770 | { // Look at the one in the middle. |
771 | mid = (lo + hi) / 2; |
772 | |
773 | pRec = Get(mid); |
774 | |
775 | // If equal to the target, done. |
776 | if (tkFind == pRec->m_tkTo) |
777 | { |
778 | for (int i = mid-1; i >= 0; i--) |
779 | { |
780 | pRec2 = Get(i); |
781 | if (tkFind != pRec2->m_tkTo) |
782 | { |
783 | *piPosition = i + 1; |
784 | return true; |
785 | } |
786 | } |
787 | *piPosition = 0; |
788 | return true; |
789 | } |
790 | |
791 | // If middle item is too small, search the top half. |
792 | if (pRec->m_tkTo < tkFind) |
793 | lo = mid + 1; |
794 | else // but if middle is to big, search bottom half. |
795 | hi = mid - 1; |
796 | } |
797 | // Didn't find anything that matched. |
798 | return false; |
799 | } // bool MDTOKENMAP::FindWithToToken() |
800 | |
801 | |
802 | |
803 | //***************************************************************************** |
804 | // output a remapped token |
805 | //***************************************************************************** |
806 | mdToken MDTOKENMAP::SafeRemap( |
807 | mdToken tkFrom) // [IN] the token value to find |
808 | { |
809 | TOKENREC *pRec; |
810 | |
811 | // If possible, validate the input. |
812 | _ASSERTE(!m_pImport || m_pImport->IsValidToken(tkFrom)); |
813 | |
814 | SortTokensByFromToken(); |
815 | |
816 | if ( Find(tkFrom, &pRec) ) |
817 | { |
818 | return pRec->m_tkTo; |
819 | } |
820 | |
821 | return tkFrom; |
822 | } // mdToken MDTOKENMAP::SafeRemap() |
823 | |
824 | |
825 | //***************************************************************************** |
826 | // Sorting |
827 | //***************************************************************************** |
828 | void MDTOKENMAP::SortTokensByToToken() |
829 | { |
830 | // Only sort if there are unsorted records or the sort kind changed. |
831 | if (m_iCountSorted < m_iCountTotal || m_sortKind != SortByToToken) |
832 | { |
833 | // Sort the entire array. |
834 | m_iCountTotal = Count(); |
835 | m_iCountIndexed = 0; |
836 | SortRangeToToken(0, m_iCountTotal - 1); |
837 | m_iCountSorted = m_iCountTotal; |
838 | m_sortKind = SortByToToken; |
839 | } |
840 | } // void MDTOKENMAP::SortTokensByToToken() |
841 | |
842 | void MDTOKENMAP::SortRangeFromToken( |
843 | int iLeft, |
844 | int iRight) |
845 | { |
846 | int iLast; |
847 | int i; // loop variable. |
848 | |
849 | // if less than two elements you're done. |
850 | if (iLeft >= iRight) |
851 | return; |
852 | |
853 | // The mid-element is the pivot, move it to the left. |
854 | Swap(iLeft, (iLeft+iRight)/2); |
855 | iLast = iLeft; |
856 | |
857 | // move everything that is smaller than the pivot to the left. |
858 | for(i = iLeft+1; i <= iRight; i++) |
859 | if (CompareFromToken(i, iLeft) < 0) |
860 | Swap(i, ++iLast); |
861 | |
862 | // Put the pivot to the point where it is in between smaller and larger elements. |
863 | Swap(iLeft, iLast); |
864 | |
865 | // Sort the each partition. |
866 | SortRangeFromToken(iLeft, iLast-1); |
867 | SortRangeFromToken(iLast+1, iRight); |
868 | } // void MDTOKENMAP::SortRangeFromToken() |
869 | |
870 | |
871 | //***************************************************************************** |
872 | // Sorting |
873 | //***************************************************************************** |
874 | void MDTOKENMAP::SortRangeToToken( |
875 | int iLeft, |
876 | int iRight) |
877 | { |
878 | int iLast; |
879 | int i; // loop variable. |
880 | |
881 | // if less than two elements you're done. |
882 | if (iLeft >= iRight) |
883 | return; |
884 | |
885 | // The mid-element is the pivot, move it to the left. |
886 | Swap(iLeft, (iLeft+iRight)/2); |
887 | iLast = iLeft; |
888 | |
889 | // move everything that is smaller than the pivot to the left. |
890 | for(i = iLeft+1; i <= iRight; i++) |
891 | if (CompareToToken(i, iLeft) < 0) |
892 | Swap(i, ++iLast); |
893 | |
894 | // Put the pivot to the point where it is in between smaller and larger elements. |
895 | Swap(iLeft, iLast); |
896 | |
897 | // Sort the each partition. |
898 | SortRangeToToken(iLeft, iLast-1); |
899 | SortRangeToToken(iLast+1, iRight); |
900 | } // void MDTOKENMAP::SortRangeToToken() |
901 | |
902 | |
903 | //***************************************************************************** |
904 | // find a token in the tokenmap. |
905 | //***************************************************************************** |
906 | HRESULT MDTOKENMAP::AppendRecord( |
907 | mdToken tkFind, |
908 | bool fDuplicate, |
909 | mdToken tkTo, |
910 | TOKENREC **ppRec) |
911 | { |
912 | HRESULT hr = NOERROR; |
913 | TOKENREC *pRec; |
914 | |
915 | // If possible, validate the input. |
916 | _ASSERTE(!m_pImport || m_pImport->IsValidToken(tkFind)); |
917 | |
918 | // If the map is indexed, and this is a table token, update-in-place. |
919 | if (m_sortKind == Indexed && TypeFromToken(tkFind) != mdtString) |
920 | { |
921 | // Get the entry. |
922 | ULONG ixTbl = CMiniMdRW::GetTableForToken(tkFind); |
923 | _ASSERTE(ixTbl != (ULONG) -1); |
924 | ULONG iRid = RidFromToken(tkFind); |
925 | _ASSERTE((m_TableOffset[ixTbl] + iRid) <= m_TableOffset[ixTbl+1]); |
926 | pRec = Get(m_TableOffset[ixTbl] + iRid - 1); |
927 | // See if it has been set. |
928 | if (!pRec->IsEmpty()) |
929 | { // Verify that it is what we think it is. |
930 | _ASSERTE(pRec->m_tkFrom == tkFind); |
931 | } |
932 | } |
933 | else |
934 | { |
935 | pRec = Append(); |
936 | IfNullGo(pRec); |
937 | |
938 | // number of entries increased but not the sorted entry |
939 | m_iCountTotal++; |
940 | } |
941 | |
942 | // Store the data. |
943 | pRec->m_tkFrom = tkFind; |
944 | pRec->m_isDuplicate = fDuplicate; |
945 | pRec->m_tkTo = tkTo; |
946 | pRec->m_isFoundInImport = false; |
947 | *ppRec = pRec; |
948 | |
949 | ErrExit: |
950 | return hr; |
951 | } // HRESULT MDTOKENMAP::AppendRecord() |
952 | |
953 | |
954 | |
955 | //********************************************************************************************************* |
956 | // |
957 | // CMapToken's constructor |
958 | // |
959 | //********************************************************************************************************* |
960 | CMapToken::CMapToken() |
961 | { |
962 | m_cRef = 1; |
963 | m_pTKMap = NULL; |
964 | m_isSorted = true; |
965 | } // TokenManager::TokenManager() |
966 | |
967 | |
968 | |
969 | //********************************************************************************************************* |
970 | // |
971 | // CMapToken's destructor |
972 | // |
973 | //********************************************************************************************************* |
974 | CMapToken::~CMapToken() |
975 | { |
976 | delete m_pTKMap; |
977 | } // CMapToken::~CMapToken() |
978 | |
979 | |
980 | ULONG CMapToken::AddRef() |
981 | { |
982 | return InterlockedIncrement(&m_cRef); |
983 | } // CMapToken::AddRef() |
984 | |
985 | |
986 | |
987 | ULONG CMapToken::Release() |
988 | { |
989 | ULONG cRef = InterlockedDecrement(&m_cRef); |
990 | if (!cRef) |
991 | delete this; |
992 | return (cRef); |
993 | } // CMapToken::Release() |
994 | |
995 | |
996 | HRESULT CMapToken::QueryInterface(REFIID riid, void **ppUnk) |
997 | { |
998 | if (ppUnk == NULL) |
999 | return E_INVALIDARG; |
1000 | |
1001 | if (IsEqualIID(riid, IID_IMapToken)) |
1002 | { |
1003 | *ppUnk = (IMapToken *) this; |
1004 | } |
1005 | else if (IsEqualIID(riid, IID_IUnknown)) |
1006 | { |
1007 | *ppUnk = (IUnknown *) this; |
1008 | } |
1009 | else |
1010 | { |
1011 | *ppUnk = NULL; |
1012 | return (E_NOINTERFACE); |
1013 | } |
1014 | |
1015 | AddRef(); |
1016 | return (S_OK); |
1017 | } // CMapToken::QueryInterface |
1018 | |
1019 | |
1020 | |
1021 | //********************************************************************************************************* |
1022 | // |
1023 | // Track the token mapping |
1024 | // |
1025 | //********************************************************************************************************* |
1026 | HRESULT CMapToken::Map( |
1027 | mdToken tkFrom, |
1028 | mdToken tkTo) |
1029 | { |
1030 | HRESULT hr = NOERROR; |
1031 | TOKENREC *pTkRec; |
1032 | |
1033 | if (m_pTKMap == NULL) |
1034 | m_pTKMap = new (nothrow) MDTOKENMAP; |
1035 | |
1036 | IfNullGo( m_pTKMap ); |
1037 | |
1038 | IfFailGo( m_pTKMap->AppendRecord(tkFrom, false, tkTo, &pTkRec) ); |
1039 | _ASSERTE( pTkRec ); |
1040 | |
1041 | m_isSorted = false; |
1042 | ErrExit: |
1043 | return hr; |
1044 | } |
1045 | |
1046 | |
1047 | //********************************************************************************************************* |
1048 | // |
1049 | // return what tkFrom is mapped to ptkTo. If there is no remap |
1050 | // (ie the token from is filtered out by the filter mechanism, it will return false. |
1051 | // |
1052 | //********************************************************************************************************* |
1053 | bool CMapToken::Find( |
1054 | mdToken tkFrom, |
1055 | TOKENREC **pRecTo) |
1056 | { |
1057 | TOKENREC *pRec; |
1058 | bool bRet; |
1059 | if ( m_isSorted == false ) |
1060 | { |
1061 | // sort the map |
1062 | m_pTKMap->SortTokensByFromToken(); |
1063 | m_isSorted = true; |
1064 | } |
1065 | |
1066 | bRet = m_pTKMap->Find(tkFrom, &pRec) ; |
1067 | if (bRet) |
1068 | { |
1069 | _ASSERTE(pRecTo); |
1070 | *pRecTo = pRec; |
1071 | } |
1072 | else |
1073 | { |
1074 | pRec = NULL; |
1075 | } |
1076 | return bRet; |
1077 | } |
1078 | |
1079 | |
1080 | //********************************************************************************************************* |
1081 | // |
1082 | // This function returns true if tkFrom is resolved to a def token. Otherwise, it returns |
1083 | // false. |
1084 | // |
1085 | //********************************************************************************************************* |
1086 | bool TokenRemapManager::ResolveRefToDef( |
1087 | mdToken tkRef, // [IN] ref token |
1088 | mdToken *ptkDef) // [OUT] def token that it resolves to. If it does not resolve to a def |
1089 | // token, it will return the tkRef token here. |
1090 | { |
1091 | mdToken tkTo; |
1092 | |
1093 | _ASSERTE(ptkDef); |
1094 | |
1095 | if (TypeFromToken(tkRef) == mdtTypeRef) |
1096 | { |
1097 | tkTo = m_TypeRefToTypeDefMap[RidFromToken(tkRef)]; |
1098 | } |
1099 | else |
1100 | { |
1101 | _ASSERTE( TypeFromToken(tkRef) == mdtMemberRef ); |
1102 | tkTo = m_MemberRefToMemberDefMap[RidFromToken(tkRef)]; |
1103 | } |
1104 | if (RidFromToken(tkTo) == mdTokenNil) |
1105 | { |
1106 | *ptkDef = tkRef; |
1107 | return false; |
1108 | } |
1109 | *ptkDef = tkTo; |
1110 | return true; |
1111 | } // ResolveRefToDef |
1112 | |
1113 | |
1114 | |
1115 | //********************************************************************************************************* |
1116 | // |
1117 | // Destructor |
1118 | // |
1119 | //********************************************************************************************************* |
1120 | TokenRemapManager::~TokenRemapManager() |
1121 | { |
1122 | m_TypeRefToTypeDefMap.Clear(); |
1123 | m_MemberRefToMemberDefMap.Clear(); |
1124 | } // ~TokenRemapManager |
1125 | |
1126 | |
1127 | //********************************************************************************************************* |
1128 | // |
1129 | // Initialize the size of Ref to Def optimization table. We will grow the tables in this function. |
1130 | // We also initialize the table entries to zero. |
1131 | // |
1132 | //********************************************************************************************************* |
1133 | HRESULT TokenRemapManager::ClearAndEnsureCapacity( |
1134 | ULONG cTypeRef, |
1135 | ULONG cMemberRef) |
1136 | { |
1137 | HRESULT hr = NOERROR; |
1138 | if ( ((ULONG) (m_TypeRefToTypeDefMap.Count())) < (cTypeRef + 1) ) |
1139 | { |
1140 | if ( m_TypeRefToTypeDefMap.AllocateBlock(cTypeRef + 1 - m_TypeRefToTypeDefMap.Count() ) == 0 ) |
1141 | IfFailGo( E_OUTOFMEMORY ); |
1142 | } |
1143 | memset( m_TypeRefToTypeDefMap.Get(0), 0, (cTypeRef + 1) * sizeof(mdToken) ); |
1144 | |
1145 | if ( ((ULONG) (m_MemberRefToMemberDefMap.Count())) < (cMemberRef + 1) ) |
1146 | { |
1147 | if ( m_MemberRefToMemberDefMap.AllocateBlock(cMemberRef + 1 - m_MemberRefToMemberDefMap.Count() ) == 0 ) |
1148 | IfFailGo( E_OUTOFMEMORY ); |
1149 | } |
1150 | memset( m_MemberRefToMemberDefMap.Get(0), 0, (cMemberRef + 1) * sizeof(mdToken) ); |
1151 | |
1152 | ErrExit: |
1153 | return hr; |
1154 | } // HRESULT TokenRemapManager::ClearAndEnsureCapacity() |
1155 | |
1156 | |
1157 | |
1158 | //********************************************************************************************************* |
1159 | // |
1160 | // Constructor |
1161 | // |
1162 | //********************************************************************************************************* |
1163 | CMDSemReadWrite::CMDSemReadWrite( |
1164 | UTSemReadWrite * pSem) |
1165 | { |
1166 | m_fLockedForRead = false; |
1167 | m_fLockedForWrite = false; |
1168 | m_pSem = pSem; |
1169 | } // CMDSemReadWrite::CMDSemReadWrite |
1170 | |
1171 | |
1172 | |
1173 | //********************************************************************************************************* |
1174 | // |
1175 | // Destructor |
1176 | // |
1177 | //********************************************************************************************************* |
1178 | CMDSemReadWrite::~CMDSemReadWrite() |
1179 | { |
1180 | _ASSERTE(!m_fLockedForRead || !m_fLockedForWrite); |
1181 | if (m_pSem == NULL) |
1182 | { |
1183 | return; |
1184 | } |
1185 | if (m_fLockedForRead) |
1186 | { |
1187 | LOG((LF_METADATA, LL_EVERYTHING, "UnlockRead called from CSemReadWrite::~CSemReadWrite \n" )); |
1188 | m_pSem->UnlockRead(); |
1189 | } |
1190 | if (m_fLockedForWrite) |
1191 | { |
1192 | LOG((LF_METADATA, LL_EVERYTHING, "UnlockWrite called from CSemReadWrite::~CSemReadWrite \n" )); |
1193 | m_pSem->UnlockWrite(); |
1194 | } |
1195 | } // CMDSemReadWrite::~CMDSemReadWrite |
1196 | |
1197 | //********************************************************************************************************* |
1198 | // |
1199 | // Used to obtain the read lock |
1200 | // |
1201 | //********************************************************************************************************* |
1202 | HRESULT CMDSemReadWrite::LockRead() |
1203 | { |
1204 | HRESULT hr = S_OK; |
1205 | |
1206 | _ASSERTE(!m_fLockedForRead && !m_fLockedForWrite); |
1207 | |
1208 | if (m_pSem == NULL) |
1209 | { |
1210 | INDEBUG(m_fLockedForRead = true); |
1211 | return hr; |
1212 | } |
1213 | |
1214 | LOG((LF_METADATA, LL_EVERYTHING, "LockRead called from CSemReadWrite::LockRead \n" )); |
1215 | IfFailRet(m_pSem->LockRead()); |
1216 | m_fLockedForRead = true; |
1217 | |
1218 | return hr; |
1219 | } // CMDSemReadWrite::LockRead |
1220 | |
1221 | //********************************************************************************************************* |
1222 | // |
1223 | // Used to obtain the read lock |
1224 | // |
1225 | //********************************************************************************************************* |
1226 | HRESULT CMDSemReadWrite::LockWrite() |
1227 | { |
1228 | HRESULT hr = S_OK; |
1229 | |
1230 | _ASSERTE(!m_fLockedForRead && !m_fLockedForWrite); |
1231 | |
1232 | if (m_pSem == NULL) |
1233 | { |
1234 | INDEBUG(m_fLockedForWrite = true); |
1235 | return hr; |
1236 | } |
1237 | |
1238 | LOG((LF_METADATA, LL_EVERYTHING, "LockWrite called from CSemReadWrite::LockWrite \n" )); |
1239 | IfFailRet(m_pSem->LockWrite()); |
1240 | m_fLockedForWrite = true; |
1241 | |
1242 | return hr; |
1243 | } |
1244 | |
1245 | //********************************************************************************************************* |
1246 | // |
1247 | // Convert a read lock to a write lock |
1248 | // |
1249 | //********************************************************************************************************* |
1250 | HRESULT CMDSemReadWrite::ConvertReadLockToWriteLock() |
1251 | { |
1252 | _ASSERTE(!m_fLockedForWrite); |
1253 | |
1254 | HRESULT hr = S_OK; |
1255 | |
1256 | if (m_pSem == NULL) |
1257 | { |
1258 | INDEBUG(m_fLockedForRead = false); |
1259 | INDEBUG(m_fLockedForWrite = true); |
1260 | return hr; |
1261 | } |
1262 | |
1263 | if (m_fLockedForRead) |
1264 | { |
1265 | LOG((LF_METADATA, LL_EVERYTHING, "UnlockRead called from CSemReadWrite::ConvertReadLockToWriteLock \n" )); |
1266 | m_pSem->UnlockRead(); |
1267 | m_fLockedForRead = false; |
1268 | } |
1269 | LOG((LF_METADATA, LL_EVERYTHING, "LockWrite called from CSemReadWrite::ConvertReadLockToWriteLock\n" )); |
1270 | IfFailRet(m_pSem->LockWrite()); |
1271 | m_fLockedForWrite = true; |
1272 | |
1273 | return hr; |
1274 | } // CMDSemReadWrite::ConvertReadLockToWriteLock |
1275 | |
1276 | |
1277 | //********************************************************************************************************* |
1278 | // |
1279 | // Unlocking for write |
1280 | // |
1281 | //********************************************************************************************************* |
1282 | void CMDSemReadWrite::UnlockWrite() |
1283 | { |
1284 | _ASSERTE(!m_fLockedForRead); |
1285 | |
1286 | if (m_pSem == NULL) |
1287 | { |
1288 | INDEBUG(m_fLockedForWrite = false); |
1289 | return; |
1290 | } |
1291 | if (m_fLockedForWrite) |
1292 | { |
1293 | LOG((LF_METADATA, LL_EVERYTHING, "UnlockWrite called from CSemReadWrite::UnlockWrite \n" )); |
1294 | m_pSem->UnlockWrite(); |
1295 | m_fLockedForWrite = false; |
1296 | } |
1297 | } // CMDSemReadWrite::UnlockWrite |
1298 | |