V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX 提问指南
jim9606
V2EX  ›  问与答

关于 Windows 下 C/C++ Runtime 兼容性的问题

  •  
  •   jim9606 · 2022-03-08 05:32:01 +08:00 · 2210 次点击
    这是一个创建于 984 天前的主题,其中的信息可能已经有所发展或是发生改变。

    目前 Windows 有两种 C 运行时:所有 NT 版本都有的 MSVCRT(msvcrt.dll)和最低支持 Vista 的 UCRT(ucrtbase.dll)。在使用 MSVC 时,还会引入主版本关联的 VC++ Runtime(msvcpXXX.dll,vcruntimeXXX.dll)现在有几个问题:

    1. UCRT 会有更好的性能吗?无需支持 XP 的话选择链接 UCRT 有什么缺点吗?
    2. 使用 MSVC 或者 MSYS2 ( mingw64/ucrt64/clang64)编译的程序必须链接以上两个 CRT 之一,据说是因为这部分跟系统有较深耦合无法避免,那为什么 golang 可以做到不依赖 CRT ?
    3. 在保证导出 API 函数都是 C 风格时,可以安全混用两种 CRT 吗?例如链接 UCRT 的 exe 加载链接 MSVCRT 的 dll ,或者链接 MSVCRT 的 exe 加载链接 UCRT 的 dll ?
    4. 如果我希望给别人用的 dll 有较好的兼容性,在保证导出 API 函数都是 C 风格时,可以将 VC++ Runtime 静态链接进 dll 吗?如果可以,怎么配置?
    5. 在保证导出 API 函数都是 C 风格时,可以在一个 exe 中安全混用依赖不同主版本 VC++ Runtime 的 dll 吗?
    18 条回复    2022-03-09 02:28:30 +08:00
    hackpro
        1
    hackpro  
       2022-03-08 05:36:12 +08:00 via iPhone
    如果希望兼容性的话 考虑静态链接吗
    thedrwu
        2
    thedrwu  
       2022-03-08 05:59:26 +08:00 via Android   ❤️ 1
    1.没用过不好回答
    2.可以不链接任何 crt ,用纯 Windows api 。尤其是为了把体积缩小到极致的时候。golang 怎么做不清楚。
    3.申请释放内存用同一个 crt
    4.同上,即使静态链接
    5.同上

    欢迎补充
    thedrwu
        3
    thedrwu  
       2022-03-08 06:16:22 +08:00 via Android
    补充上一楼。

    以前为了兼容性, 小程序我链的都是 msvcrt.dll 。其中的 bug 都成 feature 将错就错了。后来随着宽带普及,软盘淘汰,不需要榨干最后一点体积。再后来 visual studio 也免费了,根据第三方库下载安装对应版本的 vc 编译不是个事了。

    既然都跟 golang 比了,不在乎这点体积。
    ysc3839
        5
    ysc3839  
       2022-03-08 09:15:40 +08:00 via Android   ❤️ 1
    1.理论上是的,因为 msvcrt 为了兼容性可能有些妥协。ucrt 可能更加符合新的 C 标准
    2.否的,只是 mingw 项目不开发 libc ,直接用了微软的 libc ,msys2/cygwin(有 POSIX 兼容层那个模式)就是使用自己开发的 libc ,不使用系统的
    3.一直可以,许多系统 DLL 就是依赖 msvcrt 的,但一直都不可以互操作
    4.可以,msvc 是用 /MT /MTd 这两个选项 https://docs.microsoft.com/en-us/cpp/build/reference/md-mt-ld-use-run-time-library
    5.一直可以,同样也是不可用互操作
    littlefishcc
        6
    littlefishcc  
       2022-03-08 09:19:28 +08:00
    我会回答第五条:
    依赖不同版本,可以用 GetProcAddress 指向不同版本的运行库。
    3dwelcome
        7
    3dwelcome  
       2022-03-08 09:48:25 +08:00
    “如果我希望给别人用的 dll 有较好的兼容性,在保证导出 API 函数都是 C 风格时,可以将 VC++ Runtime 静态链接进 dll 吗?如果可以,怎么配置?”

    楼主没搞懂,使用动态 VC Runtime DLL 这点,对于程序开发很重要的。

    你用静态 Runtime 库的话,由 malloc 分配的内存,是没办法在各个模块之间共享的。

    要共享,就只能让所有模块,都使用同一个 VC Runtime DLL 。换句话说,你写代码肯定不是为了兼容性去用静态链接库.
    ysc3839
        8
    ysc3839  
       2022-03-08 10:02:14 +08:00 via Android   ❤️ 1
    @3dwelcome 但是没谁规定一定要用 libc 提供的 malloc 来分配内存,Windows 有很多可跨模块的分配器,比如 process heap allocator 和 COM task allocator ,甚至可以不传递所有权。
    lonewolfakela
        9
    lonewolfakela  
       2022-03-08 16:17:16 +08:00
    4. 会导致使用你的 dll 的程序也必须静态链接 VC++ Runtime ;然而如果那个程序本身用的 Runtime 和你版本不一样的话,全部静态链接到一个 exe 里之后,鬼知道会发生什么后果……
    ysc3839
        10
    ysc3839  
       2022-03-08 16:46:45 +08:00 via Android
    @lonewolfakela 不一定的,不涉及 malloc 以及 C++的互操作的话就不需要。比如说调用方传数据进来的情况,可以把数据拷一份,或者让调用方提供个回调函数,在调用回调函数前都需要保持数据有效。传数据出去的情况,可以让调用方提供缓冲区。以上两种情况还可以都用系统提供的进程级的内存分配器。
    lonewolfakela
        11
    lonewolfakela  
       2022-03-08 19:24:16 +08:00
    @ysc3839 可是,微软的 C++链接器会直接拒绝混用两种模式的情况啊……
    https://stackoverflow.com/questions/3469841/mixing-code-compiled-with-mt-and-md
    ysc3839
        12
    ysc3839  
       2022-03-08 20:12:26 +08:00 via Android
    @lonewolfakela 你发的这是静态链接时的情况呀。楼主说的是自己编译一个 DLL 提供给别人用,就没有涉及静态链接。
    lonewolfakela
        13
    lonewolfakela  
       2022-03-08 20:51:31 +08:00
    @ysc3839 哦抱歉,是我搞混了。
    jim9606
        14
    jim9606  
    OP
       2022-03-08 23:12:46 +08:00
    @3dwelcome
    @lonewolfakela
    关于是否混用 VC++ Runtime 的问题,我的理解(从 Linux 迁移过来的理解,可能有错)是如果使用共享 VC++ Runtime dll 会导致 VC++导出的符号对所有 dll/exe 可见,不同主版本的 VC++符号会被互相覆盖,会导致某个链接 VC12 的调用实际上调用了 VC14 的符号,从而产生问题。如果将 VC++静态链接进 dll ,这些 VC++符号就是对其他 dll/exe 不可见的私有符号。我用 VS2022 测试了下,使用 /MT /MTd 时 dumpbin 显示只依赖 kernel32.dll ,所以应该是把 VC14 和 UCRT 都静态链接进去了。那么在满足 ABI 稳定和谁分配谁释放的前提下,这个 dll 是不是可以安全地给 MSVC13 编译?

    另外,这种混用 Runtime 的方法是不是在 Linux 下不可能实现?例如 musl 的程序加载静态链接 glibc 的 so ?
    3dwelcome
        15
    3dwelcome  
       2022-03-09 00:21:22 +08:00
    @jim9606 Linux 的 so ,和 windows 的 dll 设计不太一样。

    dll 就是一个完整的闭环体系,缺函数没办法编译通过。

    而 so 编译时缺胳膊少腿都没问题,只要 elf 符号完整,最后代码就和变形金刚一样,能组合在一起完整运行。

    dll 也很少会覆盖符号,这个情况只在 linux 见过,windows 上我是没见过。
    lonewolfakela
        16
    lonewolfakela  
       2022-03-09 00:50:18 +08:00   ❤️ 1
    不过话又说回来,真的要想稳定性兼容性都比较好、少出各种诡异问题的话,我觉得还是给比如 MSVC10 、11 、12 、14 各自编译一份比较好——其实也不太多,只需要 4 份就能从 VS2010 一直支持到 VS2022 了。或者如果可以选择不支持那么老的版本的 VS 的话,直接只针对 MSVC14.0 编译一份动态链接 Runtime 的 DLL ,就能支持从 VS2015 到 VS2022 ,在很多时候已经挺够用的了。
    ysc3839
        17
    ysc3839  
       2022-03-09 01:38:50 +08:00 via Android   ❤️ 1
    @jim9606 不会覆盖,不同版本的 vcrt dll 可以在同一进程中共存。
    @3dwelcome 你是指链接时即使 so 里没有某个符号,也能通过吗?这个好像跟操作系统关系不大,是链接器的逻辑,msvc 的链接器需要你明确告诉它某个符号对应某个 dll 中的某个函数。实际上即使没有对应 lib ,也可以自己生成一个。
    jim9606
        18
    jim9606  
    OP
       2022-03-09 02:28:30 +08:00
    @lonewolfakela
    那当然是各自编译一份最好,这个毋庸置疑。现在 UCRT 和 VC++14 提供向后兼容是不错,但谁知道以后 UCRT 会不会变成 MSVCRT 那样的包袱呢。
    其实还有一点,在 Windows 上升级 VC++ Redist 是相对简单安全的事情,实在不行就费点内存磁盘用静态链接 /本地部署,也很安全。尽管 glibc 、libstdc++、libgcc 在近几年也提供类似的兼容性,但在 Linux 发行版上升级这些库是件挺有风险的事,反正我是没胆做这种尝试。特别是 glibc 旧版兼容的问题还不好通过静态链接来解决。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1125 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 23ms · UTC 18:51 · PVG 02:51 · LAX 10:51 · JFK 13:51
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.