cmCallVisualStudioMacro.cxx 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461
  1. /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
  2. file Copyright.txt or https://cmake.org/licensing for details. */
  3. #include "cmCallVisualStudioMacro.h"
  4. #include <sstream>
  5. #include "cmStringAlgorithms.h"
  6. #include "cmSystemTools.h"
  7. #if defined(_MSC_VER)
  8. # define HAVE_COMDEF_H
  9. #endif
  10. // Just for this file:
  11. //
  12. static bool LogErrorsAsMessages;
  13. #if defined(HAVE_COMDEF_H)
  14. # include <comdef.h>
  15. // Copied from a correct comdef.h to avoid problems with deficient versions
  16. // of comdef.h that exist in the wild... Fixes issue #7533.
  17. //
  18. # ifdef _NATIVE_WCHAR_T_DEFINED
  19. # ifdef _DEBUG
  20. # pragma comment(lib, "comsuppwd.lib")
  21. # else
  22. # pragma comment(lib, "comsuppw.lib")
  23. # endif
  24. # else
  25. # ifdef _DEBUG
  26. # pragma comment(lib, "comsuppd.lib")
  27. # else
  28. # pragma comment(lib, "comsupp.lib")
  29. # endif
  30. # endif
  31. //! Use ReportHRESULT to make a cmSystemTools::Message after calling
  32. //! a COM method that may have failed.
  33. # define ReportHRESULT(hr, context) \
  34. if (FAILED(hr)) { \
  35. if (LogErrorsAsMessages) { \
  36. std::ostringstream _hresult_oss; \
  37. _hresult_oss.flags(std::ios::hex); \
  38. _hresult_oss << context << " failed HRESULT, hr = 0x" << hr << '\n'; \
  39. _hresult_oss.flags(std::ios::dec); \
  40. _hresult_oss << __FILE__ << "(" << __LINE__ << ")"; \
  41. cmSystemTools::Message(_hresult_oss.str()); \
  42. } \
  43. }
  44. //! Using the given instance of Visual Studio, call the named macro
  45. HRESULT InstanceCallMacro(IDispatch* vsIDE, const std::string& macro,
  46. const std::string& args)
  47. {
  48. HRESULT hr = E_POINTER;
  49. _bstr_t macroName(macro.c_str());
  50. _bstr_t macroArgs(args.c_str());
  51. if (0 != vsIDE) {
  52. DISPID dispid = (DISPID)-1;
  53. wchar_t execute_command[] = L"ExecuteCommand";
  54. OLECHAR* name = execute_command;
  55. hr =
  56. vsIDE->GetIDsOfNames(IID_NULL, &name, 1, LOCALE_USER_DEFAULT, &dispid);
  57. ReportHRESULT(hr, "GetIDsOfNames(ExecuteCommand)");
  58. if (SUCCEEDED(hr)) {
  59. VARIANTARG vargs[2];
  60. DISPPARAMS params;
  61. VARIANT result;
  62. EXCEPINFO excep;
  63. UINT arg = (UINT)-1;
  64. // No VariantInit or VariantClear calls are necessary for
  65. // these two vargs. They are both local _bstr_t variables
  66. // that remain in scope for the duration of the Invoke call.
  67. //
  68. V_VT(&vargs[1]) = VT_BSTR;
  69. V_BSTR(&vargs[1]) = macroName;
  70. V_VT(&vargs[0]) = VT_BSTR;
  71. V_BSTR(&vargs[0]) = macroArgs;
  72. params.rgvarg = &vargs[0];
  73. params.rgdispidNamedArgs = 0;
  74. params.cArgs = sizeof(vargs) / sizeof(vargs[0]);
  75. params.cNamedArgs = 0;
  76. VariantInit(&result);
  77. memset(&excep, 0, sizeof(excep));
  78. hr = vsIDE->Invoke(dispid, IID_NULL, LOCALE_USER_DEFAULT,
  79. DISPATCH_METHOD, &params, &result, &excep, &arg);
  80. std::ostringstream oss;
  81. /* clang-format off */
  82. oss << "\nInvoke(ExecuteCommand)\n"
  83. " Macro: " << macro << "\n"
  84. " Args: " << args << '\n';
  85. /* clang-format on */
  86. if (DISP_E_EXCEPTION == hr) {
  87. /* clang-format off */
  88. oss << "DISP_E_EXCEPTION EXCEPINFO:" << excep.wCode << "\n"
  89. " wCode: " << excep.wCode << "\n"
  90. " wReserved: " << excep.wReserved << '\n';
  91. /* clang-format on */
  92. if (excep.bstrSource) {
  93. oss << " bstrSource: " << (const char*)(_bstr_t)excep.bstrSource
  94. << '\n';
  95. }
  96. if (excep.bstrDescription) {
  97. oss << " bstrDescription: "
  98. << (const char*)(_bstr_t)excep.bstrDescription << '\n';
  99. }
  100. if (excep.bstrHelpFile) {
  101. oss << " bstrHelpFile: " << (const char*)(_bstr_t)excep.bstrHelpFile
  102. << '\n';
  103. }
  104. /* clang-format off */
  105. oss << " dwHelpContext: " << excep.dwHelpContext << "\n"
  106. " pvReserved: " << excep.pvReserved << "\n"
  107. " pfnDeferredFillIn: "
  108. << reinterpret_cast<void*>(excep.pfnDeferredFillIn) << "\n"
  109. " scode: " << excep.scode << '\n';
  110. /* clang-format on */
  111. }
  112. std::string exstr(oss.str());
  113. ReportHRESULT(hr, exstr.c_str());
  114. VariantClear(&result);
  115. }
  116. }
  117. return hr;
  118. }
  119. //! Get the Solution object from the IDE object
  120. HRESULT GetSolutionObject(IDispatch* vsIDE, IDispatchPtr& vsSolution)
  121. {
  122. HRESULT hr = E_POINTER;
  123. if (0 != vsIDE) {
  124. DISPID dispid = (DISPID)-1;
  125. wchar_t solution[] = L"Solution";
  126. OLECHAR* name = solution;
  127. hr =
  128. vsIDE->GetIDsOfNames(IID_NULL, &name, 1, LOCALE_USER_DEFAULT, &dispid);
  129. ReportHRESULT(hr, "GetIDsOfNames(Solution)");
  130. if (SUCCEEDED(hr)) {
  131. DISPPARAMS params;
  132. VARIANT result;
  133. EXCEPINFO excep;
  134. UINT arg = (UINT)-1;
  135. params.rgvarg = 0;
  136. params.rgdispidNamedArgs = 0;
  137. params.cArgs = 0;
  138. params.cNamedArgs = 0;
  139. VariantInit(&result);
  140. memset(&excep, 0, sizeof(excep));
  141. hr = vsIDE->Invoke(dispid, IID_NULL, LOCALE_USER_DEFAULT,
  142. DISPATCH_PROPERTYGET, &params, &result, &excep, &arg);
  143. ReportHRESULT(hr, "Invoke(Solution)");
  144. if (SUCCEEDED(hr)) {
  145. vsSolution = V_DISPATCH(&result);
  146. }
  147. VariantClear(&result);
  148. }
  149. }
  150. return hr;
  151. }
  152. //! Get the FullName property from the Solution object
  153. HRESULT GetSolutionFullName(IDispatch* vsSolution, std::string& fullName)
  154. {
  155. HRESULT hr = E_POINTER;
  156. if (0 != vsSolution) {
  157. DISPID dispid = (DISPID)-1;
  158. wchar_t full_name[] = L"FullName";
  159. OLECHAR* name = full_name;
  160. hr = vsSolution->GetIDsOfNames(IID_NULL, &name, 1, LOCALE_USER_DEFAULT,
  161. &dispid);
  162. ReportHRESULT(hr, "GetIDsOfNames(FullName)");
  163. if (SUCCEEDED(hr)) {
  164. DISPPARAMS params;
  165. VARIANT result;
  166. EXCEPINFO excep;
  167. UINT arg = (UINT)-1;
  168. params.rgvarg = 0;
  169. params.rgdispidNamedArgs = 0;
  170. params.cArgs = 0;
  171. params.cNamedArgs = 0;
  172. VariantInit(&result);
  173. memset(&excep, 0, sizeof(excep));
  174. hr = vsSolution->Invoke(dispid, IID_NULL, LOCALE_USER_DEFAULT,
  175. DISPATCH_PROPERTYGET, &params, &result, &excep,
  176. &arg);
  177. ReportHRESULT(hr, "Invoke(FullName)");
  178. if (SUCCEEDED(hr)) {
  179. fullName = (std::string)(_bstr_t)V_BSTR(&result);
  180. }
  181. VariantClear(&result);
  182. }
  183. }
  184. return hr;
  185. }
  186. //! Get the FullName property from the Solution object, given the IDE object
  187. HRESULT GetIDESolutionFullName(IDispatch* vsIDE, std::string& fullName)
  188. {
  189. IDispatchPtr vsSolution;
  190. HRESULT hr = GetSolutionObject(vsIDE, vsSolution);
  191. ReportHRESULT(hr, "GetSolutionObject");
  192. if (SUCCEEDED(hr)) {
  193. GetSolutionFullName(vsSolution, fullName);
  194. ReportHRESULT(hr, "GetSolutionFullName");
  195. }
  196. return hr;
  197. }
  198. //! Get all running objects from the Windows running object table.
  199. //! Save them in a map by their display names.
  200. HRESULT GetRunningInstances(std::map<std::string, IUnknownPtr>& mrot)
  201. {
  202. // mrot == Map of the Running Object Table
  203. IRunningObjectTablePtr runningObjectTable;
  204. IEnumMonikerPtr monikerEnumerator;
  205. IMonikerPtr moniker;
  206. ULONG numFetched = 0;
  207. HRESULT hr = GetRunningObjectTable(0, &runningObjectTable);
  208. ReportHRESULT(hr, "GetRunningObjectTable");
  209. if (SUCCEEDED(hr)) {
  210. hr = runningObjectTable->EnumRunning(&monikerEnumerator);
  211. ReportHRESULT(hr, "EnumRunning");
  212. }
  213. if (SUCCEEDED(hr)) {
  214. hr = monikerEnumerator->Reset();
  215. ReportHRESULT(hr, "Reset");
  216. }
  217. if (SUCCEEDED(hr)) {
  218. while (S_OK == monikerEnumerator->Next(1, &moniker, &numFetched)) {
  219. std::string runningObjectName;
  220. IUnknownPtr runningObjectVal;
  221. IBindCtxPtr ctx;
  222. hr = CreateBindCtx(0, &ctx);
  223. ReportHRESULT(hr, "CreateBindCtx");
  224. if (SUCCEEDED(hr)) {
  225. LPOLESTR displayName = 0;
  226. hr = moniker->GetDisplayName(ctx, 0, &displayName);
  227. ReportHRESULT(hr, "GetDisplayName");
  228. if (displayName) {
  229. runningObjectName = (std::string)(_bstr_t)displayName;
  230. CoTaskMemFree(displayName);
  231. }
  232. hr = runningObjectTable->GetObject(moniker, &runningObjectVal);
  233. ReportHRESULT(hr, "GetObject");
  234. if (SUCCEEDED(hr)) {
  235. mrot.insert(std::make_pair(runningObjectName, runningObjectVal));
  236. }
  237. }
  238. numFetched = 0;
  239. moniker = 0;
  240. }
  241. }
  242. return hr;
  243. }
  244. //! Do the two file names refer to the same Visual Studio solution? Or are
  245. //! we perhaps looking for any and all solutions?
  246. bool FilesSameSolution(const std::string& slnFile, const std::string& slnName)
  247. {
  248. if (slnFile == "ALL" || slnName == "ALL") {
  249. return true;
  250. }
  251. // Otherwise, make lowercase local copies, convert to Unix slashes, and
  252. // see if the resulting strings are the same:
  253. std::string s1 = cmSystemTools::LowerCase(slnFile);
  254. std::string s2 = cmSystemTools::LowerCase(slnName);
  255. cmSystemTools::ConvertToUnixSlashes(s1);
  256. cmSystemTools::ConvertToUnixSlashes(s2);
  257. return s1 == s2;
  258. }
  259. //! Find instances of Visual Studio with the given solution file
  260. //! open. Pass "ALL" for slnFile to gather all running instances
  261. //! of Visual Studio.
  262. HRESULT FindVisualStudioInstances(const std::string& slnFile,
  263. std::vector<IDispatchPtr>& instances)
  264. {
  265. std::map<std::string, IUnknownPtr> mrot;
  266. HRESULT hr = GetRunningInstances(mrot);
  267. ReportHRESULT(hr, "GetRunningInstances");
  268. if (SUCCEEDED(hr)) {
  269. std::map<std::string, IUnknownPtr>::iterator it;
  270. for (it = mrot.begin(); it != mrot.end(); ++it) {
  271. if (cmHasLiteralPrefix(it->first, "!VisualStudio.DTE.")) {
  272. IDispatchPtr disp(it->second);
  273. if (disp != (IDispatch*)0) {
  274. std::string slnName;
  275. hr = GetIDESolutionFullName(disp, slnName);
  276. ReportHRESULT(hr, "GetIDESolutionFullName");
  277. if (FilesSameSolution(slnFile, slnName)) {
  278. instances.push_back(disp);
  279. // std::cout << "Found Visual Studio instance." << std::endl;
  280. // std::cout << " ROT entry name: " << it->first << std::endl;
  281. // std::cout << " ROT entry object: "
  282. // << (IUnknown*) it->second << std::endl;
  283. // std::cout << " slnFile: " << slnFile << std::endl;
  284. // std::cout << " slnName: " << slnName << std::endl;
  285. }
  286. }
  287. }
  288. }
  289. }
  290. return hr;
  291. }
  292. #endif // defined(HAVE_COMDEF_H)
  293. int cmCallVisualStudioMacro::GetNumberOfRunningVisualStudioInstances(
  294. const std::string& slnFile)
  295. {
  296. int count = 0;
  297. LogErrorsAsMessages = false;
  298. #if defined(HAVE_COMDEF_H)
  299. HRESULT hr = CoInitialize(0);
  300. ReportHRESULT(hr, "CoInitialize");
  301. if (SUCCEEDED(hr)) {
  302. std::vector<IDispatchPtr> instances;
  303. hr = FindVisualStudioInstances(slnFile, instances);
  304. ReportHRESULT(hr, "FindVisualStudioInstances");
  305. if (SUCCEEDED(hr)) {
  306. count = static_cast<int>(instances.size());
  307. }
  308. // Force release all COM pointers before CoUninitialize:
  309. instances.clear();
  310. CoUninitialize();
  311. }
  312. #else
  313. (void)slnFile;
  314. #endif
  315. return count;
  316. }
  317. //! Get all running objects from the Windows running object table.
  318. //! Save them in a map by their display names.
  319. int cmCallVisualStudioMacro::CallMacro(const std::string& slnFile,
  320. const std::string& macro,
  321. const std::string& args,
  322. const bool logErrorsAsMessages)
  323. {
  324. int err = 1; // no comdef.h
  325. LogErrorsAsMessages = logErrorsAsMessages;
  326. #if defined(HAVE_COMDEF_H)
  327. err = 2; // error initializing
  328. HRESULT hr = CoInitialize(0);
  329. ReportHRESULT(hr, "CoInitialize");
  330. if (SUCCEEDED(hr)) {
  331. std::vector<IDispatchPtr> instances;
  332. hr = FindVisualStudioInstances(slnFile, instances);
  333. ReportHRESULT(hr, "FindVisualStudioInstances");
  334. if (SUCCEEDED(hr)) {
  335. err = 0; // no error
  336. std::vector<IDispatchPtr>::iterator it;
  337. for (it = instances.begin(); it != instances.end(); ++it) {
  338. hr = InstanceCallMacro(*it, macro, args);
  339. ReportHRESULT(hr, "InstanceCallMacro");
  340. if (FAILED(hr)) {
  341. err = 3; // error attempting to call the macro
  342. }
  343. }
  344. if (instances.empty()) {
  345. // no instances to call
  346. // cmSystemTools::Message(
  347. // "cmCallVisualStudioMacro::CallMacro no instances found to call",
  348. // "Warning");
  349. }
  350. }
  351. // Force release all COM pointers before CoUninitialize:
  352. instances.clear();
  353. CoUninitialize();
  354. }
  355. #else
  356. (void)slnFile;
  357. (void)macro;
  358. (void)args;
  359. if (LogErrorsAsMessages) {
  360. cmSystemTools::Message("cmCallVisualStudioMacro::CallMacro is not "
  361. "supported on this platform");
  362. }
  363. #endif
  364. if (err && LogErrorsAsMessages) {
  365. std::ostringstream oss;
  366. oss << "cmCallVisualStudioMacro::CallMacro failed, err = " << err;
  367. cmSystemTools::Message(oss.str());
  368. }
  369. return 0;
  370. }