cwinapidllsal

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


Let's say I import a DLL function like so:

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

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

Now, this function RandomNumber in the code of the DLL is annotated with the SAL annotation _Check_return_. I also want to have this annotation enforced in code that calls it. How do I get that done?

If I put the annotation on the typedef:

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

Analysis fails to report a warning later when I initialize a pRandomNumber and call it without checking the return value.

If I put the annotation on an instance:

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

_Check_return_
pRandomNumber RandomNumber;

Analysis still fails to report when I call it without checking the return value.

Is there any way I can have that enforced? One way is writing a stub inline function with the annotation on it, but that feels ugly to me.


Solution

  • instead of declare variable

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

    you can declare function with __declspec(dllimport) attribute and use [[nodiscard]] and/or _Must_inspect_result_ on this declaration

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

    internally, when you declare api with __declspec(dllimport) attribute, compiler declare variable with extern keyword;

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

    in place <function_name> __FUNCDNAME__ used - the decorated name of the function

    so possible say and

    extern PVOID __imp_##__FUNCDNAME__;
    

    of course, because extern, this is only declaration. and if you not add definition - you got

    error LNK2001: unresolved external symbol __imp_RandomNumber
    

    if you try call RandomNumber. so you need add definition. for _AMD64_ this is very simply, because extern "C" symbols not decorated.

    so simply write:

    EXTERN_C_START
        PVOID __imp_RandomNumber;
    EXTERN_C_END
    

    instead

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

    you need in both case declare pointer size variable, which will be hold function pointer - different only in name - you select pRandomNumber as name (and can select any name) - in my case - you must select __imp_RandomNumber ( __imp_<function_name> ) - add __imp_ prefix to decorated function name.

    but in case _X86_ exist problem - for __stdcall - the @ symbol will be in decorated function name. you need write

    EXTERN_C_START
        PVOID __imp__RandomNumber@8;
    EXTERN_C_END
    

    because decorated name will be _RandomNumber@8. but __imp__RandomNumber@8 not valid c/c++ name. you can define such name in asm code but not in c/c++. but you can use /alternatename linker option.

    __pragma(comment(linker, "/alternatename:__imp__RandomNumber@8=___imp_RandomNumber"))
    
    EXTERN_C_START
        PVOID __imp_RandomNumber;
    EXTERN_C_END
    

    if linker not found __imp__RandomNumber@8 it try use ___imp_RandomNumber if it exist. and __imp_RandomNumber will be decorated to ___imp_RandomNumber in _X86_

    possible write next macro

    #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)
    

    with this you code will be

    // 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);
    }
    

    and you got

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

    if not use return value of RandomNumber