翻译《The Old New Thing》- How does the linker decide whether to call WinMain or wWinMain?

How does the linker decide whether to call WinMain or wWinMain? - The Old New Thing (microsoft.com)icon-default.png?t=O83Ahttps://devblogs.microsoft.com/oldnewthing/20241004-00/?p=110338


链接器如何决定是调用 WinMain 还是 wWinMain?

        如果你没有为 Visual C++ 链接器指定 /ENTRY 选项,它会尝试猜测。文档中详细说明了这一点,但我将在这里重述,因为这将引出一个故障排除会话。

        首先,如果你指定了 /DLL 标志,那么默认的入口点是 _DllMainCRTStartup。这是简单的情况。

        如果你没有指定 /DLL 标志,那么链接器会查看你的 /SUBSYSTEM 标志。如果你请求了 /SUBSYSTEM:CONSOLE,那么它会寻找 wmainmain。如果你请求了 /SUBSYSTEM:WINDOWS,那么它会寻找 wWinMainWinMain。如果你没有指定子系统,那么它会寻找所有四个符号,并且它找到的第一个符号决定了隐含的 /ENTRY 是什么:

如果链接器找到那么入口点是
wmainwmainCRTStartup
mainmainCRTStartup
wWinMainwWinMainCRTStartup
WinMainWinMainCRTStartup

        这些神奇符号的搜索是在模块中的符号经过外部引用解析后进行的(根据链接的经典模型)。如果没有匹配,那么它使用 ANSI 入口点并希望一切顺利。

        好的,现在你可以尝试解决这位客户的问题:

我们有几个遵循相同总体结构的程序。我们的想法是将 wWinMain 函数放在一个通用的静态库中,wWinMain 函数将使用全局变量(每个程序中定义不同)来管理程序特定的业务逻辑。但当我们这样做时,我们得到了一个链接器错误,说 “无法解析的外部符号 WinMain 由 invoke_main 引用。” 如果我们使用 WinMain,它就可以工作,但我们想要支持 Unicode。

        将 wWinMain 函数移到库中意味着链接器在查找入口点时看不到它。因此它假设是 ANSI,然后在找不到 WinMain 时陷入困境。

        解决这个问题的一种方法是要求库的客户定义自己的 wWinMain,但它只是将调用转发到你的库。

int WINAPI wWinMain(HINSTANCE hinst,
    HINSTANCE hinstPrev,
    PWSTR pszCmdLine,
    int nCmdShow)
{
    return Contoso::wWinMain(hinst, hinstPrev,
            pszCmdLine, nCmdShow);
}

        现在 wWinMain 函数在项目对象文件中定义,链接器会看到它并将其作为入口点。

        拥有一个单独的函数,让客户将他们的 wWinMain 转发过来,也意味着程序可以使用多个库。

int WINAPI wWinMain(HINSTANCE hinst,
    HINSTANCE hinstPrev,
    PWSTR pszCmdLine,
    int nCmdShow)
{
    if (GetConfiguration("useContoso")) {
        return Contoso::wWinMain(hinst, hinstPrev,
                pszCmdLine, nCmdShow);
    } else {
        return Fabrikam::wWinMain(hinst, hinstPrev,
                pszCmdLine, nCmdShow);
    }
}

        如果你不关心与其他库和平共处,另一个解决方案是有一个函数,每个人都必须调用,但它不做什么,②并将该函数放在与你的 wWinMain 相同的 .obj 文件中。链接的经典模型将尝试解析该函数,并且它会从库中拉入 .obj 文件,而该 .obj 文件会顺便带上 wWinMain

        另一个可能性是,既然链接器会使用 WinMain 作为 /SUBSYSTEM:WINDOWS 模块的名称(如果找不到 wWinMain),那么你只需在你的库中放入 WinMain。你的 WinMain 可以忽略它的 ANSI 命令行并调用 GetCommandLineW() 来获取 Unicode 命令行。

但如果你不在乎与其他库和平共处,可能最简单的解决方案是添加一个

#pragma comment(linker, "/include:wWinMain")

        这会强制链接器解析 wWinMain,它会在你的库中找到它并将其对象文件添加到你的项目中,到那时,“我应该使用什么入口点?”将看到 wWinMain 存在并使用它。

¹ 当然,这是在去除死代码之前完成的:没有入口点,你拥有的只是死代码!

² 基本上,你正在使用 InitCommonControls 的技巧。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

0x0007

可不可奖励我吃只毛嘴鸡 馋😋

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值