cmCallVisualStudioMacro.cxx 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457
  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 \
  39. << std::endl; \
  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. oss << std::endl;
  83. oss << "Invoke(ExecuteCommand)" << std::endl;
  84. oss << " Macro: " << macro << std::endl;
  85. oss << " Args: " << args << std::endl;
  86. if (DISP_E_EXCEPTION == hr) {
  87. oss << "DISP_E_EXCEPTION EXCEPINFO:" << excep.wCode << std::endl;
  88. oss << " wCode: " << excep.wCode << std::endl;
  89. oss << " wReserved: " << excep.wReserved << std::endl;
  90. if (excep.bstrSource) {
  91. oss << " bstrSource: " << (const char*)(_bstr_t)excep.bstrSource
  92. << std::endl;
  93. }
  94. if (excep.bstrDescription) {
  95. oss << " bstrDescription: "
  96. << (const char*)(_bstr_t)excep.bstrDescription << std::endl;
  97. }
  98. if (excep.bstrHelpFile) {
  99. oss << " bstrHelpFile: " << (const char*)(_bstr_t)excep.bstrHelpFile
  100. << std::endl;
  101. }
  102. oss << " dwHelpContext: " << excep.dwHelpContext << std::endl;
  103. oss << " pvReserved: " << excep.pvReserved << std::endl;
  104. oss << " pfnDeferredFillIn: "
  105. << reinterpret_cast<void*>(excep.pfnDeferredFillIn) << std::endl;
  106. oss << " scode: " << excep.scode << std::endl;
  107. }
  108. std::string exstr(oss.str());
  109. ReportHRESULT(hr, exstr.c_str());
  110. VariantClear(&result);
  111. }
  112. }
  113. return hr;
  114. }
  115. //! Get the Solution object from the IDE object
  116. HRESULT GetSolutionObject(IDispatch* vsIDE, IDispatchPtr& vsSolution)
  117. {
  118. HRESULT hr = E_POINTER;
  119. if (0 != vsIDE) {
  120. DISPID dispid = (DISPID)-1;
  121. wchar_t solution[] = L"Solution";
  122. OLECHAR* name = solution;
  123. hr =
  124. vsIDE->GetIDsOfNames(IID_NULL, &name, 1, LOCALE_USER_DEFAULT, &dispid);
  125. ReportHRESULT(hr, "GetIDsOfNames(Solution)");
  126. if (SUCCEEDED(hr)) {
  127. DISPPARAMS params;
  128. VARIANT result;
  129. EXCEPINFO excep;
  130. UINT arg = (UINT)-1;
  131. params.rgvarg = 0;
  132. params.rgdispidNamedArgs = 0;
  133. params.cArgs = 0;
  134. params.cNamedArgs = 0;
  135. VariantInit(&result);
  136. memset(&excep, 0, sizeof(excep));
  137. hr = vsIDE->Invoke(dispid, IID_NULL, LOCALE_USER_DEFAULT,
  138. DISPATCH_PROPERTYGET, &params, &result, &excep, &arg);
  139. ReportHRESULT(hr, "Invoke(Solution)");
  140. if (SUCCEEDED(hr)) {
  141. vsSolution = V_DISPATCH(&result);
  142. }
  143. VariantClear(&result);
  144. }
  145. }
  146. return hr;
  147. }
  148. //! Get the FullName property from the Solution object
  149. HRESULT GetSolutionFullName(IDispatch* vsSolution, std::string& fullName)
  150. {
  151. HRESULT hr = E_POINTER;
  152. if (0 != vsSolution) {
  153. DISPID dispid = (DISPID)-1;
  154. wchar_t full_name[] = L"FullName";
  155. OLECHAR* name = full_name;
  156. hr = vsSolution->GetIDsOfNames(IID_NULL, &name, 1, LOCALE_USER_DEFAULT,
  157. &dispid);
  158. ReportHRESULT(hr, "GetIDsOfNames(FullName)");
  159. if (SUCCEEDED(hr)) {
  160. DISPPARAMS params;
  161. VARIANT result;
  162. EXCEPINFO excep;
  163. UINT arg = (UINT)-1;
  164. params.rgvarg = 0;
  165. params.rgdispidNamedArgs = 0;
  166. params.cArgs = 0;
  167. params.cNamedArgs = 0;
  168. VariantInit(&result);
  169. memset(&excep, 0, sizeof(excep));
  170. hr = vsSolution->Invoke(dispid, IID_NULL, LOCALE_USER_DEFAULT,
  171. DISPATCH_PROPERTYGET, &params, &result, &excep,
  172. &arg);
  173. ReportHRESULT(hr, "Invoke(FullName)");
  174. if (SUCCEEDED(hr)) {
  175. fullName = (std::string)(_bstr_t)V_BSTR(&result);
  176. }
  177. VariantClear(&result);
  178. }
  179. }
  180. return hr;
  181. }
  182. //! Get the FullName property from the Solution object, given the IDE object
  183. HRESULT GetIDESolutionFullName(IDispatch* vsIDE, std::string& fullName)
  184. {
  185. IDispatchPtr vsSolution;
  186. HRESULT hr = GetSolutionObject(vsIDE, vsSolution);
  187. ReportHRESULT(hr, "GetSolutionObject");
  188. if (SUCCEEDED(hr)) {
  189. GetSolutionFullName(vsSolution, fullName);
  190. ReportHRESULT(hr, "GetSolutionFullName");
  191. }
  192. return hr;
  193. }
  194. //! Get all running objects from the Windows running object table.
  195. //! Save them in a map by their display names.
  196. HRESULT GetRunningInstances(std::map<std::string, IUnknownPtr>& mrot)
  197. {
  198. // mrot == Map of the Running Object Table
  199. IRunningObjectTablePtr runningObjectTable;
  200. IEnumMonikerPtr monikerEnumerator;
  201. IMonikerPtr moniker;
  202. ULONG numFetched = 0;
  203. HRESULT hr = GetRunningObjectTable(0, &runningObjectTable);
  204. ReportHRESULT(hr, "GetRunningObjectTable");
  205. if (SUCCEEDED(hr)) {
  206. hr = runningObjectTable->EnumRunning(&monikerEnumerator);
  207. ReportHRESULT(hr, "EnumRunning");
  208. }
  209. if (SUCCEEDED(hr)) {
  210. hr = monikerEnumerator->Reset();
  211. ReportHRESULT(hr, "Reset");
  212. }
  213. if (SUCCEEDED(hr)) {
  214. while (S_OK == monikerEnumerator->Next(1, &moniker, &numFetched)) {
  215. std::string runningObjectName;
  216. IUnknownPtr runningObjectVal;
  217. IBindCtxPtr ctx;
  218. hr = CreateBindCtx(0, &ctx);
  219. ReportHRESULT(hr, "CreateBindCtx");
  220. if (SUCCEEDED(hr)) {
  221. LPOLESTR displayName = 0;
  222. hr = moniker->GetDisplayName(ctx, 0, &displayName);
  223. ReportHRESULT(hr, "GetDisplayName");
  224. if (displayName) {
  225. runningObjectName = (std::string)(_bstr_t)displayName;
  226. CoTaskMemFree(displayName);
  227. }
  228. hr = runningObjectTable->GetObject(moniker, &runningObjectVal);
  229. ReportHRESULT(hr, "GetObject");
  230. if (SUCCEEDED(hr)) {
  231. mrot.insert(std::make_pair(runningObjectName, runningObjectVal));
  232. }
  233. }
  234. numFetched = 0;
  235. moniker = 0;
  236. }
  237. }
  238. return hr;
  239. }
  240. //! Do the two file names refer to the same Visual Studio solution? Or are
  241. //! we perhaps looking for any and all solutions?
  242. bool FilesSameSolution(const std::string& slnFile, const std::string& slnName)
  243. {
  244. if (slnFile == "ALL" || slnName == "ALL") {
  245. return true;
  246. }
  247. // Otherwise, make lowercase local copies, convert to Unix slashes, and
  248. // see if the resulting strings are the same:
  249. std::string s1 = cmSystemTools::LowerCase(slnFile);
  250. std::string s2 = cmSystemTools::LowerCase(slnName);
  251. cmSystemTools::ConvertToUnixSlashes(s1);
  252. cmSystemTools::ConvertToUnixSlashes(s2);
  253. return s1 == s2;
  254. }
  255. //! Find instances of Visual Studio with the given solution file
  256. //! open. Pass "ALL" for slnFile to gather all running instances
  257. //! of Visual Studio.
  258. HRESULT FindVisualStudioInstances(const std::string& slnFile,
  259. std::vector<IDispatchPtr>& instances)
  260. {
  261. std::map<std::string, IUnknownPtr> mrot;
  262. HRESULT hr = GetRunningInstances(mrot);
  263. ReportHRESULT(hr, "GetRunningInstances");
  264. if (SUCCEEDED(hr)) {
  265. std::map<std::string, IUnknownPtr>::iterator it;
  266. for (it = mrot.begin(); it != mrot.end(); ++it) {
  267. if (cmHasLiteralPrefix(it->first, "!VisualStudio.DTE.")) {
  268. IDispatchPtr disp(it->second);
  269. if (disp != (IDispatch*)0) {
  270. std::string slnName;
  271. hr = GetIDESolutionFullName(disp, slnName);
  272. ReportHRESULT(hr, "GetIDESolutionFullName");
  273. if (FilesSameSolution(slnFile, slnName)) {
  274. instances.push_back(disp);
  275. // std::cout << "Found Visual Studio instance." << std::endl;
  276. // std::cout << " ROT entry name: " << it->first << std::endl;
  277. // std::cout << " ROT entry object: "
  278. // << (IUnknown*) it->second << std::endl;
  279. // std::cout << " slnFile: " << slnFile << std::endl;
  280. // std::cout << " slnName: " << slnName << std::endl;
  281. }
  282. }
  283. }
  284. }
  285. }
  286. return hr;
  287. }
  288. #endif // defined(HAVE_COMDEF_H)
  289. int cmCallVisualStudioMacro::GetNumberOfRunningVisualStudioInstances(
  290. const std::string& slnFile)
  291. {
  292. int count = 0;
  293. LogErrorsAsMessages = false;
  294. #if defined(HAVE_COMDEF_H)
  295. HRESULT hr = CoInitialize(0);
  296. ReportHRESULT(hr, "CoInitialize");
  297. if (SUCCEEDED(hr)) {
  298. std::vector<IDispatchPtr> instances;
  299. hr = FindVisualStudioInstances(slnFile, instances);
  300. ReportHRESULT(hr, "FindVisualStudioInstances");
  301. if (SUCCEEDED(hr)) {
  302. count = static_cast<int>(instances.size());
  303. }
  304. // Force release all COM pointers before CoUninitialize:
  305. instances.clear();
  306. CoUninitialize();
  307. }
  308. #else
  309. (void)slnFile;
  310. #endif
  311. return count;
  312. }
  313. //! Get all running objects from the Windows running object table.
  314. //! Save them in a map by their display names.
  315. int cmCallVisualStudioMacro::CallMacro(const std::string& slnFile,
  316. const std::string& macro,
  317. const std::string& args,
  318. const bool logErrorsAsMessages)
  319. {
  320. int err = 1; // no comdef.h
  321. LogErrorsAsMessages = logErrorsAsMessages;
  322. #if defined(HAVE_COMDEF_H)
  323. err = 2; // error initializing
  324. HRESULT hr = CoInitialize(0);
  325. ReportHRESULT(hr, "CoInitialize");
  326. if (SUCCEEDED(hr)) {
  327. std::vector<IDispatchPtr> instances;
  328. hr = FindVisualStudioInstances(slnFile, instances);
  329. ReportHRESULT(hr, "FindVisualStudioInstances");
  330. if (SUCCEEDED(hr)) {
  331. err = 0; // no error
  332. std::vector<IDispatchPtr>::iterator it;
  333. for (it = instances.begin(); it != instances.end(); ++it) {
  334. hr = InstanceCallMacro(*it, macro, args);
  335. ReportHRESULT(hr, "InstanceCallMacro");
  336. if (FAILED(hr)) {
  337. err = 3; // error attempting to call the macro
  338. }
  339. }
  340. if (instances.empty()) {
  341. // no instances to call
  342. // cmSystemTools::Message(
  343. // "cmCallVisualStudioMacro::CallMacro no instances found to call",
  344. // "Warning");
  345. }
  346. }
  347. // Force release all COM pointers before CoUninitialize:
  348. instances.clear();
  349. CoUninitialize();
  350. }
  351. #else
  352. (void)slnFile;
  353. (void)macro;
  354. (void)args;
  355. if (LogErrorsAsMessages) {
  356. cmSystemTools::Message("cmCallVisualStudioMacro::CallMacro is not "
  357. "supported on this platform");
  358. }
  359. #endif
  360. if (err && LogErrorsAsMessages) {
  361. std::ostringstream oss;
  362. oss << "cmCallVisualStudioMacro::CallMacro failed, err = " << err;
  363. cmSystemTools::Message(oss.str());
  364. }
  365. return 0;
  366. }