代码之家  ›  专栏  ›  技术社区  ›  dlamotte

用C语言正确处理平台细节(unix/windows)?

  •  2
  • dlamotte  · 技术社区  · 15 年前

    这个问题故意是非常一般的,我不是一个C程序员,虽然我在这里和那里涉猎。下面的代码是故意含糊的,可能无法编译,但我希望您能明白这一点。。。

    int main(int argc, char *argv[]) {
        if (windows)
            dowindowsroutine();
        else
            dounixroutine();
        return 0;
    }
    

    然而,通过非常基本的宏来处理平台细节似乎也很糟糕,因为函数被分割成可能无法正确编译的小块(阅读答案 C #define macro for debug printing 类似的问题)。

    int main(int argc, char *argv[]) {
        #ifdef windows
        dowindowsroutine();
        #else
        dounixroutine();
        #endif
        return 0;
    }
    

    那么“正确”的方法是什么呢?是个案吗?有没有一个好方法可以让这些粗鄙的宏完全脱离函数?我记得在某个地方(可能是在内核文档或其他相关文件中)读到宏(更重要的是,复杂的宏逻辑)是针对头文件,而不是.c文件。你怎么处理这种事?

    我受够了ifdef函数内部的“意大利面代码”。。。我认为在某些情况下它可能是可以的,但是我看到的大多数代码都滥用它。

    注意:我见过一些perl-XS代码看起来像是将函数原型包装到和事物中,但是这是唯一的方法吗?这在社区里不是有点恶心吗?或者可以吗?来自perl、python、shell的“脚本化”背景,。。。我很难说。

    更新:让我更清楚地说,我试图避免的问题是,我不希望代码混乱。我希望能够确保,如果我的代码在linux中的编译时中断,那么在windows中也会在编译时中断。有了这些乱七八糟的代码,可能会破坏windows的编译,但linux不会,反之亦然。这种事有可能吗?到目前为止,最接近这一点的是ifdef整个函数,但是函数名是相同的,有没有更好的解决方案,其中只有一个接口,但操作系统特定的部分都在名称中嵌入了它们的OS名称?

    8 回复  |  直到 7 年前
        1
  •  6
  •   Nikolai Fetissov    15 年前

    我认为处理这个问题的正确方法是将代码库拆分为特定于平台的模块,并在构建时将它们组装起来(这当然需要某种平台抽象层)。这样一来,Unix代码就不会被Windows调用所占据,反之亦然。有效地将ifdef头痛转移到makefile。

        2
  •  3
  •   John Bode    15 年前

    我在职业生涯的大部分时间里不得不同时支持多个平台。这就是我过去解决问题的一般方法:抽象出依赖于平台的部分并将它们隐藏在可移植接口后面,为需要支持的每个平台创建该接口的单独实现,并使用适当的makefile魔法构建所需的内容。

    例子:

    /**
     * MyGenericModule.h
     */
     typedef myAbstractType ...;
     void genericFunction(MyAbstractType param);
     ...
    

    此接口提供应用程序代码将引用的平台无关类型和原型。

     /**
      * Windows implementation
      */
      #include "MyGenericModule.h"
      #include "WindowsSpecificHeader.h"
      ...
      void genericFunction(MyAbstractType param)
      {
        WindowsSpecificType lparam = convertToWindows(param);
        /**
         * Implement Windows-specific logic here
         */
      }
    

    这是Windows版本(WindowsModule.c)。

     /**
      * Unix implementation
      */
      #include "MyGenericModule.h"
      #include "UnixSpecificHeader.h"
      ...
      void genericFunction(MyAbstractType param)
      {
        UnixSpecificType lparam = convertToUnix(param);
        /**
         * Implement Unix-specific logic here
         */
      }
    

    这是Unix特有的版本。

    现在只需要在makefile中设置正确的规则。无论是静态地构建所有的东西,还是将特定于平台的代码构建到.dll中,都不太受可移植性的影响,而更多地取决于对所讨论的应用程序有意义的东西。我所做的大部分都是静态链接的。

    是的,这是一个屁股痛,但它的规模 许多的 #ifdef

    诀窍是正确地使用抽象类型,以便它们包含底层实现所需的所有内容,而不会给程序员带来过多的实现细节负担。是的,很难做好,而且我没有任何好的“食谱”例子来演示。

        3
  •  2
  •   Rob Pelletier    15 年前

    在C++或其他一些面向对象语言中,可以抽象平台,并使用工厂(ETC)来实例化正确的平台特定实现。

    也就是说,如果您需要/想要如上所述的ifdef,我建议您采用以下方式:

    #if WINDOWS
    void routine()
    {
        /* windows implementation here */
    }
    #else
    void routine()
    {
        /* non-windows implementation here */
    }
    #endif
    
    int main(int argc, char *argv[]) {
        routine();
        return 0;
    }
    
        4
  •  1
  •   Joshua    15 年前

    通常,代码片段变得足够大,看起来像这样:

    #if WINDOWS
    void dowindowsroutine()
    {
    }
    #else
    void dounixroutine()
    {
    }
    #endif
    
    int main(int argc, char *argv[]) {
        #if WINDOWS
            dowindowsroutine();
        #else
            dounixroutine();
        #endif
        return 0;
    }
    

    有时它会变得足够大,以至于我们可以在链接器中执行(根据makefile开关连接impwindows.o或impunix.o)。

        5
  •  1
  •   user213265 user213265    15 年前

        6
  •  1
  •   knight666    15 年前

    没有绝对的真理。

    我会这么做的。我会抽象掉这个例程,只调用一个函数来检查您所使用的平台。我把它也包括在内,因为我喜欢那里。

    #define PLATFORM_WINDOWS          0
    #define PLATFORM_LINUX          1
    #define PLATFORM_MACINTOSH      2
    #define PLATFORM_WM6              3
    #define PLATFORM_ANDROID          4
    #define PLATFORM_SOMETHINGELSE    1000
    
    #define COMPILER_VS6             1200
    #define COMPILER_VSDOTNET       1300
    #define COMPILER_VS2005       1400
    #define COMPILER_VS2008       1500
    #define COMPILER_GPP             100
    
    #ifndef PLATFORM
    
        /*
            Detect which platform this is being run on.
            Thanks go to Pixel Toaster for most of the flags.
        */
    
        #if defined(WIN32) || defined(WIN64) || defined(_WIN32) || defined(_WIN64)
            #define PLATFORM                        PLATFORM_WINDOWS
            #define PLATFORM_NAME                   "Windows"
        #elif defined(__APPLE__) || defined(__MACH__)
            #define PLATFORM                        PLATFORM_MACINTOSH
            #define PLATFORM_NAME                   "Macintosh"
        #elif defined(linux) || defined(__linux) || defined(__linux__) || defined(__CYGWIN__)
            #define PLATFORM                        PLATFORM_LINUX
            #define PLATFORM_NAME                   "Linux"
        #else
            #define PLATFORM                        PLATFORM_SOMETHINGELSE
            #define PLATFORM_NAME                   "Something Else"
        #endif
    
        /*
            How many bits is this system?
        */
    
        // Windows
        #if (defined(_WIN64) || defined(WIN64))
            #define PLATFORM_BITS                   64
        // Macintosh
        #elif (defined(__LP64__) || defined(_LP64) || defined(__ppc64__))
            #define PLATFORM_BITS                   64
        // Linux
        #elif (defined(__x86_64__) || defined(__64BIT__) || (__WORDSIZE == 64))
            #define PLATFORM_BITS                   64
        #else
            #define PLATFORM_BITS                   32
        #endif
    
        /*
            Determine which compiler was used to compile this program.
        */
    
        #ifdef _MSC_VER
            #define COMPILER_VERSION                _MSC_VER
    
            #if (COMPILER_VERSION >= 1500)
                #define COMPILER                    COMPILER_VS2008
                #define COMPILER_NAME               "Visual Studio 2008"
            #elif (COMPILER_VERSION >= 1400)
                #define COMPILER                    COMPILER_VS2005
                #define COMPILER_NAME               "Visual Studio 2005"
            #elif (COMPILER_VERSION >= 1300)
                #define COMPILER                    COMPILER_VSDOTNET
                #define COMPILER_NAME               "Visual Studio .NET"
            #elif (COMPILER_VERSION >= 1200)
                #define COMPILER                    COMPILER_VS6
                #define COMPILER_NAME               "Visual Studio 6"
            #else
                #error This version of Visual Studio is not supported.
            #endif
    #elif defined(__GNUC__)
            // TODO: get actual compiler information for G++
    
            #define COMPILER_VERSION                (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__)
    
            #define COMPILER_NAME                   "G++"
    
            #define PLATFORM                        PLATFORM_LINUX
        #endif  
    
        /*
            Compiler specific options
        */
    
        #if PLATFORM == PLATFORM_WINDOWS
            // Support for Windows 98
            #if COMPILER_VERSION >= COMPILER_VS6 && COMPILER_VERSION < COMPILER_VSDOTNET
                #pragma comment(linker, "/OPT:NOWIN98")
            #endif
    
            #if COMPILER_VERSION >= COMPILER_VSDOTNET
                #define FAST_CALL __declspec(noinline) __fastcall
            #else
                #define FAST_CALL __fastcall
            #endif
        #endif
    
    #endif
    
    #define MAIN   int main(int argc, char *argv[])
    
    void DoRoutine()
    {
    #if PLATFORM == PLATFORM_WINDOWS
        // do stuff
    #elif PLATFORM == PLATFORM_LINUX
        // do other stuff
    #endif
    }
    
    MAIN
    {
        DoRoutine();
    }
    
        7
  •  0
  •   Todd Stout    15 年前

    可以为每个平台(.so或.dll)创建单独的共享库,然后在运行时动态加载相应的库。每个库将包含特定于平台的代码。对于特定于操作系统的加载库调用,您需要一个包装器,但这可能是您唯一的#ifdef’d函数。

        8
  •  0
  •   Tommy McGuire    15 年前

    真正地 imake autoconf / automake

    Imake最初由X window系统使用,其工作原理类似于knight666的答案;它使用一组依赖于系统的配置文件来确定系统的具体功能,并允许您处理系统之间的差异。据我所知,它从来没有见过多少实际的使用,而且从维基百科页面来看,即使是X窗口系统也不再使用它了。Imake有点恐怖。

    Autoconf/automake(和libtool)是shell脚本、Makefile模板和m4宏的捆绑包,它们生成“configure”脚本,每个人都知道并喜欢在任何模糊的Unixish上构建软件。configure脚本运行一系列测试,然后定义预处理器宏并编写Makefile,这两种方法都允许代码处理系统依赖关系。如果你感兴趣的话,一本相当不错的书, GNU Autoconf, Automake, and Libtool ,是可用的,尽管它看起来已经过时了。Autoconf和automake有点恐怖。

    执行诸如“if(windows){doWindowsRoutine();}”这样的动态操作通常行不通,因为dowWindowRoutine不会在非windows计算机上编译。

    总的来说,整个地区不仅仅是一个爬行恐怖。