【问题标题】:Conditional Linking in IAR EWARMIAR EWARM 中的条件链接
【发布时间】:2019-03-04 20:20:36
【问题描述】:

我正在使用 IAR EWARM 8.10.1,它使用 ILINK 链接器。

我有两个编译单元使用的公共头文件。它包括具有外部链接的函数原型并构成API。根据构建的配置方式,我希望模块 A 或 B 与我的应用程序的其余部分链接。

[ Common_Header.h ]
    |         |
    |         +----- [Module_A.c] ---> [Module_A.o]
    |
    +--------------- [Module_B.c] ---> [Module_B.o]

我想以某种方式将参数传递给 ilinkarm.exe 以包含 Module_A.o。

我过去使用的其他 IAR 工具链使用了 XLINK 链接器。 XLINK 有一个 -A 选项,我想这与我需要的类似。

我本质上想要的是,当 Module_A 处于活动状态时,Module_B 中的函数定义被视为 __weak,反之亦然。

如果可能,我想避免将#pragma weak 放入我的代码中。我需要能够用几个不同的工具链编译这段代码。所以我需要用#ifdef __ICCARM__之类的东西来包装任何这样的pramgas。此外,我需要定义一些额外的预处理器符号,以便在另一个模块处于活动状态时有条件地使一个模块变弱。这是我希望避免在代码之外的所有复杂性。

此外,当 module_A 处于活动状态时,我不想将 module_B 从构建中排除。我希望两个模块始终编译。如果有人对接口和 module_A 进行了更改,但未能更新 module_B,我希望他们得到编译器错误。随着接口的发展,这将使 module_B 不会进入一些孤立和损坏的状态,并且我们的注意力会集中在 module_A 上。

我已查看 EWARM_DevelopmentGuide.ENU.pdf,但找不到似乎可以满足我要求的命令行选项。我想知道这样的选项是否存在而我错过了,或者是否有其他方法可以完成我所追求的目标。

【问题讨论】:

  • 您还没有提出我们可以回答的问题。大概你尝试了一些东西,但它没有用。你尝试了什么?问题是什么?您似乎已经知道如何执行此操作的选项(-A 选项和__weak 关键字)。
  • 我不使用 EWARM,所以无法回答,但当然可以从 IDE 中的 构建配置 中排除单个文件或组 - 所以您只需要单独构建每个构建的配置。我不确定是否可以单独选择编译而不是链接,或者它是否会在任何情况下这样做但值得一试(Keil uVision 允许这样做,所以也许 EWARM 也是如此)。
  • @user694733 也许我应该澄清我的问题。我问是否首先存在一个选项来执行此操作。考虑到另一个 IAR 产品 xlink 有这样一个选项,似乎应该存在一个选项。 __weak 关键字不是一个理想的选择,因为两个定义不能同时是弱的,否则我又回到了我开始的地方。它还涉及使用我必须对其他编译器隐藏的编译指示使代码混乱。
  • @clifford 我可以从构建中排除某些文件并将它们放入不同的配置中。但是,我已经有了三种构建配置,它们在优化、调试符号、诊断输出级别等方面代表不同的发布配置。添加它会使我必须管理的构建配置数量增加一倍。此外,我声明的目标之一是两个模块都应始终构建,以便对接口的更改不会使其中一个模块处于孤立状态。
  • “我的一个既定目标是两个模块都应该始终构建” 这是有道理的,但是:“添加它会使我拥有的构建配置数量翻倍管理” 如果不使用构建配置,您还会如何选择是使用 A 还是 B?您必须在某处拥有该信息。

标签: c linker arm embedded iar


【解决方案1】:

这并不是一个完整的答案,因为我没有像你那样新版本的编译器,但更多的是一种可能的解决方法。

Module_A.c

#if MODULE_A_SELECTED
    #define MY_WEAK
#else
    #define MY_WEAK __weak
#endif

MY_WEAK void foo(void) { ... }
 ...

Module_B.c

#if MODULE_B_SELECTED
    #define MY_WEAK
#else
    #define MY_WEAK __weak
#endif

MY_WEAK void foo(void) { ... }
 ...

然后您可以根据需要在配置中定义MODULE_*_SELECTED

【讨论】:

  • 我最终使用了一种类似于您建议的方法。我的编辑正在等待同行评审。
  • 我的编辑被拒绝了。原因是它们作为单独的答案而不是编辑更有意义。所以我将它们添加为新答案。
【解决方案2】:

无需依赖链接器特定的支持或 IDE 特定的构建管理。一个完全可移植的解决方案是使用不同的符号名称定义 A 和 B 实现,然后使用条件定义的宏来选择所需的实现。

例子:

#if defined USE_IMPLEMENTATION_A
    #define doSomething implementationA_doSomething

#elif defined USE_IMPLEMENTATION_B
    #define doSomething implementationB_doSomething

#else
    #error API implementation not defined
#endif

int implementationA_doSomething( void ) ;
int implementationB_doSomething( void ) ;

这样,实现 A 和 B 都将始终被编译,但通过使用宏 doSomething 而不是实现特定的函数名称,只会使用选定的 API。

我不知道 ILINK 有多聪明,但通过将实现放在单独的翻译单元(即 .c 文件)中,链接器应该能够从链接中消除未使用的函数。如果不是,则将目标代码放在静态链接库(.lib 或 .a)中肯定会。


为了解决维护两个除了命名空间前缀之外相同的实现文件的问题,您可以创建一个带有原型的单个虚拟头文件,例如:

int NAMESPACE_doSomething( void ) ;

然后使用诸如sed 之类的工具进行预构建步骤,以生成实现原型标头,例如:

sed -i 's/NAMESPACE/api_a/g' api_dummy.h > api_a.h    
sed -i 's/NAMESPACE/api_b/g' api_dummy.h > api_b.h

然后你有一个文件 api.h 包含(片段):

#if defined USE_IMPLEMENTATION_A
    #define doSomething api_a_doSomething

#elif defined USE_IMPLEMENTATION_B
    #define doSomething api_b_doSomething

#else
    #error API implementation not defined
#endif

#include api_a.h
#include api_b.h

您可以进一步编写代码生成器,从函数名称列表生成 api.h。这在您喜欢的脚本语言甚至 C 中都不会太难。您可以编写这样的生成器来获取命令行参数:

generate_api <input> <output> <namespace1> <namespace2> ... <namespaceN>

然后调用它:

generate_api functions.txt api.h api_a api_b

您甚至可以使用虚拟标头中的 NAMESPACE_ 文本来生成 &lt;input&gt; 的函数名称列表,这样整个 API 标头集就可以从单个虚拟标头生成。

【讨论】:

  • 我会试试这个。我怀疑我可能会收到一些关于外部链接未使用功能的消息。如果不是来自 IAR,我们的静态分析工具会报错。但是静态分析器的抱怨没什么大不了的,我们可以压制它。
  • @Nick 我想未使用的外部链接函数会被接受,因为如果使用第三方库代码,这是一种常见且正常的情况 - 您很少会使用任何API 名副其实。抱怨未使用的内部(静态)链接函数的静态分析更成问题,因为链接器可能无法消除这些函数,并且会无用地占用空间。
  • IAR 不会抱怨外部链接未使用的功能(使用我们当前的配置)。我们的静态分析器可以。但是这种方法的一个更大的问题是我们需要原型。所以我需要在 else 条件下定义一个原型。所以这种方法比依赖__weak 更便携,但缺点是需要维护冗余的原型列表。
  • @Nick :您可以通过使用带有替换文本前缀的单个虚拟头文件来解决维护问题,您可以使用它来生成实际头文件,并使用代码生成器脚本来生成函数名称宏。我在答案中进一步概述了这一点。
  • 我们已经有了一个相当复杂的构建系统,其中包含构建前和构建后的脚本。因此,我宁愿不在该过程中添加另一个步骤来生成标题。如果这个界面的复杂性增加,我可能会改变主意,或者我们添加更多可能的模块。
【解决方案3】:

我最终使用了类似于 user694733 建议的弱链接。但我的方法有点不同。

我在模块 A 和 B 的顶部都添加了这样的块。

#if (defined __ICCARM__)
    #if(defined USE_MODULE_A) && (1 == USE_MODULE_A)
        // do nothing, make definitions in this file strong
    #elif(defined USE_MODULE_B) && (1 == USE_MODULE_B)
        #pragma weak foo_fn
        #pragma weak bar_fn
        #pragma weak baz_fn
        #pragma weak qux_fn
    #else
        #error USE_MODULE_A or USE_MODULE_B must be defined.
    #endif
#endif

这种方法不需要我用MY_WEAK 装饰每个函数原型。所以非标准的东西都归为一组。

我不喜欢使用 __weak / #pragma 弱的几件事:

我不喜欢的第一件事是它增加了两个模块之间的耦合。如果两个符号都没有定义,那么两个模块的定义都会很弱。到那时,您怎么知道将使用哪一个?因此,每个模块都必须存在另一个模块,或者至少有多个选项。我本可以使用单个定义并更改值,但我选择这样做是为了让名称具有描述性。

我不喜欢的第二件事是我将代码与项目构建方式的工件混在一起。我想把这样的逻辑拉出来,并在可行的时候把它放到构建系统中。

第三个是它不是完全可移植的,必须用#if (defined __ICCARM__) 关闭。

但这将是我使用的,除非我找到一种对我更有效的方法来完成它。如果发生这种情况,我将发布/接受其他答案。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-12-05
    • 2016-09-06
    • 2018-01-20
    • 2018-07-03
    • 2023-04-02
    • 2020-05-16
    • 2012-08-18
    • 1970-01-01
    相关资源
    最近更新 更多