1.问题概述

        在使用SDL2库进行C/C++图形界面开发时,相信开发者会遇到在包含SDL.h后,链接器找不到main函数的问题,在Windows上,报错信息通常类似:

Undefined reference to 'WinMain'

2.原因分析

        这是由于SDL.h中定义了main宏,它将main替换为SDL_main

#define main SDL_main

        显然,若不进行其他处理,链接器会拒绝链接一个没有main函数的项目。SDL既然选择这样做,那么必然有其原因,我们当然可以直接

#undef main

        但是,SDL作为跨平台但和操作系统交互的底层库,内置了平台特定的初始化和清理逻辑,这些逻辑包括但不限于:

                1.统一处理命令行参数,如Windows下的Unicode到UTF-8的编码转换

                2.调用平台特定的初始化函数,如Windows的窗口子系统,Android JNI绑定等

        直接#undef main,将会失去这些自动初始化的调用,并失去对应的资源清理环节。虽然通常情况下,这样做不会带来问题,但是可能会埋下隐患,在跨平台迁移时造成一些意料之外的错误。

3.解决方案

        3.1:不管

                这也是大多数SDL教程所做的!如上所示,直接在#include <SDL.h>后取消定义main宏

#include <SDL.h>

#undef main
int main() {
    // ...
    return 0;
}

        另一些教程也会这样做:

#define SDL_MAIN_HANDLED 1

        这让SDL知道,应用程序已经自行处理了main函数。但是通常情况下,我们需要使用SDL的main而不是自定义它。

        3.2:正确链接SDL_main

                如果你查看SDL_main.h,那么你可能会被SDL所“误导”:

SDL3/SDL_main.h

/*
 * Redefine main() if necessary so that it is called by SDL.
 *
 * In order to make this consistent on all platforms, the application's main()
 * should look like this:
 *
 * ```c
 * #include <SDL3/SDL.h>
 * #include <SDL3/SDL_main.h>
 *
 * int main(int argc, char *argv[])
 * {
 * }
 * ```
 */

SDL2/SDL_main.h

/**
 *  \file SDL_main.h
 *
 *  The application's main() function must be called with C linkage,
 *  and should be declared like this:
 *  ```c
 *  #ifdef __cplusplus
 *  extern "C"
 *  #endif
 *  int main(int argc, char *argv[])
 *  {
 *  }
 *  ```
 */

                遗憾的是,通常情况下,官方给出的做法并不有效,链接器仍然抱怨找不到main函数。

                我们可以这样想:既然SDL将我们的main搞掉了,那么它必然会提供自己的main给操作系统,这个main是平台特定的,如在Windows上为WinMain,在Linux上就是main。

                如果你使用CMake,那么在链接SDL库时,IDE通常会给出几个候选:SDL2::SDL_main, SDL2::SDL2, SDL2::SDL_test...

                其中,SDL2::SDL2是SDL的核心库,任何一个使用了SDL库的程序必然已经链接了它,SDL_test是测试库,和main函数无关。那么,只有看起来有点多余的SDL_main可能有用。

                实际上,SDL_main库中就包含了SDL的平台特定main函数实现,只是以动态库的形式存在。如果直接下载SDL的Release,那么不能直接跳转到其实现。

                于是问题变得简单了,我们只需要尝试链接SDL_main库,以CMake为例:

add_executable(${PROJECT_NAME}
    ...
)

#链接SDL库
target_link_libraries(${PROJECT_NAME} PUBLIC SDL2::SDL2main SDL2::SDL2)

                必须将SDL2main写在SDL2之前,即先链接SDL2main库!

                然后,在写有main函数的源文件中:

#include <SDL.h>

int main(int argc, char * argv[]) {
    // ...
    return 0;
}

                此时如果不包含SDL.h,或者取消main宏的定义,链接器反而会报错,因为SDLmain中的main函数已经被链接器识别。

                在此情况下,建议按照头文件中的提示,写上main函数的两个形参,无论用不用。

                这样,我们就正确地使用了SDL自带的,平台特定的初始化和清理逻辑,而不造成任何不必要的损失。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐