cmCallVisualStudioMacro.cxx 13 KB

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