| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the tools applications of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include <QDir> |
| #include <QFile> |
| #include <QDir> |
| #include <QScopedArrayPointer> |
| #include <qt_windows.h> |
| #include <io.h> |
| |
| QT_BEGIN_NAMESPACE |
| |
| static QString quotePath(const QString &s) |
| { |
| if (!s.startsWith(QLatin1Char('\"')) && s.contains(QLatin1Char(' '))) |
| return QLatin1Char('\"') + s + QLatin1Char('\"'); |
| return s; |
| } |
| |
| static bool hasFileExtension(const QString &filePath, const QString &extension) |
| { |
| return filePath.endsWith(extension, Qt::CaseInsensitive); |
| } |
| |
| static bool hasExeExtension(const QString &filePath) |
| { |
| return hasFileExtension(filePath, QStringLiteral(".exe")); |
| } |
| |
| static bool hasDllExtension(const QString &filePath) |
| { |
| return hasFileExtension(filePath, QStringLiteral(".dll")); |
| } |
| |
| // Prepend the Qt binary directory to PATH. |
| static bool prependPath() |
| { |
| enum { maxEnvironmentSize = 32767 }; |
| wchar_t buffer[maxEnvironmentSize]; |
| if (!GetModuleFileName(nullptr, buffer, maxEnvironmentSize)) |
| return false; |
| wchar_t *ptr = wcsrchr(buffer, L'\\'); |
| if (!ptr) |
| return false; |
| *ptr++ = L';'; |
| const wchar_t pathVariable[] = L"PATH"; |
| return GetEnvironmentVariable(pathVariable, ptr, DWORD(maxEnvironmentSize - (ptr - buffer))) != 0 |
| && SetEnvironmentVariable(pathVariable, buffer) == TRUE; |
| } |
| |
| static QString errorString(DWORD errorCode) |
| { |
| wchar_t *resultW = nullptr; |
| FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM, |
| nullptr, errorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), |
| reinterpret_cast<LPWSTR>(&resultW), 0, nullptr); |
| const QString result = QString::fromWCharArray(resultW); |
| LocalFree(resultW); |
| return result; |
| } |
| |
| static bool runWithQtInEnvironment(const QString &cmd) |
| { |
| enum { timeOutMs = 30000 }; |
| static const bool pathSet = prependPath(); |
| if (!pathSet) |
| return false; |
| |
| STARTUPINFO si; |
| ZeroMemory(&si, sizeof(si)); |
| si.cb = sizeof(si); |
| |
| STARTUPINFO myInfo; |
| GetStartupInfo(&myInfo); |
| si.hStdInput = myInfo.hStdInput; |
| si.hStdOutput = myInfo.hStdOutput; |
| si.hStdError = myInfo.hStdError; |
| |
| PROCESS_INFORMATION pi; |
| ZeroMemory(&pi, sizeof(PROCESS_INFORMATION)); |
| |
| QScopedArrayPointer<wchar_t> commandLineW(new wchar_t[cmd.size() + 1]); |
| cmd.toWCharArray(commandLineW.data()); |
| commandLineW[cmd.size()] = 0; |
| if (!CreateProcessW(nullptr, commandLineW.data(), nullptr, nullptr, /* InheritHandles */ TRUE, 0, nullptr, nullptr, &si, &pi)) { |
| fprintf(stderr, "Unable to execute \"%s\": %s\n", qPrintable(cmd), |
| qPrintable(errorString(GetLastError()))); |
| return false; |
| } |
| |
| DWORD exitCode = 1; |
| switch (WaitForSingleObject(pi.hProcess, timeOutMs)) { |
| case WAIT_OBJECT_0: |
| GetExitCodeProcess(pi.hProcess, &exitCode); |
| break; |
| case WAIT_TIMEOUT: |
| fprintf(stderr, "Timed out after %d ms out waiting for \"%s\".\n", |
| int(timeOutMs), qPrintable(cmd)); |
| TerminateProcess(pi.hProcess, 1); |
| break; |
| default: |
| fprintf(stderr, "Error waiting for \"%s\": %s\n", |
| qPrintable(cmd), qPrintable(errorString(GetLastError()))); |
| TerminateProcess(pi.hProcess, 1); |
| break; |
| } |
| CloseHandle(pi.hThread); |
| CloseHandle(pi.hProcess); |
| if (exitCode) |
| fprintf(stderr, "\"%s\" returned exit code: %lu (0x%lx)\n", qPrintable(cmd), exitCode, exitCode); |
| return exitCode == 0; |
| } |
| |
| static bool attachTypeLibrary(const QString &applicationName, int resource, const QByteArray &data, QString *errorMessage) |
| { |
| HANDLE hExe = BeginUpdateResource(reinterpret_cast<const wchar_t *>(applicationName.utf16()), false); |
| if (hExe == nullptr) { |
| if (errorMessage) |
| *errorMessage = QString::fromLatin1("Failed to attach type library to binary %1 - could not open file.").arg(applicationName); |
| return false; |
| } |
| if (!UpdateResource(hExe, L"TYPELIB", MAKEINTRESOURCE(resource), 0, |
| const_cast<char *>(data.data()), DWORD(data.count()))) { |
| EndUpdateResource(hExe, true); |
| if (errorMessage) |
| *errorMessage = QString::fromLatin1("Failed to attach type library to binary %1 - could not update file.").arg(applicationName); |
| return false; |
| } |
| |
| if (!EndUpdateResource(hExe,false)) { |
| if (errorMessage) |
| *errorMessage = QString::fromLatin1("Failed to attach type library to binary %1 - could not write file.").arg(applicationName); |
| return false; |
| } |
| |
| if (errorMessage) |
| *errorMessage = QString::fromLatin1("Type library attached to %1.").arg(applicationName); |
| return true; |
| } |
| |
| // Manually add defines that are missing from pre-VS2012 compilers |
| #ifndef LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR |
| # define LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR 0x00000100 |
| #endif |
| #ifndef LOAD_LIBRARY_SEARCH_DEFAULT_DIRS |
| # define LOAD_LIBRARY_SEARCH_DEFAULT_DIRS 0x00001000 |
| #endif |
| |
| static HMODULE loadLibraryQt(const QString &input) |
| { |
| const wchar_t *inputC = reinterpret_cast<const wchar_t *>(input.utf16()); |
| // Load DLL with the folder containing the DLL temporarily added to the search path when loading dependencies |
| const UINT oldErrorMode = SetErrorMode(SEM_FAILCRITICALERRORS); |
| HMODULE result = |
| LoadLibraryEx(inputC, nullptr, LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | LOAD_LIBRARY_SEARCH_DEFAULT_DIRS); |
| // If that fails, call with flags=0 to get LoadLibrary() behavior (search %PATH%). |
| if (!result) |
| result = LoadLibraryEx(inputC, nullptr, 0); |
| SetErrorMode(oldErrorMode); |
| return result; |
| } |
| |
| static bool registerServer(const QString &input) |
| { |
| bool ok = false; |
| if (hasExeExtension(input)) { |
| ok = runWithQtInEnvironment(quotePath(input) + QLatin1String(" -regserver")); |
| } else { |
| HMODULE hdll = loadLibraryQt(input); |
| if (!hdll) { |
| fprintf(stderr, "Couldn't load library file %s\n", qPrintable(input)); |
| return false; |
| } |
| typedef HRESULT(__stdcall* RegServerProc)(); |
| RegServerProc DllRegisterServer = reinterpret_cast<RegServerProc>(GetProcAddress(hdll, "DllRegisterServer")); |
| if (!DllRegisterServer) { |
| fprintf(stderr, "Library file %s doesn't appear to be a COM library\n", qPrintable(input)); |
| return false; |
| } |
| ok = DllRegisterServer() == S_OK; |
| } |
| return ok; |
| } |
| |
| static bool unregisterServer(const QString &input) |
| { |
| bool ok = false; |
| if (hasExeExtension(input)) { |
| ok = runWithQtInEnvironment(quotePath(input) + QLatin1String(" -unregserver")); |
| } else { |
| HMODULE hdll = loadLibraryQt(input); |
| if (!hdll) { |
| fprintf(stderr, "Couldn't load library file %s\n", qPrintable(input)); |
| return false; |
| } |
| typedef HRESULT(__stdcall* RegServerProc)(); |
| RegServerProc DllUnregisterServer = reinterpret_cast<RegServerProc>(GetProcAddress(hdll, "DllUnregisterServer")); |
| if (!DllUnregisterServer) { |
| fprintf(stderr, "Library file %s doesn't appear to be a COM library\n", qPrintable(input)); |
| return false; |
| } |
| ok = DllUnregisterServer() == S_OK; |
| } |
| return ok; |
| } |
| |
| static HRESULT dumpIdl(const QString &input, const QString &idlfile, const QString &version) |
| { |
| HRESULT res = E_FAIL; |
| |
| if (hasExeExtension(input)) { |
| const QString command = quotePath(input) + QLatin1String(" -dumpidl ") |
| + quotePath(idlfile) + QLatin1String(" -version ") + version; |
| if (runWithQtInEnvironment(command)) |
| res = S_OK; |
| } else { |
| HMODULE hdll = loadLibraryQt(input); |
| if (!hdll) { |
| fprintf(stderr, "Couldn't load library file %s\n", qPrintable(input)); |
| return 3; |
| } |
| typedef HRESULT(__stdcall* DumpIDLProc)(const QString&, const QString&); |
| DumpIDLProc DumpIDL = reinterpret_cast<DumpIDLProc>(GetProcAddress(hdll, "DumpIDL")); |
| if (!DumpIDL) { |
| fprintf(stderr, "Couldn't resolve 'DumpIDL' symbol in %s\n", qPrintable(input)); |
| return 3; |
| } |
| res = DumpIDL(idlfile, version); |
| FreeLibrary(hdll); |
| } |
| |
| return res; |
| } |
| |
| const char usage[] = |
| "Usage: idc [options] [input_file]\n" |
| "Interface Description Compiler " QT_VERSION_STR "\n\n" |
| "Options:\n" |
| " -?, /h, -h, -help Displays this help.\n" |
| " /v, -v Displays version information.\n" |
| " /version, -version <version> Specify the interface version.\n" |
| " /idl, -idl <file> Specify the interface definition file.\n" |
| " /tlb, -tlb <file> Specify the type library file.\n" |
| " /regserver, -regserver Register server.\n" |
| " /unregserver, -unregserver Unregister server.\n\n" |
| "Examples:\n" |
| "idc -regserver l.dll Register the COM server l.dll\n" |
| "idc -unregserver l.dll Unregister the COM server l.dll\n" |
| "idc l.dll -idl l.idl -version 2.3 Writes the IDL of the server dll to the file idl.\n" |
| " The type library will have version 2.3\n" |
| "idc l.dll -tlb l.tlb Replaces the type library in l.dll with l.tlb\n"; |
| |
| enum Mode { RegisterServer, UnregisterServer, Other }; |
| |
| int runIdc(int argc, char **argv) |
| { |
| QString error; |
| QString tlbfile; |
| QString idlfile; |
| QString input; |
| QString version = QLatin1String("1.0"); |
| Mode mode = Other; |
| |
| int i = 1; |
| while (i < argc) { |
| QString p = QString::fromLocal8Bit(argv[i]).toLower(); |
| |
| if (p == QLatin1String("/idl") || p == QLatin1String("-idl")) { |
| ++i; |
| if (i > argc) { |
| error = QLatin1String("Missing name for interface definition file!"); |
| break; |
| } |
| idlfile = QFile::decodeName(argv[i]).trimmed(); |
| } else if (p == QLatin1String("/version") || p == QLatin1String("-version")) { |
| ++i; |
| if (i > argc) |
| version = QLatin1String("1.0"); |
| else |
| version = QLatin1String(argv[i]); |
| } else if (p == QLatin1String("/tlb") || p == QLatin1String("-tlb")) { |
| ++i; |
| if (i > argc) { |
| error = QLatin1String("Missing name for type library file!"); |
| break; |
| } |
| tlbfile = QFile::decodeName(argv[i]).trimmed(); |
| } else if (p == QLatin1String("/v") || p == QLatin1String("-v")) { |
| fprintf(stdout, "Qt Interface Definition Compiler version 1.0 using Qt %s\n", QT_VERSION_STR); |
| return 0; |
| } else if (p == QLatin1String("/h") || p == QLatin1String("-h") || p == QLatin1String("-?") || p == QLatin1String("/?")) { |
| fprintf(stdout, "%s\n", usage); |
| return 0; |
| } else if (p == QLatin1String("/regserver") || p == QLatin1String("-regserver")) { |
| mode = RegisterServer; |
| } else if (p == QLatin1String("/unregserver") || p == QLatin1String("-unregserver")) { |
| mode = UnregisterServer; |
| } else if (p[0] == QLatin1Char('/') || p[0] == QLatin1Char('-')) { |
| error = QLatin1String("Unknown option \"") + p + QLatin1Char('"'); |
| break; |
| } else { |
| input = QFile::decodeName(argv[i]).trimmed(); |
| // LoadLibraryEx requires a fully qualified path when used together with LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR |
| input = QFileInfo(input).absoluteFilePath(); |
| input = QDir::toNativeSeparators(input); |
| } |
| i++; |
| } |
| if (!error.isEmpty()) { |
| fprintf(stderr, "%s\n", qPrintable(error)); |
| return 5; |
| } |
| if (input.isEmpty()) { |
| fprintf(stderr, "No input file specified!\n\n%s\n", usage); |
| return 1; |
| } |
| |
| switch (mode) { |
| case RegisterServer: |
| if (!registerServer(input)) { |
| fprintf(stderr, "Failed to register server!\n"); |
| return 1; |
| } |
| fprintf(stderr, "Server registered successfully!\n"); |
| return 0; |
| case UnregisterServer: |
| if (!unregisterServer(input)) { |
| fprintf(stderr, "Failed to unregister server!\n"); |
| return 1; |
| } |
| fprintf(stderr, "Server unregistered successfully!\n"); |
| return 0; |
| case Other: |
| break; |
| } |
| |
| if (hasExeExtension(input) && tlbfile.isEmpty() && idlfile.isEmpty()) { |
| fprintf(stderr, "No type output file specified!\n"); |
| return 2; |
| } |
| if (hasDllExtension(input) && idlfile.isEmpty() && tlbfile.isEmpty()) { |
| fprintf(stderr, "No interface definition file and no type library file specified!\n"); |
| return 3; |
| } |
| if (!tlbfile.isEmpty()) { |
| tlbfile = QDir::toNativeSeparators(tlbfile); |
| QFile file(tlbfile); |
| if (!file.open(QIODevice::ReadOnly)) { |
| fprintf(stderr, "Couldn't open %s for read: %s\n", qPrintable(tlbfile), qPrintable(file.errorString())); |
| return 4; |
| } |
| QString error; |
| const bool ok = attachTypeLibrary(input, 1, file.readAll(), &error); |
| fprintf(stderr, "%s\n", qPrintable(error)); |
| return ok ? 0 : 4; |
| } |
| if (!idlfile.isEmpty()) { |
| idlfile = QDir::toNativeSeparators(idlfile); |
| fprintf(stderr, "\n\n%s\n\n", qPrintable(idlfile)); |
| const HRESULT res = dumpIdl(input, idlfile, version); |
| |
| switch (res) { |
| case S_OK: |
| break; |
| case E_FAIL: |
| fprintf(stderr, "IDL generation failed trying to run program %s!\n", qPrintable(input)); |
| return res; |
| case -1: |
| fprintf(stderr, "Couldn't open %s for writing!\n", qPrintable(idlfile)); |
| return res; |
| case 1: |
| fprintf(stderr, "Malformed appID value in %s!\n", qPrintable(input)); |
| return res; |
| case 2: |
| fprintf(stderr, "Malformed typeLibID value in %s!\n", qPrintable(input)); |
| return res; |
| case 3: |
| fprintf(stderr, "Class has no metaobject information (error in %s)!\n", qPrintable(input)); |
| return res; |
| case 4: |
| fprintf(stderr, "Malformed classID value in %s!\n", qPrintable(input)); |
| return res; |
| case 5: |
| fprintf(stderr, "Malformed interfaceID value in %s!\n", qPrintable(input)); |
| return res; |
| case 6: |
| fprintf(stderr, "Malformed eventsID value in %s!\n", qPrintable(input)); |
| return res; |
| |
| default: |
| fprintf(stderr, "Unknown error writing IDL from %s\n", qPrintable(input)); |
| return 7; |
| } |
| } |
| return 0; |
| } |
| |
| QT_END_NAMESPACE |
| |
| int main(int argc, char **argv) |
| { |
| return QT_PREPEND_NAMESPACE(runIdc)(argc, argv); |
| } |