Compiling GLFW with wineg++

I’d like to take a look at DirectX graphics programming but as an avid Linux user I don’t want to install Windows just for that. Therefore I’ve been experimenting with using winegcc in order to compile wine executables as described here. Once can actually compile and run windows applications with these tools, like this:

wineg++ test.cpp
WINEARCH=win64 wine a.out.so

I don’t want to focus too much on handling the window itself with all that LRESULT CALLBACK weirdness, so I’m researching if it’s possible to use GLFW for that. I tried to compile following code:

// https://github.com/glfw/glfw/issues/1755

#include <windows.h>
#include <GLFW/glfw3.h>

#define GLFW_EXPOSE_NATIVE_WIN32
#include <GLFW/glfw3native.h>

int WINAPI WinMain(
    HINSTANCE hInstance,
    HINSTANCE hPrevInstance,
    LPSTR lpCmdLine,
    int nCmdShow
) {
    GLFWwindow* window;

    if(!glfwInit())
        return -1;

    window = glfwCreateWindow(640, 480, "Hello World", NULL, NULL);
    if(!window) {
        glfwTerminate();
        return -1;
    }
    glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);

    HWND hWnd = glfwGetWin32Window(window);

    while (!glfwWindowShouldClose(window)) {
        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    glfwTerminate();
    return 0;
}

And this doesn’t work because I don’t have Window native code present in Linux libraries:

$ wineg++ foo.cpp  -o foo -lglfw
/usr/bin/ld: foo-7t6mJJ.o: in function `WinMain':
foo.cpp:(.text+0xc7): undefined reference to `glfwGetWin32Window'
collect2: error: ld returned 1 exit status
winegcc: g++ failed

I can’t just use precompiled Windows binaries because architecture doesn’t match:

/usr/bin/ld: relocatable linking with relocations from format pe-x86-64 (./libglfw3.a(context.c.obj)) to format elf64-x86-64 (foo.icCN0D.o) is not supported
winebuild: /usr/bin/ld failed with status 1
winegcc: /usr/lib/wine/winebuild failed

So tried compiling GLFW in Linux but pretending that it’s a Windows. In CMakeList.txt I’ve added:

set(WIN32 TRUE)
set(UNIX FALSE)

In order to bypass OS checking. I generate configuration like this:

$ cmake .. -D CMAKE_C_COMPILER=winegcc
-- The C compiler identification is GNU 11.0.1
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - failed
-- Check for working C compiler: /usr/bin/winegcc
-- Check for working C compiler: /usr/bin/winegcc - works
-- Detecting C compile features
-- Detecting C compile features - done
CMake Warning (dev) at /usr/share/cmake-3.18/Modules/GNUInstallDirs.cmake:225 (message):
  Unable to determine default CMAKE_INSTALL_LIBDIR directory because no
  target architecture is known.  Please enable at least one language before
  including GNUInstallDirs.
Call Stack (most recent call first):
  CMakeLists.txt:34 (include)
This warning is for project developers.  Use -Wno-dev to suppress it.

-- Looking for pthread.h
-- Looking for pthread.h - found
-- Performing Test CMAKE_HAVE_LIBC_PTHREAD
-- Performing Test CMAKE_HAVE_LIBC_PTHREAD - Failed
-- Looking for pthread_create in pthreads
-- Looking for pthread_create in pthreads - not found
-- Looking for pthread_create in pthread
-- Looking for pthread_create in pthread - found
-- Found Threads: TRUE  
-- Could NOT find Doxygen (missing: DOXYGEN_EXECUTABLE) 
-- Including Win32 support
-- Configuring done
-- Generating done
-- Build files have been written to: /tmp/glfw/build

This compiles but has linkage errors:

$ make
Scanning dependencies of target glfw
[  1%] Building C object src/CMakeFiles/glfw.dir/context.c.o
[  2%] Building C object src/CMakeFiles/glfw.dir/init.c.o
[  3%] Building C object src/CMakeFiles/glfw.dir/input.c.o
[  4%] Building C object src/CMakeFiles/glfw.dir/monitor.c.o
[  5%] Building C object src/CMakeFiles/glfw.dir/platform.c.o
[  6%] Building C object src/CMakeFiles/glfw.dir/vulkan.c.o
[  7%] Building C object src/CMakeFiles/glfw.dir/window.c.o
[  8%] Building C object src/CMakeFiles/glfw.dir/egl_context.c.o
[  9%] Building C object src/CMakeFiles/glfw.dir/osmesa_context.c.o
[ 10%] Building C object src/CMakeFiles/glfw.dir/null_init.c.o
[ 11%] Building C object src/CMakeFiles/glfw.dir/null_monitor.c.o
[ 12%] Building C object src/CMakeFiles/glfw.dir/null_window.c.o
[ 13%] Building C object src/CMakeFiles/glfw.dir/null_joystick.c.o
[ 14%] Building C object src/CMakeFiles/glfw.dir/win32_module.c.o
[ 15%] Building C object src/CMakeFiles/glfw.dir/win32_time.c.o
[ 16%] Building C object src/CMakeFiles/glfw.dir/win32_thread.c.o
[ 17%] Building C object src/CMakeFiles/glfw.dir/win32_init.c.o
[ 18%] Building C object src/CMakeFiles/glfw.dir/win32_joystick.c.o
[ 20%] Building C object src/CMakeFiles/glfw.dir/win32_monitor.c.o
[ 21%] Building C object src/CMakeFiles/glfw.dir/win32_window.c.o
/tmp/glfw/src/win32_window.c: In function ‘enableRawMouseMotion’:
/tmp/glfw/src/win32_window.c:275:34: warning: passing argument 1 of ‘RegisterRawInputDevices’ discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers]
  275 |     if (!RegisterRawInputDevices(&rid, 1, sizeof(rid)))
      |                                  ^~~~
In file included from /usr/include/wine/wine/windows/windows.h:40,
                 from /tmp/glfw/src/win32_platform.h:68,
                 from /tmp/glfw/src/platform.h:31,
                 from /tmp/glfw/src/internal.h:331,
                 from /tmp/glfw/src/win32_window.c:30:
/usr/include/wine/wine/windows/winuser.h:4111:55: note: expected ‘PRAWINPUTDEVICE’ {aka ‘struct tagRAWINPUTDEVICE *’} but argument is of type ‘const RAWINPUTDEVICE *’ {aka ‘const struct tagRAWINPUTDEVICE *’}
 4111 | WINUSERAPI BOOL        WINAPI RegisterRawInputDevices(PRAWINPUTDEVICE,UINT,UINT);
      |                                                       ^~~~~~~~~~~~~~~
/tmp/glfw/src/win32_window.c: In function ‘disableRawMouseMotion’:
/tmp/glfw/src/win32_window.c:288:34: warning: passing argument 1 of ‘RegisterRawInputDevices’ discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers]
  288 |     if (!RegisterRawInputDevices(&rid, 1, sizeof(rid)))
      |                                  ^~~~
In file included from /usr/include/wine/wine/windows/windows.h:40,
                 from /tmp/glfw/src/win32_platform.h:68,
                 from /tmp/glfw/src/platform.h:31,
                 from /tmp/glfw/src/internal.h:331,
                 from /tmp/glfw/src/win32_window.c:30:
/usr/include/wine/wine/windows/winuser.h:4111:55: note: expected ‘PRAWINPUTDEVICE’ {aka ‘struct tagRAWINPUTDEVICE *’} but argument is of type ‘const RAWINPUTDEVICE *’ {aka ‘const struct tagRAWINPUTDEVICE *’}
 4111 | WINUSERAPI BOOL        WINAPI RegisterRawInputDevices(PRAWINPUTDEVICE,UINT,UINT);
      |                                                       ^~~~~~~~~~~~~~~
[ 22%] Building C object src/CMakeFiles/glfw.dir/wgl_context.c.o
[ 23%] Linking C static library libglfw3.a
[ 23%] Built target glfw
Scanning dependencies of target boing
[ 24%] Building C object examples/CMakeFiles/boing.dir/boing.c.o
[ 25%] Linking C executable boing
/usr/bin/ld: ../src/libglfw3.a(win32_monitor.c.o): in function `createMonitor':
win32_monitor.c:(.text+0x24f): undefined reference to `CreateDCW'
/usr/bin/ld: win32_monitor.c:(.text+0x272): undefined reference to `GetDeviceCaps'
/usr/bin/ld: win32_monitor.c:(.text+0x294): undefined reference to `GetDeviceCaps'
/usr/bin/ld: win32_monitor.c:(.text+0x2b1): undefined reference to `DeleteDC'
/usr/bin/ld: ../src/libglfw3.a(win32_monitor.c.o): in function `_glfwGetGammaRampWin32':
win32_monitor.c:(.text+0x138b): undefined reference to `CreateDCW'
/usr/bin/ld: win32_monitor.c:(.text+0x13b0): undefined reference to `GetDeviceGammaRamp'
/usr/bin/ld: win32_monitor.c:(.text+0x13c7): undefined reference to `DeleteDC'
/usr/bin/ld: ../src/libglfw3.a(win32_monitor.c.o): in function `_glfwSetGammaRampWin32':
win32_monitor.c:(.text+0x156a): undefined reference to `CreateDCW'
/usr/bin/ld: win32_monitor.c:(.text+0x158f): undefined reference to `SetDeviceGammaRamp'
/usr/bin/ld: win32_monitor.c:(.text+0x15a6): undefined reference to `DeleteDC'
/usr/bin/ld: ../src/libglfw3.a(win32_window.c.o): in function `createIcon':
win32_window.c:(.text+0x24f): undefined reference to `CreateDIBSection'
/usr/bin/ld: win32_window.c:(.text+0x2ce): undefined reference to `CreateBitmap'
/usr/bin/ld: win32_window.c:(.text+0x30a): undefined reference to `DeleteObject'
/usr/bin/ld: win32_window.c:(.text+0x44c): undefined reference to `DeleteObject'
/usr/bin/ld: win32_window.c:(.text+0x463): undefined reference to `DeleteObject'
/usr/bin/ld: ../src/libglfw3.a(win32_window.c.o): in function `updateFramebufferTransparency':
win32_window.c:(.text+0xde2): undefined reference to `CreateRectRgn'
/usr/bin/ld: win32_window.c:(.text+0xe52): undefined reference to `DeleteObject'
/usr/bin/ld: ../src/libglfw3.a(win32_window.c.o): in function `windowProc':
win32_window.c:(.text+0x2a0e): undefined reference to `DragQueryFileW'
/usr/bin/ld: win32_window.c:(.text+0x2a46): undefined reference to `DragQueryPoint'
/usr/bin/ld: win32_window.c:(.text+0x2aaf): undefined reference to `DragQueryFileW'
/usr/bin/ld: win32_window.c:(.text+0x2aff): undefined reference to `DragQueryFileW'
/usr/bin/ld: win32_window.c:(.text+0x2bd6): undefined reference to `DragFinish'
/usr/bin/ld: ../src/libglfw3.a(win32_window.c.o): in function `createNativeWindow':
win32_window.c:(.text+0x31ae): undefined reference to `DragAcceptFiles'
/usr/bin/ld: ../src/libglfw3.a(wgl_context.c.o): in function `choosePixelFormatWGL':
wgl_context.c:(.text+0x990): undefined reference to `DescribePixelFormat'
/usr/bin/ld: wgl_context.c:(.text+0xead): undefined reference to `DescribePixelFormat'
/usr/bin/ld: ../src/libglfw3.a(wgl_context.c.o): in function `swapBuffersWGL':
wgl_context.c:(.text+0x129f): undefined reference to `SwapBuffers'
/usr/bin/ld: ../src/libglfw3.a(wgl_context.c.o): in function `_glfwInitWGL':
wgl_context.c:(.text+0x16e6): undefined reference to `ChoosePixelFormat'
/usr/bin/ld: wgl_context.c:(.text+0x1703): undefined reference to `SetPixelFormat'
/usr/bin/ld: ../src/libglfw3.a(wgl_context.c.o): in function `_glfwCreateContextWGL':
wgl_context.c:(.text+0x1c33): undefined reference to `DescribePixelFormat'
/usr/bin/ld: wgl_context.c:(.text+0x1c83): undefined reference to `SetPixelFormat'
/usr/bin/ld: boing.exe.so: hidden symbol `CreateDIBSection' isn't defined
/usr/bin/ld: final link failed: bad value
collect2: error: ld returned 1 exit status
winegcc: gcc failed
make[2]: *** [examples/CMakeFiles/boing.dir/build.make:104: examples/boing] Error 2
make[1]: *** [CMakeFiles/Makefile2:296: examples/CMakeFiles/boing.dir/all]

So apparently these errors means I’m lacking libgdi32 library. I’ve found it is supposed to be added here in src/CMakeList.txt:

if (GLFW_BUILD_WIN32)
    list(APPEND glfw_PKG_LIBS "-lgdi32")
endif()

But this doesn’t seem to do the trick.

I know I’m kind of trying to force a square peg in a round hole here, but can I actually compile the library as a windows one using winegcc on Linux?

If you want to develop Windows binaries on Linux then my recommendation to you would be to use mingw compiler & runtime instead. It works way simpler and more reliable. And it will produce native windows binaries that you can run under wine.

Neat, I didn’t know MinGW can be also used in Linux, I’ve thought it is a Windows only project. After installing x86_64-w64-mingw32-gcc in the system I used

cmake .. -D CMAKE_TOOLCHAIN_FILE=../CMake/x86_64-w64-mingw32.cmake

And it looks like it picked the right version.