1 // Copyright (c) Microsoft. All rights reserved. 2 // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 4 using System; 5 using System.Globalization; 6 using System.Runtime.InteropServices.ComTypes; 7 using System.Text; 8 9 using Microsoft.Build.Shared; 10 using Microsoft.Build.Utilities; 11 12 using Marshal = System.Runtime.InteropServices.Marshal; 13 using COMException = System.Runtime.InteropServices.COMException; 14 15 namespace Microsoft.Build.Tasks 16 { 17 /* 18 * Class: ComReference 19 * 20 * Abstract base class for COM reference wrappers providing common functionality. 21 * This class hierarchy is used by the ResolveComReference task. Every class deriving from ComReference 22 * provides functionality for wrapping Com type libraries in a given way (for example AxReference, or PiaReference). 23 * 24 */ 25 internal abstract class ComReference 26 { 27 #region Constructors 28 29 /// <summary> 30 /// Internal constructor 31 /// </summary> 32 /// <param name="taskLoggingHelper">task logger instance used for logging</param> 33 /// <param name="silent">true if this task should log only errors, no warnings or messages; false otherwise</param> 34 /// <param name="referenceInfo">cached reference information (typelib pointer, original task item, typelib name etc.)</param> 35 /// <param name="itemName">reference name (for better logging experience)</param> ComReference(TaskLoggingHelper taskLoggingHelper, bool silent, ComReferenceInfo referenceInfo, string itemName)36 internal ComReference(TaskLoggingHelper taskLoggingHelper, bool silent, ComReferenceInfo referenceInfo, string itemName) 37 { 38 _referenceInfo = referenceInfo; 39 _itemName = itemName; 40 _log = taskLoggingHelper; 41 _silent = silent; 42 } 43 44 #endregion 45 46 #region Properties 47 48 /// <summary> 49 /// various data for this reference (type lib attrs, name, path, ITypeLib pointer etc) 50 /// </summary> 51 internal virtual ComReferenceInfo ReferenceInfo 52 { 53 get 54 { 55 return _referenceInfo; 56 } 57 } 58 59 private ComReferenceInfo _referenceInfo; 60 61 /// <summary> 62 /// item name as it appears in the project file 63 /// (used for logging purposes, we use the actual typelib name for interesting operations) 64 /// </summary> 65 internal virtual string ItemName 66 { 67 get 68 { 69 return _itemName; 70 } 71 } 72 73 private string _itemName; 74 75 /// <summary> 76 /// task used for logging messages 77 /// </summary> 78 protected internal TaskLoggingHelper Log 79 { 80 get 81 { 82 return _log; 83 } 84 } 85 86 private TaskLoggingHelper _log; 87 88 /// <summary> 89 /// True if this class should only log errors, but no messages or warnings. 90 /// </summary> 91 protected internal bool Silent 92 { 93 get 94 { 95 return _silent; 96 } 97 } 98 99 private bool _silent; 100 101 /// <summary> 102 /// lazy-init property, returns true if ADO 2.7 is installed on the machine 103 /// </summary> 104 internal static bool Ado27Installed 105 { 106 get 107 { 108 // if we already know the answer, return it 109 if (ado27PropertyInitialized) 110 return ado27Installed; 111 112 // not initialized? Find out if ADO 2.7 is installed 113 ado27Installed = true; 114 ado27PropertyInitialized = true; 115 116 ITypeLib ado27 = null; 117 118 try 119 { 120 // see if ADO 2.7 is registered. 121 ado27 = (ITypeLib)NativeMethods.LoadRegTypeLib(ref s_guidADO27, 2, 7, 0); 122 } 123 catch (COMException ex) 124 { 125 // it's not registered. 126 ado27Installed = false; 127 ado27ErrorMessage = ex.Message; 128 } 129 finally 130 { 131 if (ado27 != null) 132 Marshal.ReleaseComObject(ado27); 133 } 134 135 return ado27Installed; 136 } 137 } 138 139 internal static bool ado27PropertyInitialized = false; 140 internal static bool ado27Installed; 141 142 /// <summary> 143 /// Error message if Ado27 is not installed on the machine (usually something like "type lib not registered") 144 /// Only contains valid data if ADO 2.7 is not installed and Ado27Installed was called before 145 /// </summary> 146 internal static string Ado27ErrorMessage 147 { 148 get 149 { 150 return ado27ErrorMessage; 151 } 152 } 153 154 internal static string ado27ErrorMessage; 155 156 #endregion 157 158 #region Methods 159 160 /* 161 * Method: UniqueKeyFromTypeLibAttr 162 * 163 * Given a TYPELIBATTR structure, generates a key that can be used in hashtables to identify it. 164 */ UniqueKeyFromTypeLibAttr(TYPELIBATTR attr)165 internal static string UniqueKeyFromTypeLibAttr(TYPELIBATTR attr) 166 { 167 return String.Format(CultureInfo.InvariantCulture, @"{0}|{1}.{2}|{3}", attr.guid, attr.wMajorVerNum, attr.wMinorVerNum, attr.lcid); 168 } 169 170 /* 171 * Method: AreTypeLibAttrEqual 172 * 173 * Compares two TYPELIBATTR structures 174 */ AreTypeLibAttrEqual(TYPELIBATTR attr1, TYPELIBATTR attr2)175 internal static bool AreTypeLibAttrEqual(TYPELIBATTR attr1, TYPELIBATTR attr2) 176 { 177 return attr1.wMajorVerNum == attr2.wMajorVerNum && 178 attr1.wMinorVerNum == attr2.wMinorVerNum && 179 attr1.lcid == attr2.lcid && 180 attr1.guid == attr2.guid; 181 } 182 183 /// <summary> 184 /// Helper method for retrieving type lib attributes for the given type lib 185 /// </summary> 186 /// <param name="typeLib"></param> 187 /// <param name="typeLibAttr"></param> 188 /// <returns></returns> GetTypeLibAttrForTypeLib(ref ITypeLib typeLib, out TYPELIBATTR typeLibAttr)189 internal static void GetTypeLibAttrForTypeLib(ref ITypeLib typeLib, out TYPELIBATTR typeLibAttr) 190 { 191 IntPtr pAttrs = IntPtr.Zero; 192 typeLib.GetLibAttr(out pAttrs); 193 194 // GetLibAttr should never return null, this is just to be safe 195 if (pAttrs == IntPtr.Zero) 196 { 197 throw new COMException( 198 ResourceUtilities.GetResourceString("ResolveComReference.CannotGetTypeLibAttrForTypeLib")); 199 } 200 201 try 202 { 203 typeLibAttr = (TYPELIBATTR)Marshal.PtrToStructure(pAttrs, typeof(TYPELIBATTR)); 204 } 205 finally 206 { 207 typeLib.ReleaseTLibAttr(pAttrs); 208 } 209 } 210 211 /// <summary> 212 /// Helper method for retrieving type attributes for a given type info 213 /// </summary> 214 /// <param name="typeInfo"></param> 215 /// <param name="typeAttr"></param> 216 /// <returns></returns> GetTypeAttrForTypeInfo(ITypeInfo typeInfo, out TYPEATTR typeAttr)217 internal static void GetTypeAttrForTypeInfo(ITypeInfo typeInfo, out TYPEATTR typeAttr) 218 { 219 IntPtr pAttrs = IntPtr.Zero; 220 typeInfo.GetTypeAttr(out pAttrs); 221 222 // GetTypeAttr should never return null, this is just to be safe 223 if (pAttrs == IntPtr.Zero) 224 { 225 throw new COMException( 226 ResourceUtilities.GetResourceString("ResolveComReference.CannotRetrieveTypeInformation")); 227 } 228 229 try 230 { 231 typeAttr = (TYPEATTR)Marshal.PtrToStructure(pAttrs, typeof(TYPEATTR)); 232 } 233 finally 234 { 235 typeInfo.ReleaseTypeAttr(pAttrs); 236 } 237 } 238 239 /// <summary> 240 /// Helper method for retrieving type attributes for a given type info 241 /// This method needs to also return the native pointer to be released when we're done with our VARDESC. 242 /// It's not really possible to copy everything to a managed struct and then release the ptr immediately 243 /// here, since VARDESCs contain other native pointers we may need to access. 244 /// </summary> 245 /// <param name="typeInfo"></param> 246 /// <param name="varIndex"></param> 247 /// <param name="typeAttr"></param> 248 /// <param name="varDesc"></param> 249 /// <param name="varDescHandle"></param> 250 /// <returns></returns> GetVarDescForVarIndex(ITypeInfo typeInfo, int varIndex, out VARDESC varDesc, out IntPtr varDescHandle)251 internal static void GetVarDescForVarIndex(ITypeInfo typeInfo, int varIndex, out VARDESC varDesc, out IntPtr varDescHandle) 252 { 253 IntPtr pVarDesc = IntPtr.Zero; 254 typeInfo.GetVarDesc(varIndex, out pVarDesc); 255 256 // GetVarDesc should never return null, this is just to be safe 257 if (pVarDesc == IntPtr.Zero) 258 { 259 throw new COMException( 260 ResourceUtilities.GetResourceString("ResolveComReference.CannotRetrieveTypeInformation")); 261 } 262 263 varDesc = (VARDESC)Marshal.PtrToStructure(pVarDesc, typeof(VARDESC)); 264 varDescHandle = pVarDesc; 265 } 266 267 /// <summary> 268 /// Helper method for retrieving the function description structure for the given function index. 269 /// This method needs to also return the native pointer to be released when we're done with our FUNCDESC. 270 /// It's not really possible to copy everything to a managed struct and then release the ptr immediately 271 /// here, since FUNCDESCs contain other native pointers we may need to access. 272 /// </summary> 273 /// <param name="typeInfo"></param> 274 /// <param name="funcIndex"></param> 275 /// <param name="funcDesc"></param> 276 /// <param name="funcDescHandle"></param> 277 /// <returns></returns> GetFuncDescForDescIndex(ITypeInfo typeInfo, int funcIndex, out FUNCDESC funcDesc, out IntPtr funcDescHandle)278 internal static void GetFuncDescForDescIndex(ITypeInfo typeInfo, int funcIndex, out FUNCDESC funcDesc, out IntPtr funcDescHandle) 279 { 280 IntPtr pFuncDesc = IntPtr.Zero; 281 typeInfo.GetFuncDesc(funcIndex, out pFuncDesc); 282 283 // GetFuncDesc should never return null, this is just to be safe 284 if (pFuncDesc == IntPtr.Zero) 285 { 286 throw new COMException( 287 ResourceUtilities.GetResourceString("ResolveComReference.CannotRetrieveTypeInformation")); 288 } 289 290 funcDesc = (FUNCDESC)Marshal.PtrToStructure(pFuncDesc, typeof(FUNCDESC)); 291 funcDescHandle = pFuncDesc; 292 } 293 294 /* 295 * Method: GetTypeLibNameForITypeLib 296 * 297 * Gets the name of given type library. 298 */ GetTypeLibNameForITypeLib(TaskLoggingHelper log, bool silent, ITypeLib typeLib, string typeLibId, out string typeLibName)299 internal static bool GetTypeLibNameForITypeLib(TaskLoggingHelper log, bool silent, ITypeLib typeLib, string typeLibId, out string typeLibName) 300 { 301 typeLibName = ""; 302 303 // see if the type library supports ITypeLib2 304 ITypeLib2 typeLib2 = typeLib as ITypeLib2; 305 306 if (typeLib2 == null) 307 { 308 // Looks like the type lib doesn't support it. Let's use the Marshal method. 309 typeLibName = Marshal.GetTypeLibName(typeLib); 310 return true; 311 } 312 313 // Get the custom attribute. If anything fails then just return the 314 // type library name. 315 try 316 { 317 object data = null; 318 319 typeLib2.GetCustData(ref NativeMethods.GUID_TYPELIB_NAMESPACE, out data); 320 321 // if returned namespace is null or its type is not System.String, fall back to the default 322 // way of getting the type lib name (just to be safe) 323 if (data == null || string.Compare(data.GetType().ToString(), "system.string", StringComparison.OrdinalIgnoreCase) != 0) 324 { 325 typeLibName = Marshal.GetTypeLibName(typeLib); 326 return true; 327 } 328 329 // Strip off the DLL extension if it's there 330 typeLibName = (string)data; 331 332 if (typeLibName.Length >= 4) 333 { 334 if (string.Compare(typeLibName.Substring(typeLibName.Length - 4), ".dll", StringComparison.OrdinalIgnoreCase) == 0) 335 { 336 typeLibName = typeLibName.Substring(0, typeLibName.Length - 4); 337 } 338 } 339 } 340 catch (COMException ex) 341 { 342 // If anything fails log a warning and just return the type library name. 343 if (!silent) 344 { 345 log.LogWarningWithCodeFromResources("ResolveComReference.CannotAccessTypeLibName", typeLibId, ex.Message); 346 } 347 typeLibName = Marshal.GetTypeLibName(typeLib); 348 return true; 349 } 350 351 return true; 352 } 353 354 /* 355 * Method: GetTypeLibNameForTypeLibAttrs 356 * 357 * Gets the name of given type library. 358 */ GetTypeLibNameForTypeLibAttrs(TaskLoggingHelper log, bool silent, TYPELIBATTR typeLibAttr, out string typeLibName)359 internal static bool GetTypeLibNameForTypeLibAttrs(TaskLoggingHelper log, bool silent, TYPELIBATTR typeLibAttr, out string typeLibName) 360 { 361 typeLibName = ""; 362 ITypeLib typeLib = null; 363 364 try 365 { 366 // load our type library 367 try 368 { 369 TYPELIBATTR attr = typeLibAttr; 370 typeLib = (ITypeLib)NativeMethods.LoadRegTypeLib(ref attr.guid, attr.wMajorVerNum, attr.wMinorVerNum, attr.lcid); 371 } 372 catch (COMException ex) 373 { 374 if (!silent) 375 { 376 log.LogWarningWithCodeFromResources("ResolveComReference.CannotLoadTypeLib", typeLibAttr.guid, typeLibAttr.wMajorVerNum, typeLibAttr.wMinorVerNum, ex.Message); 377 } 378 379 return false; 380 } 381 382 string typeLibId = log.FormatResourceString("ResolveComReference.TypeLibAttrId", typeLibAttr.guid.ToString(), typeLibAttr.wMajorVerNum, typeLibAttr.wMinorVerNum); 383 384 return GetTypeLibNameForITypeLib(log, silent, typeLib, typeLibId, out typeLibName); 385 } 386 finally 387 { 388 if (typeLib != null) 389 Marshal.ReleaseComObject(typeLib); 390 } 391 } 392 393 /// <summary> 394 /// Strips type library number from a type library path (for example, "ref.dll\2" becomes "ref.dll") 395 /// </summary> 396 /// <param name="typeLibPath">type library path with possible typelib number appended to it</param> 397 /// <returns>proper file path to the type library</returns> StripTypeLibNumberFromPath(string typeLibPath, FileExists fileExists)398 internal static string StripTypeLibNumberFromPath(string typeLibPath, FileExists fileExists) 399 { 400 bool lastChance = false; 401 if (typeLibPath != null && typeLibPath.Length > 0) 402 { 403 if (!fileExists(typeLibPath)) 404 { 405 // Strip the type library number 406 int lastSlash = typeLibPath.LastIndexOf('\\'); 407 408 if (lastSlash != -1) 409 { 410 bool allNumbers = true; 411 412 for (int i = lastSlash + 1; i < typeLibPath.Length; i++) 413 { 414 if (!Char.IsDigit(typeLibPath[i])) 415 { 416 allNumbers = false; 417 break; 418 } 419 } 420 421 // If we had all numbers past the last slash then we're OK to strip 422 // the type library number 423 if (allNumbers) 424 { 425 typeLibPath = typeLibPath.Substring(0, lastSlash); 426 if (!fileExists(typeLibPath)) 427 { 428 lastChance = true; 429 } 430 } 431 else 432 { 433 lastChance = true; 434 } 435 } 436 else 437 { 438 lastChance = true; 439 } 440 } 441 } 442 443 // If we couldn't find the path directly, we'll use the same mechanism Windows uses to find 444 // libraries. LoadLibrary() will search all of the correct paths to find this module. We can then 445 // use GetModuleFileName() to determine the actual path from which the module was loaded. This problem 446 // was exposed in Vista where certain libraries are registered but are lacking paths in the registry, 447 // so the old code would fail to find them on disk using the simplistic checks above. 448 if (lastChance) 449 { 450 IntPtr libraryHandle = NativeMethodsShared.LoadLibrary(typeLibPath); 451 if (IntPtr.Zero != libraryHandle) 452 { 453 try 454 { 455 StringBuilder sb = new StringBuilder(NativeMethodsShared.MAX_PATH); 456 System.Runtime.InteropServices.HandleRef handleRef = new System.Runtime.InteropServices.HandleRef(sb, libraryHandle); 457 int len = NativeMethodsShared.GetModuleFileName(handleRef, sb, sb.Capacity); 458 if ((len != 0) && 459 ((uint)Marshal.GetLastWin32Error() != NativeMethodsShared.ERROR_INSUFFICIENT_BUFFER)) 460 { 461 typeLibPath = sb.ToString(); 462 } 463 else 464 { 465 typeLibPath = ""; 466 } 467 } 468 finally 469 { 470 NativeMethodsShared.FreeLibrary(libraryHandle); 471 } 472 } 473 else 474 { 475 typeLibPath = ""; 476 } 477 } 478 479 return typeLibPath; 480 } 481 482 /* 483 * Method: GetPathOfTypeLib 484 * 485 * Gets the type lib path for given type lib attributes (reused almost verbatim from vsdesigner utils code) 486 * NOTE: If there's a typelib number at the end of the path, does NOT strip it. 487 */ GetPathOfTypeLib(TaskLoggingHelper log, bool silent, ref TYPELIBATTR typeLibAttr, out string typeLibPath)488 internal static bool GetPathOfTypeLib(TaskLoggingHelper log, bool silent, ref TYPELIBATTR typeLibAttr, out string typeLibPath) 489 { 490 // Get which file the type library resides in. If the appropriate 491 // file cannot be found then a blank string is returned. 492 typeLibPath = ""; 493 494 try 495 { 496 // Get the path from the registry 497 // This call has known issues. See http://msdn.microsoft.com/en-us/library/ms221436.aspx for the method and 498 // here for the fix http://support.microsoft.com/kb/982110. Most users from Win7 or Win2008R2 should have already received this post Win7SP1. 499 // In Summary: The issue is about calls to The QueryPathOfRegTypeLib function not returning the correct path for a 32-bit version of a 500 // registered type library in a 64-bit edition of Windows 7 or in Windows Server 2008 R2. It either returns the 64bit path or null. 501 typeLibPath = NativeMethods.QueryPathOfRegTypeLib(ref typeLibAttr.guid, typeLibAttr.wMajorVerNum, typeLibAttr.wMinorVerNum, typeLibAttr.lcid); 502 typeLibPath = Environment.ExpandEnvironmentVariables(typeLibPath); 503 } 504 catch (COMException ex) 505 { 506 if (!silent) 507 { 508 log.LogWarningWithCodeFromResources("ResolveComReference.CannotGetPathForTypeLib", typeLibAttr.guid, typeLibAttr.wMajorVerNum, typeLibAttr.wMinorVerNum, ex.Message); 509 } 510 511 return false; 512 } 513 514 if (typeLibPath != null && typeLibPath.Length > 0) 515 { 516 // We have to check for NULL here because QueryPathOfRegTypeLib() returns 517 // a BSTR with a NULL character appended to it. 518 if (typeLibPath[typeLibPath.Length - 1] == '\0') 519 { 520 typeLibPath = typeLibPath.Substring(0, typeLibPath.Length - 1); 521 } 522 } 523 524 if (typeLibPath != null && typeLibPath.Length > 0) 525 { 526 return true; 527 } 528 else 529 { 530 if (!silent) 531 { 532 log.LogWarningWithCodeFromResources("ResolveComReference.CannotGetPathForTypeLib", typeLibAttr.guid, typeLibAttr.wMajorVerNum, typeLibAttr.wMinorVerNum, ""); 533 } 534 535 return false; 536 } 537 } 538 539 #region RemapAdoTypeLib guids 540 541 // guids for RemapAdoTypeLib 542 private readonly static Guid s_guidADO20 = new Guid("{00000200-0000-0010-8000-00AA006D2EA4}"); 543 private readonly static Guid s_guidADO21 = new Guid("{00000201-0000-0010-8000-00AA006D2EA4}"); 544 private readonly static Guid s_guidADO25 = new Guid("{00000205-0000-0010-8000-00AA006D2EA4}"); 545 private readonly static Guid s_guidADO26 = new Guid("{00000206-0000-0010-8000-00AA006D2EA4}"); 546 // unfortunately this cannot be readonly, since it's being passed by reference to LoadRegTypeLib 547 private static Guid s_guidADO27 = new Guid("{EF53050B-882E-4776-B643-EDA472E8E3F2}"); 548 549 #endregion 550 551 /* 552 * Method: RemapAdoTypeLib 553 * 554 * Tries to remap an ADO type library to ADO 2.7. If the type library passed in is an older ADO tlb, 555 * then remap it to ADO 2.7 if it's registered on the machine (!). Otherwise don't modify the typelib. 556 * Returns true if the type library passed in was successfully remapped. 557 */ RemapAdoTypeLib(TaskLoggingHelper log, bool silent, ref TYPELIBATTR typeLibAttr)558 internal static bool RemapAdoTypeLib(TaskLoggingHelper log, bool silent, ref TYPELIBATTR typeLibAttr) 559 { 560 // we only care about ADO 2.0, 2.1, 2.5 or 2.6 here. 561 if (typeLibAttr.wMajorVerNum == 2) 562 { 563 if ((typeLibAttr.wMinorVerNum == 0 && typeLibAttr.guid == s_guidADO20) || 564 (typeLibAttr.wMinorVerNum == 1 && typeLibAttr.guid == s_guidADO21) || 565 (typeLibAttr.wMinorVerNum == 5 && typeLibAttr.guid == s_guidADO25) || 566 (typeLibAttr.wMinorVerNum == 6 && typeLibAttr.guid == s_guidADO26)) 567 { 568 // see if ADO 2.7 is registered. 569 if (!Ado27Installed) 570 { 571 if (!silent) 572 { 573 // it's not registered. Don't change the original typelib then. 574 log.LogWarningWithCodeFromResources("ResolveComReference.FailedToRemapAdoTypeLib", typeLibAttr.wMajorVerNum, typeLibAttr.wMinorVerNum, Ado27ErrorMessage); 575 } 576 577 return false; 578 } 579 580 typeLibAttr.guid = s_guidADO27; 581 typeLibAttr.wMajorVerNum = 2; 582 typeLibAttr.wMinorVerNum = 7; 583 typeLibAttr.lcid = 0; 584 585 return true; 586 } 587 } 588 589 return false; 590 } 591 592 /// <summary> 593 /// Finds an existing wrapper for the specified component 594 /// </summary> 595 /// <param name="wrapperInfo"></param> 596 /// <param name="componentTimestamp"></param> 597 /// <returns></returns> FindExistingWrapper(out ComReferenceWrapperInfo wrapperInfo, DateTime componentTimestamp)598 internal abstract bool FindExistingWrapper(out ComReferenceWrapperInfo wrapperInfo, DateTime componentTimestamp); 599 600 #endregion 601 } 602 } 603