This commit is contained in:
2025-01-17 13:10:42 +01:00
commit 4536213c91
15115 changed files with 1442174 additions and 0 deletions

View File

@@ -0,0 +1,40 @@
#pragma once
#include <OleAuto.h>
struct BStrHolder
{
BStrHolder() :
m_Str(NULL)
{
}
BStrHolder(const wchar_t* str) :
m_Str(SysAllocString(str))
{
}
~BStrHolder()
{
if (m_Str != NULL)
SysFreeString(m_Str);
}
operator BSTR() const
{
return m_Str;
}
BSTR* operator&()
{
if (m_Str != NULL)
{
SysFreeString(m_Str);
m_Str = NULL;
}
return &m_Str;
}
private:
BSTR m_Str;
};

View File

@@ -0,0 +1,14 @@
cmake_minimum_required(VERSION 3.15)
project(com)
set(SOURCES
COMIntegration.cpp
BStrHolder.h
ComPtr.h
dte80a.tlh
)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3 -Wall")
add_executable(COMIntegration ${SOURCES})
set_property(TARGET COMIntegration PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded")
target_link_libraries(COMIntegration Shlwapi.lib)

View File

@@ -0,0 +1,483 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Unity Technologies.
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
#include <iostream>
#include <sstream>
#include <string>
#include <filesystem>
#include <windows.h>
#include <shlwapi.h>
#include <fcntl.h>
#include <io.h>
#include "BStrHolder.h"
#include "ComPtr.h"
#include "dte80a.tlh"
constexpr int RETRY_INTERVAL_MS = 150;
constexpr int TIMEOUT_MS = 10000;
// Often a DTE call made to Visual Studio can fail after Visual Studio has just started. Usually the
// return value will be RPC_E_CALL_REJECTED, meaning that Visual Studio is probably busy on another
// thread. This types filter the RPC messages and retries to send the message until VS accepts it.
class CRetryMessageFilter : public IMessageFilter
{
private:
static bool ShouldRetryCall(DWORD dwTickCount, DWORD dwRejectType)
{
if (dwRejectType == SERVERCALL_RETRYLATER || dwRejectType == SERVERCALL_REJECTED) {
return dwTickCount < TIMEOUT_MS;
}
return false;
}
win::ComPtr<IMessageFilter> currentFilter;
public:
CRetryMessageFilter()
{
HRESULT hr = CoRegisterMessageFilter(this, &currentFilter);
_ASSERT(SUCCEEDED(hr));
}
~CRetryMessageFilter()
{
win::ComPtr<IMessageFilter> messageFilter;
HRESULT hr = CoRegisterMessageFilter(currentFilter, &messageFilter);
_ASSERT(SUCCEEDED(hr));
}
// IUnknown methods
IFACEMETHODIMP QueryInterface(REFIID riid, void** ppv)
{
static const QITAB qit[] =
{
QITABENT(CRetryMessageFilter, IMessageFilter),
{ 0 },
};
return QISearch(this, qit, riid, ppv);
}
IFACEMETHODIMP_(ULONG) AddRef()
{
return 0;
}
IFACEMETHODIMP_(ULONG) Release()
{
return 0;
}
DWORD STDMETHODCALLTYPE HandleInComingCall(DWORD dwCallType, HTASK htaskCaller, DWORD dwTickCount, LPINTERFACEINFO lpInterfaceInfo)
{
if (currentFilter)
return currentFilter->HandleInComingCall(dwCallType, htaskCaller, dwTickCount, lpInterfaceInfo);
return SERVERCALL_ISHANDLED;
}
DWORD STDMETHODCALLTYPE RetryRejectedCall(HTASK htaskCallee, DWORD dwTickCount, DWORD dwRejectType)
{
if (ShouldRetryCall(dwTickCount, dwRejectType))
return RETRY_INTERVAL_MS;
if (currentFilter)
return currentFilter->RetryRejectedCall(htaskCallee, dwTickCount, dwRejectType);
return (DWORD)-1;
}
DWORD STDMETHODCALLTYPE MessagePending(HTASK htaskCallee, DWORD dwTickCount, DWORD dwPendingType)
{
if (currentFilter)
return currentFilter->MessagePending(htaskCallee, dwTickCount, dwPendingType);
return PENDINGMSG_WAITDEFPROCESS;
}
};
static void DisplayProgressbar() {
std::wcout << "displayProgressBar" << std::endl;
}
static void ClearProgressbar() {
std::wcout << "clearprogressbar" << std::endl;
}
inline const std::wstring QuoteString(const std::wstring& str)
{
return L"\"" + str + L"\"";
}
static std::wstring ErrorCodeToMsg(DWORD code)
{
LPWSTR msgBuf = nullptr;
if (!FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
nullptr, code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPWSTR)&msgBuf, 0, nullptr))
{
return L"Unknown error";
}
else
{
return msgBuf;
}
}
// Get an environment variable
static std::wstring GetEnvironmentVariableValue(const std::wstring& variableName) {
DWORD currentBufferSize = MAX_PATH;
std::wstring variableValue;
variableValue.resize(currentBufferSize);
DWORD requiredBufferSize = GetEnvironmentVariableW(variableName.c_str(), variableValue.data(), currentBufferSize);
if (requiredBufferSize == 0) {
// Environment variable probably does not exist.
return std::wstring();
}
if (currentBufferSize < requiredBufferSize) {
variableValue.resize(requiredBufferSize);
if (GetEnvironmentVariableW(variableName.c_str(), variableValue.data(), currentBufferSize) == 0)
return std::wstring();
}
variableValue.resize(requiredBufferSize);
return variableValue;
}
static bool StartVisualStudioProcess(
const std::filesystem::path &visualStudioExecutablePath,
const std::filesystem::path &solutionPath,
DWORD *dwProcessId) {
STARTUPINFOW si;
PROCESS_INFORMATION pi;
BOOL result;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
std::wstring startingDirectory = visualStudioExecutablePath.parent_path();
// Build the command line that is passed as the argv of the VS process
// argv[0] must be the quoted full path to the VS exe
std::wstringstream commandLineStream;
commandLineStream << QuoteString(visualStudioExecutablePath) << L" ";
std::wstring vsArgsWide = GetEnvironmentVariableValue(L"UNITY_VS_ARGS");
if (!vsArgsWide.empty())
commandLineStream << vsArgsWide << L" ";
commandLineStream << QuoteString(solutionPath);
std::wstring commandLine = commandLineStream.str();
std::wcout << "Starting Visual Studio process with: " << commandLine << std::endl;
result = CreateProcessW(
visualStudioExecutablePath.c_str(), // Full path to VS, must not be quoted
commandLine.data(), // Command line, as passed as argv, separate arguments must be quoted if they contain spaces
nullptr, // Process handle not inheritable
nullptr, // Thread handle not inheritable
false, // Set handle inheritance to FALSE
0, // No creation flags
nullptr, // Use parent's environment block
startingDirectory.c_str(), // starting directory set to the VS directory
&si,
&pi);
if (!result) {
DWORD error = GetLastError();
std::wcout << "Starting Visual Studio process failed: " << ErrorCodeToMsg(error) << std::endl;
return false;
}
*dwProcessId = pi.dwProcessId;
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
return true;
}
static bool
MonikerIsVisualStudioProcess(const win::ComPtr<IMoniker> &moniker, const win::ComPtr<IBindCtx> &bindCtx, const DWORD dwProcessId = 0) {
LPOLESTR oleMonikerName;
if (FAILED(moniker->GetDisplayName(bindCtx, nullptr, &oleMonikerName)))
return false;
std::wstring monikerName(oleMonikerName);
// VisualStudio Moniker is "!VisualStudio.DTE.$Version:$PID"
// Example "!VisualStudio.DTE.14.0:1234"
if (monikerName.find(L"!VisualStudio.DTE") != 0)
return false;
if (dwProcessId == 0)
return true;
std::wstringstream suffixStream;
suffixStream << ":";
suffixStream << dwProcessId;
std::wstring suffix(suffixStream.str());
return monikerName.length() - suffix.length() == monikerName.find(suffix);
}
static win::ComPtr<EnvDTE::_DTE> FindRunningVisualStudioWithSolution(
const std::filesystem::path &visualStudioExecutablePath,
const std::filesystem::path &solutionPath)
{
win::ComPtr<IUnknown> punk = nullptr;
win::ComPtr<EnvDTE::_DTE> dte = nullptr;
CRetryMessageFilter retryMessageFilter;
// Search through the Running Object Table for an instance of Visual Studio
// to use that either has the correct solution already open or does not have
// any solution open.
win::ComPtr<IRunningObjectTable> ROT;
if (FAILED(GetRunningObjectTable(0, &ROT)))
return nullptr;
win::ComPtr<IBindCtx> bindCtx;
if (FAILED(CreateBindCtx(0, &bindCtx)))
return nullptr;
win::ComPtr<IEnumMoniker> enumMoniker;
if (FAILED(ROT->EnumRunning(&enumMoniker)))
return nullptr;
win::ComPtr<IMoniker> moniker;
ULONG monikersFetched = 0;
while (SUCCEEDED(enumMoniker->Next(1, &moniker, &monikersFetched)) && monikersFetched) {
if (!MonikerIsVisualStudioProcess(moniker, bindCtx))
continue;
if (FAILED(ROT->GetObject(moniker, &punk)))
continue;
punk.As(&dte);
if (!dte)
continue;
// Okay, so we found an actual running instance of Visual Studio.
// Get the executable path of this running instance.
BStrHolder visualStudioFullName;
if (FAILED(dte->get_FullName(&visualStudioFullName)))
continue;
std::filesystem::path currentVisualStudioExecutablePath = std::wstring(visualStudioFullName);
// Ask for its current solution.
win::ComPtr<EnvDTE::_Solution> solution;
if (FAILED(dte->get_Solution(&solution)))
continue;
// Get the name of that solution.
BStrHolder solutionFullName;
if (FAILED(solution->get_FullName(&solutionFullName)))
continue;
std::filesystem::path currentSolutionPath = std::wstring(solutionFullName);
if (currentSolutionPath.empty())
continue;
std::wcout << "Visual Studio opened on " << currentSolutionPath.wstring() << std::endl;
// 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.
// If we don't have a Visual Studio installation path to use, just use this solution.
if (std::filesystem::equivalent(currentSolutionPath, solutionPath)) {
std::wcout << "We found a running Visual Studio session with the solution open." << std::endl;
if (!visualStudioExecutablePath.empty()) {
if (std::filesystem::equivalent(currentVisualStudioExecutablePath, visualStudioExecutablePath)) {
return dte;
}
else {
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;
}
}
else {
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;
return dte;
}
}
}
return nullptr;
}
static win::ComPtr<EnvDTE::_DTE> FindRunningVisualStudioWithPID(const DWORD dwProcessId) {
win::ComPtr<IUnknown> punk = nullptr;
win::ComPtr<EnvDTE::_DTE> dte = nullptr;
// Search through the Running Object Table for a Visual Studio
// process with the process ID specified
win::ComPtr<IRunningObjectTable> ROT;
if (FAILED(GetRunningObjectTable(0, &ROT)))
return nullptr;
win::ComPtr<IBindCtx> bindCtx;
if (FAILED(CreateBindCtx(0, &bindCtx)))
return nullptr;
win::ComPtr<IEnumMoniker> enumMoniker;
if (FAILED(ROT->EnumRunning(&enumMoniker)))
return nullptr;
win::ComPtr<IMoniker> moniker;
ULONG monikersFetched = 0;
while (SUCCEEDED(enumMoniker->Next(1, &moniker, &monikersFetched)) && monikersFetched) {
if (!MonikerIsVisualStudioProcess(moniker, bindCtx, dwProcessId))
continue;
if (FAILED(ROT->GetObject(moniker, &punk)))
continue;
punk.As(&dte);
if (dte)
return dte;
}
return nullptr;
}
static bool HaveRunningVisualStudioOpenFile(const win::ComPtr<EnvDTE::_DTE> &dte, const std::filesystem::path &filename, int line) {
BStrHolder bstrFileName(filename.c_str());
BStrHolder bstrKind(L"{00000000-0000-0000-0000-000000000000}"); // EnvDTE::vsViewKindPrimary
win::ComPtr<EnvDTE::Window> window = nullptr;
CRetryMessageFilter retryMessageFilter;
if (!filename.empty()) {
std::wcout << "Getting operations API from the Visual Studio session." << std::endl;
win::ComPtr<EnvDTE::ItemOperations> item_ops;
if (FAILED(dte->get_ItemOperations(&item_ops)))
return false;
std::wcout << "Waiting for the Visual Studio session to open the file: " << filename.wstring() << "." << std::endl;
if (FAILED(item_ops->OpenFile(bstrFileName, bstrKind, &window)))
return false;
if (line > 0) {
win::ComPtr<IDispatch> selection_dispatch;
if (window && SUCCEEDED(window->get_Selection(&selection_dispatch))) {
win::ComPtr<EnvDTE::TextSelection> selection;
if (selection_dispatch &&
SUCCEEDED(selection_dispatch->QueryInterface(__uuidof(EnvDTE::TextSelection), &selection)) &&
selection) {
selection->GotoLine(line, false);
selection->EndOfLine(false);
}
}
}
}
window = nullptr;
if (SUCCEEDED(dte->get_MainWindow(&window))) {
// Allow the DTE to make its main window the foreground
HWND hWnd;
window->get_HWnd((LONG *)&hWnd);
DWORD processID;
if (SUCCEEDED(GetWindowThreadProcessId(hWnd, &processID)))
AllowSetForegroundWindow(processID);
// Activate() set the window to visible and active (blinks in taskbar)
window->Activate();
}
return true;
}
static bool VisualStudioOpenFile(
const std::filesystem::path &visualStudioExecutablePath,
const std::filesystem::path &solutionPath,
const std::filesystem::path &filename,
int line)
{
win::ComPtr<EnvDTE::_DTE> dte = nullptr;
std::wcout << "Looking for a running Visual Studio session." << std::endl;
// TODO: If path does not exist pass empty, which will just try to match all windows with solution
dte = FindRunningVisualStudioWithSolution(visualStudioExecutablePath, solutionPath);
if (!dte) {
std::wcout << "No appropriate running Visual Studio session not found, creating a new one." << std::endl;
DisplayProgressbar();
DWORD dwProcessId;
if (!StartVisualStudioProcess(visualStudioExecutablePath, solutionPath, &dwProcessId)) {
ClearProgressbar();
return false;
}
int timeWaited = 0;
while (timeWaited < TIMEOUT_MS) {
dte = FindRunningVisualStudioWithPID(dwProcessId);
if (dte)
break;
std::wcout << "Retrying to acquire DTE" << std::endl;
Sleep(RETRY_INTERVAL_MS);
timeWaited += RETRY_INTERVAL_MS;
}
ClearProgressbar();
if (!dte)
return false;
}
else {
std::wcout << "Using the existing Visual Studio session." << std::endl;
}
return HaveRunningVisualStudioOpenFile(dte, filename, line);
}
int wmain(int argc, wchar_t* argv[]) {
// We need this to properly display UTF16 text on the console
_setmode(_fileno(stdout), _O_U16TEXT);
if (argc != 3 && argc != 5) {
std::wcerr << argc << ": wrong number of arguments\n" << "Usage: com.exe installationPath solutionPath [fileName lineNumber]" << std::endl;
for (int i = 0; i < argc; i++) {
std::wcerr << argv[i] << std::endl;
}
return EXIT_FAILURE;
}
if (FAILED(CoInitialize(nullptr))) {
std::wcerr << "CoInitialize failed." << std::endl;
return EXIT_FAILURE;
}
std::filesystem::path visualStudioExecutablePath = std::filesystem::absolute(argv[1]);
std::filesystem::path solutionPath = std::filesystem::absolute(argv[2]);
if (argc == 3) {
VisualStudioOpenFile(visualStudioExecutablePath, solutionPath, L"", -1);
return EXIT_SUCCESS;
}
std::filesystem::path fileName = std::filesystem::absolute(argv[3]);
int lineNumber = std::stoi(argv[4]);
VisualStudioOpenFile(visualStudioExecutablePath, solutionPath, fileName, lineNumber);
return EXIT_SUCCESS;
}

View File

@@ -0,0 +1,186 @@
#pragma once
namespace win
{
template<typename T>
class ComPtr;
template<typename T>
class ComPtrRef
{
private:
ComPtr<T>& m_ComPtr;
ComPtrRef(ComPtr<T>& comPtr) :
m_ComPtr(comPtr)
{
}
friend class ComPtr<T>;
public:
inline operator T**()
{
return m_ComPtr.ReleaseAndGetAddressOf();
}
inline operator void**()
{
return reinterpret_cast<void**>(m_ComPtr.ReleaseAndGetAddressOf());
}
inline T* operator*() throw ()
{
return m_ComPtr;
}
};
template<typename T>
class ComPtr
{
private:
T *ptr;
public:
inline ComPtr(void) : ptr(NULL) {}
inline ~ComPtr(void) { this->Free(); }
ComPtr(T *ptr)
{
if (NULL != (this->ptr = ptr))
{
this->ptr->AddRef();
}
}
ComPtr(const ComPtr &ptr)
{
if (NULL != (this->ptr = ptr.ptr))
{
this->ptr->AddRef();
}
}
inline bool operator!() const
{
return (NULL == this->ptr);
}
inline operator T*() const { return this->ptr; }
inline T *operator->() const
{
//_assert(NULL != this->ptr);
return this->ptr;
}
inline T &operator*()
{
//_assert(NULL != this->ptr);
return *this->ptr;
}
inline ComPtrRef<T> operator&()
{
return ComPtrRef<T>(*this);
}
const ComPtr &operator=(T *ptr)
{
if (this->ptr != ptr)
{
this->Free();
if (NULL != (this->ptr = ptr))
{
this->ptr->AddRef();
}
}
return *this;
}
const ComPtr &operator=(const ComPtr &ptr)
{
if (this->ptr != ptr.ptr)
{
this->Free();
if (NULL != (this->ptr = ptr.ptr))
{
this->ptr->AddRef();
}
}
return *this;
}
void Free(void)
{
if (NULL != this->ptr)
{
this->ptr->Release();
this->ptr = NULL;
}
}
inline T** ReleaseAndGetAddressOf()
{
Free();
return &ptr;
}
template<typename U>
inline HRESULT As(ComPtrRef<U> p) const throw ()
{
return ptr->QueryInterface(__uuidof(U), p);
}
inline bool operator==(std::nullptr_t) const
{
return this->ptr == nullptr;
}
template<typename U>
inline bool operator==(U* other)
{
if (ptr == nullptr || other == nullptr)
return ptr == other;
ComPtr<IUnknown> meUnknown;
ComPtr<IUnknown> otherUnknown;
if (FAILED(this->ptr->QueryInterface(__uuidof(IUnknown), &meUnknown)))
return false;
if (FAILED(other->QueryInterface(__uuidof(IUnknown), &otherUnknown)))
return false;
return static_cast<IUnknown*>(meUnknown) == static_cast<IUnknown*>(otherUnknown);
}
template<typename U>
inline bool operator==(ComPtr<U>& other)
{
return *this == static_cast<U*>(other);
}
inline bool operator!=(std::nullptr_t) const
{
return this->ptr != nullptr;
}
template<typename U>
inline bool operator!=(U* other)
{
return !(*this == other);
}
template<typename U>
inline bool operator!=(ComPtr<U>& other)
{
return *this != static_cast<U*>(other);
}
};
}

View File

@@ -0,0 +1,9 @@
Direct style:
cl /EHsc /std:c++17 COMIntegration.cpp /link Shlwapi.lib /out:"..\Release\COMIntegration.exe"
For a debug build with PDB:
cl /EHsc /std:c++17 /Z7 /DEBUG:FULL COMIntegration.cpp /link Shlwapi.lib /out:"..\Release\COMIntegration.exe"
CMake style:
cmake ../COMIntegration~ -B ./build
cmake --build ./build --config=release -- /p:OutDir=..