Platform differences encapsulation

In our app, we need to detect if the debugger is attached to either allow CrashPad to catch the crash or allow the developer to dive into the wonderful world of debugging in the IDE.

The app runs on both Windows and macOS and 99% of the code is cross-platform.

So, on Windows, there is a WinAPI <IsDebuggerPresent> function.
https://msdn.microsoft.com/en-us/library/windows/desktop/ms680345(v=vs.85).aspx

On a macOS, we have to implement this functionality ourselves.

My colleague wrote something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#ifdef WIN32
#include <windows.h>
#else
bool IsDebuggerPresent()
{
 int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid()};
 struct kinfo_proc info;

 info.kp_proc.p_flag = 0;
 size_t size = sizeof(info);
 sysctl(mib, 4, &info, &size, NULL, 0);

 return (info.kp_proc.p_flag & P_TRACED) != 0;
}
#endif 


As seen from the snippet above, on macOS he defines the <IsDebuggerPresent> function, that is included in windows.h on a Windows platform.
And he calls this functions below in the CPP file like that:

1
2
3
4
if (IsDebuggerPresent())
{
    // ...
}


Personally, I dislike this approach.
I recommended to create a separate function:

1
2
3
4
5
6
7
8
9
10
11
12
// debugger_detector.h
bool isDebuggerAttached();

// debugger_detector.cpp
bool isDebuggerAttached()
{
#ifdef WIN32
    return IsDebuggerPresent();
#else
   // here goes macOS implementation
#endif
}


What's your opinion on these approaches?
I think that the latter one is a better design but I don't know if there is a common pattern or approach that describes this. Are you aware of any?
My colleague doesn't disagree with me but he thinks that his implementation is pretty logical and I would like to point him to some document/book/article that would argue that.

Thank you
Last edited on
Personally, I'd go for something like this.

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
// include/debugger_detector.h
#ifndef DEBUGGER_DETECTOR_H_INCLUDED
#define DEBUGGER_DETECTOR_H_INCLUDED
bool isDebuggerAttached();
#endif


// Win32/debugger_detector.cpp
#include <windows.h>
#include "debugger_detector.h"
bool isDebuggerAttached()
{
    return IsDebuggerPresent();
}

// MacOS/debugger_detector.cpp
#include .....
#include "debugger_detector.h"
bool isDebuggerAttached()
{
 int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid()};
 struct kinfo_proc info;

 info.kp_proc.p_flag = 0;
 size_t size = sizeof(info);
 sysctl(mib, 4, &info, &size, NULL, 0);

 return (info.kp_proc.p_flag & P_TRACED) != 0;
}


You extract a concept (is the debugger detected?) into your common code, and you implement the appropriate shims in platform/file.cpp file(s).

Which file is actually used is resolved by the build system choosing to compile the common code, and the appropriate platform.

If you add another platform, there should be zero modifications to any of your common source code. You certainly shouldn't be needing to hack about with any number of #ifdef WIN32 .. #elif .. #else fragments.
Personally, I prefer your version over his, but I don't have any arguments other than personal preference. I would go even further, actually:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// debugger_detector_windows.cpp (only compile and link if targeting Windows)
#include <Windows.h>

bool isDebuggerAttached()
{
    return IsDebuggerPresent();
}

// debugger_detector_macos.cpp (only compile and link if targeting MacOS)
bool isDebuggerAttached()
{
 int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid()};
 struct kinfo_proc info;

 info.kp_proc.p_flag = 0;
 size_t size = sizeof(info);
 sysctl(mib, 4, &info, &size, NULL, 0);

 return (info.kp_proc.p_flag & P_TRACED) != 0;
}
To me, at least, this is a bit easier to read because there's no need to mentally parse #if blocks.
#defs are useful for things that just won't compile on some targets, otherwise I also prefer the function approach.
things that won't compile are usually tied to platform specific headers, like windows.h in these examples.
It’s a non-issue, but I disagree with your approach for multiple names for the same interface.

It is common practice (for good raisins) to separate platform-specific code into separate files, the inclusion of which is managed by your build process.

I typically keep a layout similar to this:

source_dir/
  unix/
    platform.cpp

  win32/
    platform.cpp

  osx/
    platform.cpp

  Makefile
  platform.hpp
  ...

All the common interface goes in platform.hpp, implemented by the correct platform.cpp.

If you really want to have functionality unique to a specific platform which is not managed opaquely, then that goes in separate include file(s) under the appropriate platform subdirectory.

Hope this helps.
For some "real life" examples that use somewhat different techniques:

1. You can see SFML's respository. It's similar to the format Duthomhas posted.
https://github.com/SFML/SFML/blob/master/include/SFML/Window
https://github.com/SFML/SFML/tree/master/src/SFML/Window

Window.hpp is a common interface, while in the source directories, you have {platform}/Window.cpp implementation, with some other files that serve as glue.

1
2
3
4
5
6
7
8
9
#if defined(SFML_SYSTEM_WINDOWS)

    #include <SFML/Window/Win32/WindowImplWin32.hpp>
    typedef sf::priv::WindowImplWin32 WindowImplType;

#elif defined(SFML_SYSTEM_LINUX) || defined(SFML_SYSTEM_FREEBSD) || defined(SFML_SYSTEM_OPENBSD)

    #include <SFML/Window/Unix/WindowImplX11.hpp>
    typedef sf::priv::WindowImplX11 WindowImplType;


2. wxWidgets is messier, imo, but still gets the job done:
https://github.com/wxWidgets/wxWidgets/tree/master/include/wx/
https://github.com/wxWidgets/wxWidgets/tree/master/src/

src
    /msw
        /window.cpp
    /osx
       /window_osx.cpp
    /x11
        /window.cpp

include/wx
    /window.h
    /msw
        /window.h
    /osx
        /window.h
    /x11
        /window.h


/include/wx/window.h is a bit messy but:
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
// ----------------------------------------------------------------------------
// now include the declaration of wxWindow class
// ----------------------------------------------------------------------------

// include the declaration of the platform-specific class
#if defined(__WXMSW__)
    #ifdef __WXUNIVERSAL__
        #define wxWindowNative wxWindowMSW
    #else // !wxUniv
        #define wxWindowMSW wxWindow
    #endif // wxUniv/!wxUniv
    #include "wx/msw/window.h"
...
#elif defined(__WXX11__)
    #ifdef __WXUNIVERSAL__
        #define wxWindowNative wxWindowX11
    #else // !wxUniv
        #define wxWindowX11 wxWindow
    #endif // wxUniv
    #include "wx/x11/window.h"
...
#elif defined(__WXMAC__)
    #ifdef __WXUNIVERSAL__
        #define wxWindowNative wxWindowMac
    #else // !wxUniv
        #define wxWindowMac wxWindow
    #endif // wxUniv
    #include "wx/osx/window.h"
    #define wxHAVE_DPI_INDEPENDENT_PIXELS 
Last edited on
Thank you all for your input!

I like the option of having the implementation separated into several (per-platform) files, even for such a small function.

We use CMake so including either of files (depending on the platform) is easy and straightforward.
Topic archived. No new replies allowed.