跨平台代码的3种组织方式

一、起源

在前一篇文章中,我分享了一个跨平台头文件的示例,该示例在 Windows 平台上更为重要,因为它需要处理库函数的导入和导出声明(dllexport、dllimport)。基于这个头文件,我们可以进一步扩展,以实现更细粒度的控制,比如对编译器和其版本的判断。

在源代码中,我们同样会遇到一些跨平台的问题。不同平台上的相同功能,实现方式可能有所不同。那么,如何有效地组织这些平台相关的代码呢?本文将探讨这个问题。

PS:文末将提供一个简单的跨平台构建代码示例。

二、问题引入

假设我们要编写一个库,需要实现一个获取系统时间戳的函数。作为库的作者,你决定提供以下API函数:

我们的任务是在函数实现中,通过不同平台下的C库或系统调用来计算系统当前的时间戳。

在Linux平台下,可以通过以下代码实现:

struct timeval tv;gettimeofday(&tv, NULL);return tv.tv_sec * 1000 + tv.tv_usec / 1000;

在Windows平台下,可以通过以下代码实现:

struct timeb tp;ftime(&tp);return tp.time * 1000 + tp.millitm;

那么问题来了:如何将这两段平台相关的代码组织在一起?下面将介绍三种不同的组织方式,这些方式没有优劣之分,适合每个人和团队的不同习惯。

此外,这个示例中只有一个函数,而且较短。如果有许多跨平台的函数,且都很长,你的选择可能会有所不同。

三、三个解决方案

方案1

直接在接口函数中,通过平台宏定义来区分不同平台。

平台宏定义(T_LINUX, T_WINDOWS)是在前一篇文章中介绍的,通过操作系统和编译器来判断当前平台,并定义统一的平台宏供我们使用:

代码组织方式如下:

int64 t_get_timestamp() {    int64 ts = -1;    #if defined(T_LINUX)        struct timeval tv;        gettimeofday(&tv, NULL);        ts = tv.tv_sec * 1000 + tv.tv_usec / 1000;    #elif defined(T_WINDOWS)        struct timeb tp;        ftime(&tp);        ts = tp.time;        ts = ts * 1000 + tp.millitm;    #endif    return ts;}

这种方式将所有平台代码放在API函数中,通过平台宏定义进行条件编译。由于代码较短,看起来还不错。

方案2

将不同平台的实现代码放在独立的文件中,然后通过#include预处理符号,在API函数中引入平台相关的代码。

增加两个文件:

(1) t_time_linux.c

代码小浣熊 代码小浣熊

代码小浣熊是基于商汤大语言模型的软件智能研发助手,覆盖软件需求分析、架构设计、代码编写、软件测试等环节

代码小浣熊 51 查看详情 代码小浣熊

#include "t_time.h"#include 

int64 t_get_timestamp() {int64 ts = -1;struct timeval tv;gettimeofday(&tv, NULL);ts = tv.tv_sec * 1000 + tv.tv_usec / 1000;return ts;}

(2) t_time_windows.c

#include "t_time.h"

include

include

int64 t_get_timestamp() {int64 ts = -1;struct timeb tp;ftime(&tp);ts = tp.time;ts = ts * 1000 + tp.millitm;return ts;}

(3) t_time.c

这个文件不做任何事情,仅用于include其他代码。

#include "t_time.h"

if defined(T_LINUX)

#include "t_time_linux.c"

elif defined(T_WINDOWS)

#include "t_time_windows.c"

else

int64 t_get_timestamp() {    return -1;}

endif

有些人可能不喜欢这种组织方式,因为通常我们会include头文件(.h),而这里通过平台宏定义include不同的源文件(.c),感觉有些怪异。

实际上,一些开源库也是这样做的,例如:

跨平台代码的3种组织方式

方案3

在方案2中,我们是在源代码中填入不同平台的实现代码。

实际上,我们可以换一种思路,既然已经根据平台的不同放在了不同的文件中,那么可以通过让不同的源文件加入到编译过程中来实现。

我们使用cmake工具来构建测试代码,因此可以编辑CMakelists.txt文件,来控制参与编译的源文件。

CMakelists.txt文件部分内容:

# 设置平台变量

if (CMAKE_SYSTEM_NAME MATCHES "Linux")set(PLATFORM linux)elseif (CMAKE_SYSTEM_NAME MATCHES "Windows")set(PLATFORM windows)endif()

根据平台变量,来编译不同的源文件

set(LIBSRC ttime${PLATFORM}.c)

这种组织方式让代码看起来更“干净”。同样,我们也可以看到一些开源库也是这样做的:

跨平台代码的3种组织方式跨平台代码的3种组织方式

四、One More Thing

由于文章篇幅原因,上述仅展示了代码片段。

我编写了一个最简单的demo,使用cmake来构建跨平台的动态库、静态库和可执行程序。这个demo的主要目的是作为一个外壳,用于测试文章中的代码。

在Linux平台下,可以通过cmake指令手动编译;在Windows平台下,可以通过CLion集成开发环境直接编译和执行,也可以通过cmake工具直接生成VS2017/2019解决方案。

这个demo已上传至gitee仓库,有兴趣的小伙伴可以通过公众号dg36获取克隆地址。

以上就是跨平台代码的3种组织方式的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/593989.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2025年11月10日 17:17:50
下一篇 2025年11月10日 17:18:43

相关推荐

  • Golang如何在云原生架构中实现高可用

    Go凭借高性能和并发优势成为云原生高可用服务的首选语言,需结合无状态设计、健康检查、弹性通信与可观测性实现稳定。1. 服务应无状态并快速启动,外部化会话与配置;2. 暴露/healthz接口并监听SIGTERM实现优雅关闭;3. 使用gRPC或go-kit配合重试与熔断机制提升容错;4. 通过zap…

    2025年12月16日
    000
  • Go语言中利用go install命令一次性构建多个二进制文件

    本文详细介绍了如何使用go语言的`go install`命令,通过路径通配符`…`实现一次性构建位于同一根目录下多个`main`包的二进制文件。这种方法无需借助外部构建工具,简化了多应用项目的构建流程,适用于`go get`、`go build`等其他go命令,显著提升开发效率。 Go语…

    2025年12月16日
    000
  • Go语言中模拟动态方法调用:实现Ruby send 等效功能

    go语言不内置类似ruby `send` 的动态方法调用机制。本文将介绍两种实现方式:一是利用 `map[string]func()` 注册函数并按名称调用,适用于预定义函数集合;二是使用 `reflect` 包进行运行时反射,以实现更通用的动态方法调用。文章将通过示例代码和注意事项,指导读者在go…

    2025年12月16日
    000
  • 高效管理Go多二进制文件安装:使用go install root/…模式

    本文详细介绍了如何在Go语言项目中,利用`go install root/…`命令一次性构建并安装位于指定根目录下所有`main`包对应的多个可执行文件。通过引入`…`通配符模式,开发者可以避免为每个二进制文件单独执行`go install`,从而简化构建流程,提高开发效率。…

    2025年12月16日
    000
  • 在Geany中配置Go语言运行环境:解决‘go: not found’错误

    本教程旨在解决在geany编辑器中运行go语言代码时遇到的“go: not found”错误。核心解决方案是在geany的构建命令设置中,将go可执行文件的完整路径配置到执行命令中,例如使用`/path/to/go/bin/go run “%f”`,确保geany能正确找到并…

    2025年12月16日
    000
  • Geany集成Go开发环境:运行Go代码的完整指南

    本教程详细介绍了如何在geany集成开发环境中正确配置go语言程序的运行环境。当遇到“go: not found”错误时,核心解决方案是修改geany的构建命令设置,指定go可执行文件的完整路径来执行go源代码,从而确保程序能够顺利编译和运行。 在使用Geany进行Go语言开发时,开发者可能会遇到一…

    2025年12月16日
    000
  • 在Go中稳健处理 text/template 文件路径的教程

    本文旨在解决go语言中 `text/template` 包在加载模板文件时遇到的路径问题,特别是当 `go test` 从不同目录执行时导致的“文件未找到”错误。核心解决方案包括理解当前工作目录(cwd)对相对路径解析的影响,以及如何通过统一项目执行目录、利用 `os.getwd()` 和 `fil…

    2025年12月16日
    000
  • Geany中配置Go语言开发环境:解决“go: not found”问题

    本教程旨在解决在geany编辑器中运行go程序时遇到的“go: not found”错误。核心解决方案是在geany的“构建命令”设置中,为“执行”命令指定go可执行文件的完整路径,例如`/path/to/go/bin/go run “%f”`,确保geany能够正确找到并执…

    2025年12月16日
    000
  • 在Geany中配置和运行Go语言代码

    本文详细介绍了如何在geany集成开发环境中配置go语言的运行命令,以解决常见的“go: not found”错误。通过指定go可执行文件的完整路径,用户可以顺利在geany中编译并执行go代码,从而提升开发效率。 在Geany集成开发环境中运行Go语言代码时,开发者可能会遇到“go: not fo…

    2025年12月16日
    000
  • Go与Dart跨平台数据传输:Protocol Buffers序列化实践

    本文详细介绍了如何在go后端与dart前端之间高效、类型安全地传输数据。教程涵盖了protocol buffers的安装配置、`.proto`消息定义、go语言中的数据序列化,以及dart语言中的数据反序列化过程,并提供了完整的代码示例,旨在帮助开发者实现基于http的跨语言结构化数据通信。 Go与…

    2025年12月16日
    000
  • 使用Protocol Buffers在Go与Dart之间高效传输结构化数据

    本教程详细介绍了如何利用protocol buffers在go后端序列化结构化数据,并通过ajax将其传输至dart前端进行反序列化。文章涵盖了环境搭建、`.proto`文件定义、代码生成、go服务端的对象创建与数据序列化发送,以及dart客户端的数据接收与反序列化处理,旨在提供一套完整的跨语言数据…

    2025年12月16日
    000
  • 在Geany中配置Go语言运行环境

    本教程旨在解决在geany集成开发环境中运行go语言代码时遇到的“go: not found”错误。核心解决方案在于明确指定go可执行文件的完整路径,通过geany的“构建命令设置”功能,将执行命令修改为`/path/to/go/bin/go run “%f”`,从而确保ge…

    2025年12月16日
    000
  • 基于Protocol Buffers实现Go后端与Dart前端的数据交互

    本教程详细介绍了如何利用Protocol Buffers在Go后端对结构化数据进行序列化,并通过Ajax传输至Dart前端进行反序列化。内容涵盖了环境搭建、.proto文件定义、代码生成、Go语言中的数据封装与序列化,以及Dart语言中的数据请求与反序列化,旨在提供一套完整的跨语言数据通信解决方案,…

    2025年12月16日
    000
  • Go语言中模拟动态方法调用:实现类似Ruby send的功能

    go语言原生不支持像ruby `send`那样通过字符串动态调用函数或方法。本文将介绍两种主要实现方式:一种是利用go的函数作为一等公民的特性,通过`map[string]func()`构建函数映射表,实现高效且类型安全的动态调用;另一种是利用`reflect`包进行运行时反射,实现更灵活但开销更大…

    2025年12月16日
    000
  • 如何在Golang中减少内存垃圾生成_Golang内存垃圾生成优化方法汇总

    使用sync.Pool复用对象、减少字符串与字节切片转换、避免变量逃逸、预分配切片容量可降低GC压力。通过pprof分析内存热点,结合逃逸分析和对象复用策略,有效提升Golang程序性能。 在Golang中,频繁的内存分配会增加GC压力,导致程序停顿时间变长、性能下降。减少内存垃圾生成是提升服务吞吐…

    2025年12月16日
    000
  • 如何在Golang中避免指针循环引用

    Go的垃圾回收机制可自动处理循环引用,真正需避免的是人为延长对象生命周期。应合理使用指针,及时置nil释放引用,注意闭包捕获和全局存储导致的内存泄漏风险。 在Golang中,指针本身不会直接导致“循环引用”问题,因为Go有自动垃圾回收机制(GC),能够正确识别并回收不可达的对象,即使它们之间存在相互…

    2025年12月16日
    000
  • Golang如何使用reflect修改数组元素值_Golang reflect数组元素修改实践详解

    必须传入可寻址的指针,通过reflect.ValueOf(&arr).Elem()获取可写引用,再用Index(i)定位并Set(newVal)修改值,确保类型匹配且不越界。 在Go语言中,reflect 包提供了运行时反射能力,允许程序动态地查看和操作变量的值与类型。当我们需要通过反射修改…

    2025年12月16日
    000
  • Golang如何配置VSCode Golang插件

    首先安装Go扩展和开发工具链,再配置VSCode设置以启用格式化、代码提示和调试功能,最后通过运行示例代码验证环境是否正常。 要在 VSCode 中配置 Go 语言开发环境,需要安装并正确设置 Go 插件及相关工具。下面是如何一步步完成配置的详细说明。 安装 Go 扩展 打开 VSCode,进入扩展…

    2025年12月16日
    000
  • Golang如何使用Grafana可视化监控数据_Golang Grafana监控可视化实践详解

    Go应用通过Prometheus客户端暴露指标,Prometheus抓取后由Grafana展示。首先在Go服务中引入prometheus/client_golang,注册Counter、Gauge、Histogram等指标并启用/metrics接口;接着配置Prometheus的scrape_con…

    2025年12月16日
    000
  • 使用 Golang 进行跨数据库 JOIN 查询

    本文介绍了如何在 Golang 中使用 SQL JOIN 语句跨多个数据库进行数据查询。通过直接在 SQL 语句中指定数据库名称,可以实现跨库关联查询。同时,也讨论了另一种通过多个数据库连接分别查询数据并在应用层进行关联的方法,但推荐使用数据库服务器本身提供的 JOIN 功能以获得更好的性能。 在 …

    2025年12月16日
    000

发表回复

登录后才能评论
关注微信