如何在 typedef 函数上强制执行 SAL 注释?

How do I enforce SAL annotations on a typedef'd function?

假设我像这样导入一个 DLL 函数:

typedef int(__stdcall *pRandomNumber)(size_t cb, unsigned char *pb);

HINSTANCE hDLL = LoadLibraryW(L"foo.dll");
pRandomNumber RandomNumber = (pRandomNumber)GetProcAddress(hDLL, "RandomNumber");

现在,DLL代码中的这个函数RandomNumber被SAL注解_Check_return_注解了。我还希望在调用它的代码中强制执行此注释。我该如何完成?

如果我把注释放在 typedef:

_Check_return_
typedef int(__stdcall *pRandomNumber)(size_t cb, unsigned char *pb);

稍后当我初始化一个 pRandomNumber 并调用它而不检查 return 值时,分析无法报告警告。

如果我把注释放在一个实例上:

typedef int(__stdcall *pRandomNumber)(size_t cb, unsigned char *pb);

_Check_return_
pRandomNumber RandomNumber;

不检查return值就调用分析还是报错

有什么办法可以强制执行吗?一种方法是编写带有注释的存根内联函数,但这对我来说很难看。

而不是声明变量

int (__stdcall *pRandomNumber)(size_t cb, unsigned char *pb);

您可以在此声明中使用 __declspec(dllimport) attribute and use [[nodiscard]] and/or _Must_inspect_result_ 声明函数

EXTERN_C
_NODISCARD
DECLSPEC_IMPORT
_Must_inspect_result_
int 
WINAPI 
RandomNumber(
    _In_ size_t cb, 
    _Out_writes_bytes_(cb) unsigned char *pb
    );

在内部,当您使用 __declspec(dllimport) attribute, compiler declare 变量 extern 关键字声明 api 时;

extern "C" { 
    extern PVOID __imp_<function_name>;
}

到位 __FUNCDNAME__ 使用 - 函数的修饰名

所以可能说

extern PVOID __imp_##__FUNCDNAME__;

当然,因为extern,这只是声明。如果你不添加定义 - 你得到

error LNK2001: unresolved external symbol __imp_RandomNumber

如果你尝试调用 RandomNumber。所以你需要添加定义。对于 _AMD64_ 这很简单,因为 extern "C" 符号没有修饰。

所以简单写:

EXTERN_C_START
    PVOID __imp_RandomNumber;
EXTERN_C_END

改为

EXTERN_C_START
    int (__stdcall *pRandomNumber)(size_t cb, unsigned char *pb);
EXTERN_C_END

你需要在这两种情况下声明指针大小变量,它将保存函数指针 - 仅在名称上不同 - 你 select pRandomNumber 作为名称(并且可以 select 任何名称) - 在我的例子中 - 你必须 select __imp_RandomNumber ( __imp_<function_name> ) - 添加 __imp_ 修饰函数名称前缀。

但如果 _X86_ 存在问题 - 对于 __stdcall - @ 符号将出现在装饰函数名称中。你需要写

EXTERN_C_START
    PVOID __imp__RandomNumber@8;
EXTERN_C_END

因为装饰名称将是 _RandomNumber@8。但 __imp__RandomNumber@8 无效 c/c++ 名称。您可以在 asm 代码中定义这样的名称,但不能在 c/c++ 中定义。但您可以使用 /alternatename 链接器选项。

__pragma(comment(linker, "/alternatename:__imp__RandomNumber@8=___imp_RandomNumber"))

EXTERN_C_START
    PVOID __imp_RandomNumber;
EXTERN_C_END

如果未找到链接器 __imp__RandomNumber@8 它会尝试使用 ___imp_RandomNumber 如果它存在。 __imp_RandomNumber 将在 _X86_

中被修饰为 ___imp_RandomNumber

可能写下一个宏

#ifdef _X86_
#define ALT_NAME(name, n) __pragma(comment(linker, _CRT_STRINGIZE(/alternatename:__imp__##name##@##n####=___imp_##name)))
#else
#define ALT_NAME(name, n)
#endif

#define IMP_FUNC(name, n) EXTERN_C_START PVOID __imp_##name; EXTERN_C_END ALT_NAME(name, n)

你的代码将是

// global declaration

EXTERN_C
_NODISCARD
DECLSPEC_IMPORT
_Must_inspect_result_
int 
WINAPI 
RandomNumber(
    _In_ size_t cb, 
    _Out_writes_bytes_(cb) unsigned char *pb
    );

IMP_FUNC(RandomNumber, 8);

// initialization
__imp_RandomNumber = GetProcAddress(GetModuleHandle(L"foo"), "RandomNumber");

//usage
if (__imp_RandomNumber )
{
    UCHAR test[16];
    RandomNumber(sizeof(test), test);
}

你得到了

warning C4834: discarding return value of function with 'nodiscard' attribute

如果不使用 RandomNumber

的 return 值