三国卡牌客户端基础资源仓库
yyl
2025-05-15 aac116baba3b0c43808e05458c2d4793a0729730
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
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;
}