Skip to content

fix: Add MINGW compiler macro for MSYS2 Clang64 environment#5209

Open
feihongmeilian wants to merge 2 commits intopocoproject:mainfrom
feihongmeilian:main
Open

fix: Add MINGW compiler macro for MSYS2 Clang64 environment#5209
feihongmeilian wants to merge 2 commits intopocoproject:mainfrom
feihongmeilian:main

Conversation

@feihongmeilian
Copy link
Copy Markdown
Contributor

In the MSYS2 clang64 compilation environment, although the compiler is Clang, it is based on the MinGW runtime underneath. In this case, the Windows SEH syntax with __try/__except in the code is incompatible, which leads to compilation failures.

By detecting the MINGW32/MINGW64 macros under the Clang compiler branch and defining POCO_COMPILER_MINGW, subsequent code can skip the __try/__except related logic to ensure compilation compatibility.

In the MSYS2 clang64 compilation environment, although the compiler is Clang,
it is based on the MinGW runtime underneath. In this case, the Windows SEH syntax
with __try/__except in the code is incompatible, which leads to compilation failures.

By detecting the MINGW32/MINGW64 macros under the Clang compiler branch and defining
POCO_COMPILER_MINGW, subsequent code can skip the __try/__except related logic to
ensure compilation compatibility.
@feihongmeilian
Copy link
Copy Markdown
Contributor Author

Note: In my opinion, a better approach would be to separate platform detection (MINGW32/MINGW64) from compiler detection, and define POCO_COMPILER_MINGW in a standalone check instead of duplicating the logic inside each compiler branch.

This would make the code cleaner and easier to maintain. However, since I don't have access to enough platforms and environments for full validation, I'm applying this minimal fix for now to resolve the compilation issue on MSYS2 Clang64.

The separated detection logic can be refactored in a future change after more comprehensive testing.

#include <vector>

#ifdef _WIN32
#ifdef _WIN32 && defined(_MSC_VER)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: #ifdef only accepts a single identifier — it does not support expressions. The && defined(_MSC_VER) part will either be silently ignored or cause a preprocessor error depending on the compiler/mode.

// Current (broken):
#ifdef _WIN32 && defined(_MSC_VER)

// Should be:
#if defined(_WIN32) && defined(_MSC_VER)

That said, see my top-level review comment — I believe this change (and all the other __declspec guard changes) should actually be reverted entirely.

#include <stdio.h>

#ifdef _WIN32
#ifdef _WIN32 && defined(_MSC_VER)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Same #ifdef vs #if syntax error as in cpptrace.hpp. #ifdef does not support compound expressions.

// Current (broken):
#ifdef _WIN32 && defined(_MSC_VER)

// Would need to be:
#if defined(_WIN32) && defined(_MSC_VER)

Comment thread Data/SQLParser/src/sqlparser_win.h Outdated
#endif
#else
#if defined(_DLL) || defined(_USRDLL)
#if defined(_DLL) || defined(_USRDLL) && defined(_MSC_VER)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: operator precedence. In the C preprocessor && binds tighter than ||, so this evaluates as:

#if defined(_DLL) || (defined(_USRDLL) && defined(_MSC_VER))

The defined(_DLL) path is completely unguarded by _MSC_VER, which is almost certainly not the intent. If this change were to be kept, it should be:

#if (defined(_DLL) || defined(_USRDLL)) && defined(_MSC_VER)

Comment thread Foundation/include/Poco/ClassLibrary.h Outdated


#if defined(_WIN32)
#if defined(_WIN32) && defined(_MSC_VER)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regression: This breaks MinGW plugin loading. With this change, MinGW builds will fall through to the __GNUC__ visibility branch, which uses ELF-style __attribute__((visibility("default"))). That attribute has no effect on Windows — plugins built with MinGW will fail to export the required ClassLoader entry points.

__declspec(dllexport) is correct and supported by all Windows compilers (MSVC, GCC-MinGW, Clang-MinGW). The original #if defined(_WIN32) guard was right here.

// defined with this macro as being exported.
//
#if (defined(_WIN32) || defined(__CYGWIN__)) && defined(POCO_DLL)
#if defined(_WIN32) && defined(_MSC_VER) && defined(POCO_DLL)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regression: The original guard included defined(__CYGWIN__). This change silently drops Cygwin DLL export/import support. The same issue applies to NetSSL_OpenSSL/include/Poco/Net/NetSSL.h and NetSSL_Win/include/Poco/Net/NetSSL.h.

#if __has_include(<cxxabi.h>)
#define POCO_HAVE_CXXABI_H
#endif
#if defined (__MINGW32__) || defined (__MINGW64__)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 This addition is correct and valuable. Currently, MSYS2 Clang64 gets POCO_COMPILER_CLANG but not POCO_COMPILER_MINGW, which means MinGW-specific workarounds (e.g. avoiding __try/__except SEH) do not trigger. This is the right fix for the core issue described in this PR.


#ifdef _WIN32
#ifdef _WIN32 && defined(_MSC_VER)
#define CPPTRACE_EXPORT_ATTR __declspec(dllexport)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maintenance concern: This is a vendored upstream header (cpptrace). Changes here will be overwritten on the next vendor update. If a fix is needed, it would be better handled via a patch file or build-system defines that avoid modifying the vendored source directly.

Copy link
Copy Markdown
Contributor

@matejk matejk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Summary

Thanks for working on MSYS2 Clang64 support! The diagnosis is correct — Clang64 in MSYS2 targets MinGW but doesn't support MSVC-specific __try/__except SEH syntax, and POCO_COMPILER_MINGW isn't being set in that environment.

However, the approach of gating all __declspec(dllexport/dllimport) behind _MSC_VER is fundamentally incorrect and will break shared library builds for all non-MSVC Windows compilers.

Critical issues

  1. __declspec(dllexport/dllimport) works on all Windows compilers — GCC-MinGW and Clang-MinGW fully support __declspec. Restricting it to MSVC-only means MinGW/Clang shared library builds will produce DLLs with no exported symbols.

  2. Preprocessor syntax errors — Two files use #ifdef X && defined(Y) which is invalid (#ifdef takes a single token). See inline comments on cpptrace.hpp and ctrace.h.

  3. Operator precedence bugsqlparser_win.h has defined(_DLL) || defined(_USRDLL) && defined(_MSC_VER) where && binds tighter than ||, leaving the _DLL path unguarded.

  4. Cygwin support silently dropped — Three files previously included __CYGWIN__ in their guards; this is removed without discussion.

  5. ClassLibrary.h regression — MinGW plugin builds will fall through to the GCC visibility attribute branch, which has no effect on Windows.

  6. Vendored headers modifiedcpptrace.hpp and ctrace.h are upstream vendored files; direct edits will be lost on the next update.

What should change

  • Keep the POCO_COMPILER_MINGW addition in Platform.h (this is the correct fix)
  • Revert all __declspec guard changes — __declspec(dllexport/dllimport) is correct for all Windows compilers
  • Fix the actual issue — guard __try/__except SEH usage sites with !defined(POCO_COMPILER_MINGW) or defined(POCO_COMPILER_MSVC) where SEH is used
  • Split out the ODBCVER 0x03000x0350 bump into a separate PR for cleaner history

@feihongmeilian
Copy link
Copy Markdown
Contributor Author

feihongmeilian commented Feb 20, 2026

I added the check for _MSC_VER because when compiling POCO, the JSON library depends on the Foundation library. Without the _MSC_VER check, the code would use the __declspec syntax. The following code exists in Foundation\include\Poco\Dynamic\Struct.h:

#if defined(POCO_OS_FAMILY_WINDOWS)

extern template class Struct<std::string>;
extern template class Struct<int>;

extern template class Struct<std::string, Poco::OrderedMap<std::string, Var>, Poco::OrderedSet<std::string>>;
extern template class Struct<int, OrderedMap<int, Var>, OrderedSet<int>>;

#else

extern template class Foundation_API Struct<std::string>;
extern template class Foundation_API Struct<int>;

extern template class Foundation_API Struct<std::string, Poco::OrderedMap<std::string, Var>, Poco::OrderedSet<std::string>>;
extern template class Foundation_API Struct<int, OrderedMap<int, Var>, OrderedSet<int>>;

#endif

It is mandatory to add Foundation_API before the template classes within the POCO_OS_FAMILY_WINDOWS block as well; otherwise, the function definitions cannot be found. However, MSVC (Microsoft Visual C++) behaves correctly in this scenario: it automatically adds Foundation_API to template classes, so the missing Foundation_API in the Windows branch does not cause issues for pure MSVC builds.
That said, after modifying this part for non-MSVC Windows compilers (e.g., mingw64/clang), many other places still need to be adjusted, and some template classes require additional export syntax. When I added the _MSC_VER check, the code uses the attribute syntax instead, which resolves this issue universally. The POCO dynamic library can be fully compiled under clang and mingw64 in msys2, so I confirm this approach works.
That said, the following modification might be more robust and compatible with the clang-cl compiler:

#if defined(_WIN32) && defined(POCO_DLL)
    #if defined(_MSC_VER) || (defined(__clang__) && defined(_MSC_VER))
        #ifdef Foundation_EXPORTS
            #define Foundation_API __declspec(dllexport)
        #else
            #define Foundation_API __declspec(dllimport)
        #endif
    #else
        #ifdef Foundation_EXPORTS
            #define Foundation_API __attribute__((visibility("default")))
        #else
            #define Foundation_API
        #endif
    #endif
#endif

#if !defined(Foundation_API)
	#if !defined(POCO_NO_GCC_API_ATTRIBUTE) && defined (__GNUC__) && (__GNUC__ >= 4)
		#define Foundation_API __attribute__ ((visibility ("default")))
	#else
		#define Foundation_API
	#endif
#endif

@feihongmeilian
Copy link
Copy Markdown
Contributor Author

I am not very familiar with submitting changes to GitHub. For each submission, I end up forking the repository again from scratch. Should I close this current pull request (PR), re-fork the repository, and then create three separate branches in the forked repository to submit my changes individually?
I originally intended to make three separate submissions. However, since I did not know how to fork the repository three times (or manage multiple forks properly), and my first commit was made directly on the main branch, all of my changes ended up being included in a single pull request.

@matejk
Copy link
Copy Markdown
Contributor

matejk commented Feb 20, 2026

You can add more commits to the branch you created for the PR and push them.

@matejk
Copy link
Copy Markdown
Contributor

matejk commented Feb 20, 2026

@feihongmeilian Thanks for the detailed explanation about the extern template issue — that's a real problem and I appreciate you digging into it.

However, I believe this was already resolved by PR #5202 (merged Feb 12, before this PR was created). That PR changed all the extern template guards from:

#if defined(POCO_OS_FAMILY_WINDOWS)
extern template class Struct<std::string>;           // no _API macro
#else
extern template class Foundation_API Struct<std::string>;
#endif

to:

#if defined(POCO_OS_FAMILY_WINDOWS) && defined(Foundation_EXPORTS)
extern template class Struct<std::string>;           // only when building the DLL
#else
extern template class Foundation_API Struct<std::string>; // consumers + non-Windows
#endif

This means MinGW consumers now always take the #else branch and get the properly __declspec(dllimport)-decorated extern template declarations, which should resolve the linker errors you were seeing. Since your branch is based on main, you should already have this fix.

Given that, the __declspec__attribute__((visibility)) workaround in this PR should no longer be necessary. I'd suggest:

  1. Keep the POCO_COMPILER_MINGW addition in Platform.h — this is correct and valuable
  2. Keep the ODBCVER bump (ideally as a separate PR, but not a blocker)
  3. Drop all the _MSC_VER guard changes to the _API macros — with fix(cmake+msvc): Restore dllimport on extern template declarations (#5200) #5202 merged, __declspec(dllexport/dllimport) should work correctly on MinGW

Could you rebase on current main and verify that the MinGW/Clang64 build works without the _API macro changes?

@feihongmeilian
Copy link
Copy Markdown
Contributor Author

I've reverted the commit regarding symbol exports and split the ODBCVER modification into a new PR. However, after updating my local branch to main, I'm still encountering compilation failures with Clang in MSYS2. I may need to explore alternative approaches to fix this issue going forward.

@feihongmeilian
Copy link
Copy Markdown
Contributor Author

feihongmeilian commented Mar 1, 2026

@matejk I have concerns about the appropriateness of the following implementation:

#if defined(POCO_OS_FAMILY_WINDOWS) && defined(Foundation_EXPORTS)
extern template class Struct<std::string>;           // only when building the DLL
#else
extern template class Foundation_API Struct<std::string>; // consumers + non-Windows
#endif

When POCO_OS_FAMILY_WINDOWS is defined and Foundation_EXPORTS is set, the code path extern template class Structstd::string; will be taken. Could this result in missing symbol exports from the DLL when the function is used?
Furthermore, I think conditional checks for function exports are less optimal than directly setting Foundation_API to an empty value in specific scenarios. Splitting the judgment logic between export and import stages will lead to fragmented logic and increase the risk of errors.
To simplify the logic and avoid potential symbol export issues, I suggest removing the conditional branches and directly using the unified declaration:

extern template class Foundation_API Struct<std::string>;

This approach centralizes the export/import control in the Foundation_API macro itself (e.g., setting it to __declspec(dllexport), __declspec(dllimport) or an empty value based on compilation context), eliminating scattered conditional judgments and ensuring consistent symbol handling across all platforms and build scenarios.

@matejk
Copy link
Copy Markdown
Contributor

matejk commented Mar 2, 2026

@feihongmeilian The current pattern is correct. The extern template declaration in the header only suppresses implicit instantiation — it does not control symbol export. The actual export happens in the .cpp file.

When building the DLL (Foundation_EXPORTS is defined):

  • Struct.h:291extern template class Struct<std::string>; — tells the compiler "don't instantiate this here," no export annotation needed
  • VarHolder.cpp:27template class Foundation_API Struct<std::string>; — this is where the single explicit instantiation happens, and Foundation_API expands to __declspec(dllexport), so the symbols get exported

When consuming the DLL (Foundation_EXPORTS is not defined):

  • Struct.h:299extern template class Foundation_API Struct<std::string>; — here Foundation_API expands to __declspec(dllimport), so the consumer knows to import these symbols from the DLL

Your suggestion to unconditionally use extern template class Foundation_API Struct<...>; would mean that when building the DLL we'd get extern template class __declspec(dllexport) Struct<...>;. MSVC has known issues with this combination — it may ignore the extern and instantiate the template in every translation unit, causing duplicate symbols. The two-branch approach exists specifically to avoid this.

Regarding Clang on Windows: both Clang-CL and Clang/MinGW support __declspec(dllexport/dllimport) — this is a PE/COFF format requirement, not MSVC-specific. __attribute__((visibility("default"))) is an ELF concept and has no effect on Windows DLL exports.

If you're still seeing build failures with Clang64 after rebasing on current main, could you share the specific error messages? That would help identify whether the issue is in the extern template handling or somewhere else.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants