1

I am working on program that uses a DLL library (LibPlcTag : https://github.com/libplctag/libplctag) and I wanted to add some exception handling to 'my' code. However after finding this was not working, that is the thrown exceptions were not being caught, I managed to distill it all down to the following bit of demonstration code.

So the question is what is going on here ? Why does a call to a DLL function kill C++ try catch ? Is there a way to fix this ?

FYI : I am using Mingw32 C++ with -std=c++11 compiler build flag on Windows 11.

#include <iostream>

#define SHOWERROR 1

#if SHOWERROR
  #ifdef __cplusplus
  extern "C" {
  #endif
  __declspec(dllimport) int plc_tag_check_lib_version(int req_major, int req_minor, int req_patch);
  #ifdef __cplusplus
  }
  #endif
#endif

int main(int argc, char *argv[])
{
  #if SHOWERROR
    std::cout << "Call DLL function" << std::endl;
    plc_tag_check_lib_version(0,0,0);
    std::cout << "Call Finished" << std::endl;
  #else
    std::cout << "No DLL Function Call !!" << std::endl;
  #endif

  try
  {
    std::cout << "Throw error" << std::endl;
    throw "Error";
  }
  catch( ... )
  {
    std::cout << "Exception happened" << std::endl;
  }

  std::cout << "End" << std::endl;
  return 0;
}

With SHOWERROR = 0 the exception is caught :

No DLL Function Call !!
Throw error
Exception happened
End

C:\...\test>

But with SHOWERROR = 1 the exception is not caught and the program terminates without printing 'End' :

Call DLL function
Call Finished
Throw error

C:\...\test>
  • Did you load a DLL containing the definition of that function? If you don't load a DLL, you can't call a DLL function. – Nicol Bolas Dec 13 '22 at 15:03
  • 1
    Throwing an exception from within an extern "C" function is undefined behavior. – Pepijn Kramer Dec 13 '22 at 15:09
  • @PepijnKramer can you provide reference for that claim? I can't find anything about that. I usually start with this [list of UBs](https://stackoverflow.com/a/367662/1387438). – Marek R Dec 13 '22 at 15:12
  • @MarekR It is more implementation specific. But If you think about it : "C" is not designed with exception unwinding in mind. MSVC has support for developer to enable C++ stack unwinding : https://learn.microsoft.com/en-us/cpp/build/reference/eh-exception-handling-modelredirectedfrom=MSDN&view=msvc-170. Can't find something similar for gcc/clang – Pepijn Kramer Dec 13 '22 at 15:19
  • I took the newest release of that libplctag library and I can't reproduce your problem. After the call to `plc_tag_check_lib_version` exception is caught as expected. – pptaszni Dec 13 '22 at 15:20
  • Does it matter if the function is actually called? What happens if you move the call into a separate function that you never call? I would suspect it's not the calling of the function but the loading of the dll that's causing the issue. If you don't actually reference any symbol from the dll, the linker will not actually link against it and the dll will thus never be loaded. – Chronial Dec 13 '22 at 15:29
  • In any case, OP is not throwing from extern C function but from `int main` and the program is compiled with C++ compiler, so I don't see any reason for the exception mechanism not to work. Probably another windows bug, like usual. – pptaszni Dec 13 '22 at 15:29
  • You probably have the wrong calling convention for the DLL function, which is leading to a corrupted stack pointer after the call. It's very important that caller and callee agree on who cleans the parameters off the stack. – Ben Voigt Dec 13 '22 at 15:55
  • pptaszni - I would if the build of LIbPlcTag has gone wrong in some way, which is a bit baffling as I can you use the Library ok to talk to my PLC's. – user3314691 Dec 13 '22 at 16:01
  • Ben Voigt - I did not write LibPlcTag, I just include its headers and DLL and it works. So I don't understand how it could have the wrong calling convention. Surely it would not work at all. – user3314691 Dec 13 '22 at 16:03
  • Chronial - Yes, interesting thought which I just tried. Having the function call in another function that is never called gives the same result, the catch does not happen. So something to do with the DLL loading, which I assume happens automatically as there is no need to call anything first to use the DLL lib. And by the way it does not matter which function I call in the DLL Lib either, the result is the same. – user3314691 Dec 13 '22 at 16:07
  • @user3314691: Ahh, if it doesn't matter if you call the DLL function, then it isn't calling convention. (But do note that "I called one function and it ran properly but I got problems later" does not mean the calling convention is correct, because some of the calling convention differences affect the function return). – Ben Voigt Dec 13 '22 at 16:37
  • I would recommend indicating whether the `catch (...)` block is entered via some additional means, just in case the behavior you are seeing is not "try/catch is broken" but "`std::cout` is broken in a way that doesn't affect the first line of output / doesn't affect output prior to the `throw`". Evidently something in the DLL loading sequence is messing your program up, whether that's a `DllMain` (the toolchain-provided DLL entrypoint, not user-written one) or else another DLL that the first DLL has a dependency on. – Ben Voigt Dec 13 '22 at 16:41
  • Quite possibly the DLL is written in a language that also uses exceptions, and somehow the C++ exception handler is finding the exception unwinding data for the DLL and not knowing what to do with it. Actually the library build docs mention that building with MINGW creates a dependency on dwarf debug library which is going to be exception-related. – Ben Voigt Dec 13 '22 at 16:43
  • Writing a message to a test file in the catch block does not happen either when it fails. So it's not just the stdout which is being sent somewhere else. – user3314691 Dec 13 '22 at 17:04
  • Since 'pptaszni' was able to build the library and run my test code ok I am thinking it must be something which goes wrong when I build the Library DLL from the provided source code. – user3314691 Dec 13 '22 at 17:06
  • https://stackoverflow.com/questions/7244645/porting-vcs-try-except-exception-stack-overflow-to-mingw – Hans Passant Dec 13 '22 at 18:15

1 Answers1

0

After working on the hunch that my build LibPlcTag was broken in some way I went back to the beginning and instead of using the MinGW32 (5.1.0) that was installed by PlatformIO for 'platform = windows_x86' I installed MSYS2 and the mingw-w64-i686-toolchain. I then re-built the LibPlcTag library to get a new DLL. I then used the mingw toolchain above to rebuild the my test app. Now it works and the exception is caught.

The final test code :

#include <iostream>
#include <fstream>

#define SHOWERROR 1

#if SHOWERROR
  #ifdef __cplusplus
  extern "C" {
  #endif
  __declspec(dllimport) int plc_tag_check_lib_version(int req_major, int req_minor, int req_patch);
  #ifdef __cplusplus
  }
  #endif
#endif

// void not_called()
// {
//   std::cout << "Call DLL function" << std::endl;
//   #if SHOWERROR
//     plc_tag_check_lib_version(0,0,0);
//   #endif
//   std::cout << "Call Finished" << std::endl;
// }

int main(int argc, char *argv[])

{
  #if SHOWERROR
    std::cout << "Call DLL function" << std::endl;
    plc_tag_check_lib_version(0,0,0);
    std::cout << "Call Finished" << std::endl;
  #else
    std::cout << "No DLL Function Call !!" << std::endl;
  #endif

  try
  {
    std::cout << "Throw error" << std::endl;
    throw "Error";
  }
  catch( ... )
  {
    std::ofstream debugFile("debug.txt");
    debugFile << "Exception happened" << std::endl;
    debugFile.close();
    std::cout << "Exception happened" << std::endl;
  }

    std::cout << "End" << std::endl;
  return 0;
}

And output :

Call DLL function
Call Finished
Throw error
Exception happened
End

C:\...\test>

Thank you to those that contributed the above thoughtful comments...