cmCallVisualStudioMacro.cxx 13 KB

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