暂无描述
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

COMIntegration.cpp 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483
  1. /*---------------------------------------------------------------------------------------------
  2. * Copyright (c) Unity Technologies.
  3. * Copyright (c) Microsoft Corporation. All rights reserved.
  4. * Licensed under the MIT License. See License.txt in the project root for license information.
  5. *--------------------------------------------------------------------------------------------*/
  6. #include <iostream>
  7. #include <sstream>
  8. #include <string>
  9. #include <filesystem>
  10. #include <windows.h>
  11. #include <shlwapi.h>
  12. #include <fcntl.h>
  13. #include <io.h>
  14. #include "BStrHolder.h"
  15. #include "ComPtr.h"
  16. #include "dte80a.tlh"
  17. constexpr int RETRY_INTERVAL_MS = 150;
  18. constexpr int TIMEOUT_MS = 10000;
  19. // Often a DTE call made to Visual Studio can fail after Visual Studio has just started. Usually the
  20. // return value will be RPC_E_CALL_REJECTED, meaning that Visual Studio is probably busy on another
  21. // thread. This types filter the RPC messages and retries to send the message until VS accepts it.
  22. class CRetryMessageFilter : public IMessageFilter
  23. {
  24. private:
  25. static bool ShouldRetryCall(DWORD dwTickCount, DWORD dwRejectType)
  26. {
  27. if (dwRejectType == SERVERCALL_RETRYLATER || dwRejectType == SERVERCALL_REJECTED) {
  28. return dwTickCount < TIMEOUT_MS;
  29. }
  30. return false;
  31. }
  32. win::ComPtr<IMessageFilter> currentFilter;
  33. public:
  34. CRetryMessageFilter()
  35. {
  36. HRESULT hr = CoRegisterMessageFilter(this, &currentFilter);
  37. _ASSERT(SUCCEEDED(hr));
  38. }
  39. ~CRetryMessageFilter()
  40. {
  41. win::ComPtr<IMessageFilter> messageFilter;
  42. HRESULT hr = CoRegisterMessageFilter(currentFilter, &messageFilter);
  43. _ASSERT(SUCCEEDED(hr));
  44. }
  45. // IUnknown methods
  46. IFACEMETHODIMP QueryInterface(REFIID riid, void** ppv)
  47. {
  48. static const QITAB qit[] =
  49. {
  50. QITABENT(CRetryMessageFilter, IMessageFilter),
  51. { 0 },
  52. };
  53. return QISearch(this, qit, riid, ppv);
  54. }
  55. IFACEMETHODIMP_(ULONG) AddRef()
  56. {
  57. return 0;
  58. }
  59. IFACEMETHODIMP_(ULONG) Release()
  60. {
  61. return 0;
  62. }
  63. DWORD STDMETHODCALLTYPE HandleInComingCall(DWORD dwCallType, HTASK htaskCaller, DWORD dwTickCount, LPINTERFACEINFO lpInterfaceInfo)
  64. {
  65. if (currentFilter)
  66. return currentFilter->HandleInComingCall(dwCallType, htaskCaller, dwTickCount, lpInterfaceInfo);
  67. return SERVERCALL_ISHANDLED;
  68. }
  69. DWORD STDMETHODCALLTYPE RetryRejectedCall(HTASK htaskCallee, DWORD dwTickCount, DWORD dwRejectType)
  70. {
  71. if (ShouldRetryCall(dwTickCount, dwRejectType))
  72. return RETRY_INTERVAL_MS;
  73. if (currentFilter)
  74. return currentFilter->RetryRejectedCall(htaskCallee, dwTickCount, dwRejectType);
  75. return (DWORD)-1;
  76. }
  77. DWORD STDMETHODCALLTYPE MessagePending(HTASK htaskCallee, DWORD dwTickCount, DWORD dwPendingType)
  78. {
  79. if (currentFilter)
  80. return currentFilter->MessagePending(htaskCallee, dwTickCount, dwPendingType);
  81. return PENDINGMSG_WAITDEFPROCESS;
  82. }
  83. };
  84. static void DisplayProgressbar() {
  85. std::wcout << "displayProgressBar" << std::endl;
  86. }
  87. static void ClearProgressbar() {
  88. std::wcout << "clearprogressbar" << std::endl;
  89. }
  90. inline const std::wstring QuoteString(const std::wstring& str)
  91. {
  92. return L"\"" + str + L"\"";
  93. }
  94. static std::wstring ErrorCodeToMsg(DWORD code)
  95. {
  96. LPWSTR msgBuf = nullptr;
  97. if (!FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
  98. nullptr, code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPWSTR)&msgBuf, 0, nullptr))
  99. {
  100. return L"Unknown error";
  101. }
  102. else
  103. {
  104. return msgBuf;
  105. }
  106. }
  107. // Get an environment variable
  108. static std::wstring GetEnvironmentVariableValue(const std::wstring& variableName) {
  109. DWORD currentBufferSize = MAX_PATH;
  110. std::wstring variableValue;
  111. variableValue.resize(currentBufferSize);
  112. DWORD requiredBufferSize = GetEnvironmentVariableW(variableName.c_str(), variableValue.data(), currentBufferSize);
  113. if (requiredBufferSize == 0) {
  114. // Environment variable probably does not exist.
  115. return std::wstring();
  116. }
  117. if (currentBufferSize < requiredBufferSize) {
  118. variableValue.resize(requiredBufferSize);
  119. if (GetEnvironmentVariableW(variableName.c_str(), variableValue.data(), currentBufferSize) == 0)
  120. return std::wstring();
  121. }
  122. variableValue.resize(requiredBufferSize);
  123. return variableValue;
  124. }
  125. static bool StartVisualStudioProcess(
  126. const std::filesystem::path &visualStudioExecutablePath,
  127. const std::filesystem::path &solutionPath,
  128. DWORD *dwProcessId) {
  129. STARTUPINFOW si;
  130. PROCESS_INFORMATION pi;
  131. BOOL result;
  132. ZeroMemory(&si, sizeof(si));
  133. si.cb = sizeof(si);
  134. ZeroMemory(&pi, sizeof(pi));
  135. std::wstring startingDirectory = visualStudioExecutablePath.parent_path();
  136. // Build the command line that is passed as the argv of the VS process
  137. // argv[0] must be the quoted full path to the VS exe
  138. std::wstringstream commandLineStream;
  139. commandLineStream << QuoteString(visualStudioExecutablePath) << L" ";
  140. std::wstring vsArgsWide = GetEnvironmentVariableValue(L"UNITY_VS_ARGS");
  141. if (!vsArgsWide.empty())
  142. commandLineStream << vsArgsWide << L" ";
  143. commandLineStream << QuoteString(solutionPath);
  144. std::wstring commandLine = commandLineStream.str();
  145. std::wcout << "Starting Visual Studio process with: " << commandLine << std::endl;
  146. result = CreateProcessW(
  147. visualStudioExecutablePath.c_str(), // Full path to VS, must not be quoted
  148. commandLine.data(), // Command line, as passed as argv, separate arguments must be quoted if they contain spaces
  149. nullptr, // Process handle not inheritable
  150. nullptr, // Thread handle not inheritable
  151. false, // Set handle inheritance to FALSE
  152. 0, // No creation flags
  153. nullptr, // Use parent's environment block
  154. startingDirectory.c_str(), // starting directory set to the VS directory
  155. &si,
  156. &pi);
  157. if (!result) {
  158. DWORD error = GetLastError();
  159. std::wcout << "Starting Visual Studio process failed: " << ErrorCodeToMsg(error) << std::endl;
  160. return false;
  161. }
  162. *dwProcessId = pi.dwProcessId;
  163. CloseHandle(pi.hProcess);
  164. CloseHandle(pi.hThread);
  165. return true;
  166. }
  167. static bool
  168. MonikerIsVisualStudioProcess(const win::ComPtr<IMoniker> &moniker, const win::ComPtr<IBindCtx> &bindCtx, const DWORD dwProcessId = 0) {
  169. LPOLESTR oleMonikerName;
  170. if (FAILED(moniker->GetDisplayName(bindCtx, nullptr, &oleMonikerName)))
  171. return false;
  172. std::wstring monikerName(oleMonikerName);
  173. // VisualStudio Moniker is "!VisualStudio.DTE.$Version:$PID"
  174. // Example "!VisualStudio.DTE.14.0:1234"
  175. if (monikerName.find(L"!VisualStudio.DTE") != 0)
  176. return false;
  177. if (dwProcessId == 0)
  178. return true;
  179. std::wstringstream suffixStream;
  180. suffixStream << ":";
  181. suffixStream << dwProcessId;
  182. std::wstring suffix(suffixStream.str());
  183. return monikerName.length() - suffix.length() == monikerName.find(suffix);
  184. }
  185. static win::ComPtr<EnvDTE::_DTE> FindRunningVisualStudioWithSolution(
  186. const std::filesystem::path &visualStudioExecutablePath,
  187. const std::filesystem::path &solutionPath)
  188. {
  189. win::ComPtr<IUnknown> punk = nullptr;
  190. win::ComPtr<EnvDTE::_DTE> dte = nullptr;
  191. CRetryMessageFilter retryMessageFilter;
  192. // Search through the Running Object Table for an instance of Visual Studio
  193. // to use that either has the correct solution already open or does not have
  194. // any solution open.
  195. win::ComPtr<IRunningObjectTable> ROT;
  196. if (FAILED(GetRunningObjectTable(0, &ROT)))
  197. return nullptr;
  198. win::ComPtr<IBindCtx> bindCtx;
  199. if (FAILED(CreateBindCtx(0, &bindCtx)))
  200. return nullptr;
  201. win::ComPtr<IEnumMoniker> enumMoniker;
  202. if (FAILED(ROT->EnumRunning(&enumMoniker)))
  203. return nullptr;
  204. win::ComPtr<IMoniker> moniker;
  205. ULONG monikersFetched = 0;
  206. while (SUCCEEDED(enumMoniker->Next(1, &moniker, &monikersFetched)) && monikersFetched) {
  207. if (!MonikerIsVisualStudioProcess(moniker, bindCtx))
  208. continue;
  209. if (FAILED(ROT->GetObject(moniker, &punk)))
  210. continue;
  211. punk.As(&dte);
  212. if (!dte)
  213. continue;
  214. // Okay, so we found an actual running instance of Visual Studio.
  215. // Get the executable path of this running instance.
  216. BStrHolder visualStudioFullName;
  217. if (FAILED(dte->get_FullName(&visualStudioFullName)))
  218. continue;
  219. std::filesystem::path currentVisualStudioExecutablePath = std::wstring(visualStudioFullName);
  220. // Ask for its current solution.
  221. win::ComPtr<EnvDTE::_Solution> solution;
  222. if (FAILED(dte->get_Solution(&solution)))
  223. continue;
  224. // Get the name of that solution.
  225. BStrHolder solutionFullName;
  226. if (FAILED(solution->get_FullName(&solutionFullName)))
  227. continue;
  228. std::filesystem::path currentSolutionPath = std::wstring(solutionFullName);
  229. if (currentSolutionPath.empty())
  230. continue;
  231. std::wcout << "Visual Studio opened on " << currentSolutionPath.wstring() << std::endl;
  232. // If the name matches the solution we want to open and we have a Visual Studio installation path to use and this one matches that path, then use it.
  233. // If we don't have a Visual Studio installation path to use, just use this solution.
  234. if (std::filesystem::equivalent(currentSolutionPath, solutionPath)) {
  235. std::wcout << "We found a running Visual Studio session with the solution open." << std::endl;
  236. if (!visualStudioExecutablePath.empty()) {
  237. if (std::filesystem::equivalent(currentVisualStudioExecutablePath, visualStudioExecutablePath)) {
  238. return dte;
  239. }
  240. else {
  241. std::wcout << "This running Visual Studio session does not seem to be the version requested in the user preferences. We will keep looking." << std::endl;
  242. }
  243. }
  244. else {
  245. std::wcout << "We're not sure which version of Visual Studio was requested in the user preferences. We will use this running session." << std::endl;
  246. return dte;
  247. }
  248. }
  249. }
  250. return nullptr;
  251. }
  252. static win::ComPtr<EnvDTE::_DTE> FindRunningVisualStudioWithPID(const DWORD dwProcessId) {
  253. win::ComPtr<IUnknown> punk = nullptr;
  254. win::ComPtr<EnvDTE::_DTE> dte = nullptr;
  255. // Search through the Running Object Table for a Visual Studio
  256. // process with the process ID specified
  257. win::ComPtr<IRunningObjectTable> ROT;
  258. if (FAILED(GetRunningObjectTable(0, &ROT)))
  259. return nullptr;
  260. win::ComPtr<IBindCtx> bindCtx;
  261. if (FAILED(CreateBindCtx(0, &bindCtx)))
  262. return nullptr;
  263. win::ComPtr<IEnumMoniker> enumMoniker;
  264. if (FAILED(ROT->EnumRunning(&enumMoniker)))
  265. return nullptr;
  266. win::ComPtr<IMoniker> moniker;
  267. ULONG monikersFetched = 0;
  268. while (SUCCEEDED(enumMoniker->Next(1, &moniker, &monikersFetched)) && monikersFetched) {
  269. if (!MonikerIsVisualStudioProcess(moniker, bindCtx, dwProcessId))
  270. continue;
  271. if (FAILED(ROT->GetObject(moniker, &punk)))
  272. continue;
  273. punk.As(&dte);
  274. if (dte)
  275. return dte;
  276. }
  277. return nullptr;
  278. }
  279. static bool HaveRunningVisualStudioOpenFile(const win::ComPtr<EnvDTE::_DTE> &dte, const std::filesystem::path &filename, int line) {
  280. BStrHolder bstrFileName(filename.c_str());
  281. BStrHolder bstrKind(L"{00000000-0000-0000-0000-000000000000}"); // EnvDTE::vsViewKindPrimary
  282. win::ComPtr<EnvDTE::Window> window = nullptr;
  283. CRetryMessageFilter retryMessageFilter;
  284. if (!filename.empty()) {
  285. std::wcout << "Getting operations API from the Visual Studio session." << std::endl;
  286. win::ComPtr<EnvDTE::ItemOperations> item_ops;
  287. if (FAILED(dte->get_ItemOperations(&item_ops)))
  288. return false;
  289. std::wcout << "Waiting for the Visual Studio session to open the file: " << filename.wstring() << "." << std::endl;
  290. if (FAILED(item_ops->OpenFile(bstrFileName, bstrKind, &window)))
  291. return false;
  292. if (line > 0) {
  293. win::ComPtr<IDispatch> selection_dispatch;
  294. if (window && SUCCEEDED(window->get_Selection(&selection_dispatch))) {
  295. win::ComPtr<EnvDTE::TextSelection> selection;
  296. if (selection_dispatch &&
  297. SUCCEEDED(selection_dispatch->QueryInterface(__uuidof(EnvDTE::TextSelection), &selection)) &&
  298. selection) {
  299. selection->GotoLine(line, false);
  300. selection->EndOfLine(false);
  301. }
  302. }
  303. }
  304. }
  305. window = nullptr;
  306. if (SUCCEEDED(dte->get_MainWindow(&window))) {
  307. // Allow the DTE to make its main window the foreground
  308. HWND hWnd;
  309. window->get_HWnd((LONG *)&hWnd);
  310. DWORD processID;
  311. if (SUCCEEDED(GetWindowThreadProcessId(hWnd, &processID)))
  312. AllowSetForegroundWindow(processID);
  313. // Activate() set the window to visible and active (blinks in taskbar)
  314. window->Activate();
  315. }
  316. return true;
  317. }
  318. static bool VisualStudioOpenFile(
  319. const std::filesystem::path &visualStudioExecutablePath,
  320. const std::filesystem::path &solutionPath,
  321. const std::filesystem::path &filename,
  322. int line)
  323. {
  324. win::ComPtr<EnvDTE::_DTE> dte = nullptr;
  325. std::wcout << "Looking for a running Visual Studio session." << std::endl;
  326. // TODO: If path does not exist pass empty, which will just try to match all windows with solution
  327. dte = FindRunningVisualStudioWithSolution(visualStudioExecutablePath, solutionPath);
  328. if (!dte) {
  329. std::wcout << "No appropriate running Visual Studio session not found, creating a new one." << std::endl;
  330. DisplayProgressbar();
  331. DWORD dwProcessId;
  332. if (!StartVisualStudioProcess(visualStudioExecutablePath, solutionPath, &dwProcessId)) {
  333. ClearProgressbar();
  334. return false;
  335. }
  336. int timeWaited = 0;
  337. while (timeWaited < TIMEOUT_MS) {
  338. dte = FindRunningVisualStudioWithPID(dwProcessId);
  339. if (dte)
  340. break;
  341. std::wcout << "Retrying to acquire DTE" << std::endl;
  342. Sleep(RETRY_INTERVAL_MS);
  343. timeWaited += RETRY_INTERVAL_MS;
  344. }
  345. ClearProgressbar();
  346. if (!dte)
  347. return false;
  348. }
  349. else {
  350. std::wcout << "Using the existing Visual Studio session." << std::endl;
  351. }
  352. return HaveRunningVisualStudioOpenFile(dte, filename, line);
  353. }
  354. int wmain(int argc, wchar_t* argv[]) {
  355. // We need this to properly display UTF16 text on the console
  356. _setmode(_fileno(stdout), _O_U16TEXT);
  357. if (argc != 3 && argc != 5) {
  358. std::wcerr << argc << ": wrong number of arguments\n" << "Usage: com.exe installationPath solutionPath [fileName lineNumber]" << std::endl;
  359. for (int i = 0; i < argc; i++) {
  360. std::wcerr << argv[i] << std::endl;
  361. }
  362. return EXIT_FAILURE;
  363. }
  364. if (FAILED(CoInitialize(nullptr))) {
  365. std::wcerr << "CoInitialize failed." << std::endl;
  366. return EXIT_FAILURE;
  367. }
  368. std::filesystem::path visualStudioExecutablePath = std::filesystem::absolute(argv[1]);
  369. std::filesystem::path solutionPath = std::filesystem::absolute(argv[2]);
  370. if (argc == 3) {
  371. VisualStudioOpenFile(visualStudioExecutablePath, solutionPath, L"", -1);
  372. return EXIT_SUCCESS;
  373. }
  374. std::filesystem::path fileName = std::filesystem::absolute(argv[3]);
  375. int lineNumber = std::stoi(argv[4]);
  376. VisualStudioOpenFile(visualStudioExecutablePath, solutionPath, fileName, lineNumber);
  377. return EXIT_SUCCESS;
  378. }