【问题标题】:Template for WinAPI functionsWinAPI 函数模板
【发布时间】:2017-12-28 20:33:33
【问题描述】:

是否有可能以某种方式为 WinAPI 函数创建模板?例如,有两个类似的函数(LookupPrivilegeName 和 LookupPrivilegeDisplayName),但参数集不同。我这样调用这两个函数:第一个调用检索所需的缓冲区大小,第二个调用返回所需的值。如果这不可能,是否有任何替代方法可以使代码更紧凑?

【问题讨论】:

  • 您可以创建一些元编程来将一组参数类型与一个 winapi 函数相匹配,从而创建一个您自己解决的重载集,但没有什么神奇的解决方案。
  • 是的,通常的做法是创建一个方便的包装器,提供更简单的界面、验证输入参数、检查错误代码等。
  • 感谢您的回复,但举个例子怎么样?或者你能指出我关于这个主题的有用链接吗?我确实需要了解这一点。
  • 这不能自动化。您将必须实现该功能,并将其包装在具有更简单接口的函数中。有关随机示例,请参阅this answer。我建议不要合并通用模式来检索所需的返回缓冲区大小,然后调用以使用宏和/或模板将信息检索到单个实现中。并非所有具有兼容参数和返回值的 API 调用的行为都相同。不要让你的编译器成为产生错误的帮凶。
  • 您实际上应该提供一些您希望使其更紧凑的示例代码。由于LookupPrivilegeNameLookupPrivilegeDisplayName 采用不同的参数,因此无论如何您都需要调用它们。

标签: c++ templates winapi


【解决方案1】:

你可能想要这样的东西:

template<typename CharT>
bool LookupPriviledgeDisplayName(CharT const *, CharT const *, DWORD *, std::basic_string<CharT> & strDisplayName);
template<>
bool LookupPriviledgeDisplayName<char>(char const * pszSystem, char const * pszName, DWORD * pdwLangID, std::basic_string <char> & strDisplayName)
{
    DWORD dwLength = 0;
    if(!LookupPrivilegeDisplayNameA(pszSystem, pszName, nullptr, &dwLength, pdwLangID))
        return false;

    std::vector<char> buffer(dwLength + 1);
    if(!LookupPrivilegeDisplayNameA(pszSystem, pszName, &buffer[0], &dwLength, pdwLangID))
        return false;

    strDisplayName.assign(&buffer[0], &buffer[0] + dwLength);

    return true;
}
template<>
bool LookupPriviledgeDisplayName<wchar_t>(wchar_t const * pszSystem, wchar_t const * pszName, DWORD * pdwLangID, std::basic_string <wchar_t> & strDisplayName)
{
    DWORD dwLength = 0;
    if(!LookupPrivilegeDisplayNameW(pszSystem, pszName, nullptr, &dwLength, pdwLangID))
        return false;

    std::vector<wchar_t> buffer(dwLength + 1);
    if(!LookupPrivilegeDisplayNameW(pszSystem, pszName, &buffer[0], &dwLength, pdwLangID))
        return false;

    strDisplayName.assign(&buffer[0], &buffer[0] + dwLength);

    return true;
}

【讨论】:

  • 这是copy-pasta的定义,也确实没必要叫A变种。
  • 因为LookupPrivilege[Display]NameW 是非常重的调用(使用 rpc 调用 lsass)在第一次调用时更好地使用一些按分配的缓冲区,而不是 0。只有当它失败时,才为第二次调用重新分配缓冲区
  • 也许我不太正确表达我的想法。我的意思是有没有办法为两个带有可变参数集的 winapi 函数创建一个模板?像template&lt;typename TRet, typename TVal, typename... TArgs&gt; 这样的东西,其中 Tret 是返回的函数类型,TVal 是我想要检索的所需值,而 TArgs 是一组参数。
【解决方案2】:

类似的东西。主要思想是按功能分离代码,避免重复。

#include <memory>
#include <string>
#include <iostream>
#include <system_error>
#include <cassert>

//  Error handler.
void
On_Error(const ::DWORD error_code)
{
    throw ::std::system_error{static_cast<int>(error_code), ::std::system_category()};
}

//  Error check.
void
Validate_LookupPrivilegeNameSuccess(const ::BOOL result, const bool expect_insufficient_buffer)
{
    if(FALSE == result)
    {
        auto const error_code{::GetLastError()};
        if((ERROR_INSUFFICIENT_BUFFER == error_code) && (!expect_insufficient_buffer))
        {
            On_Error(error_code);
        }
    }
}

enum class
t_PrivilegeNameCategoryId
{
    name
,   display_name
};

//  Helper class calling WinAPI methods, checking input and output.
template<t_PrivilegeNameCategoryId> class
t_LookupPrivilegeNameImpl;

template<> class
t_LookupPrivilegeNameImpl<t_PrivilegeNameCategoryId::name> final
{
    public: static ::std::size_t
    Lookup(const ::LPCWSTR psz_system_name, ::LUID & luid, const ::LPWSTR p_buffer, ::DWORD buffer_capacity_items_count)
    {
        assert((0 == buffer_capacity_items_count) || (nullptr != p_buffer));
        Validate_LookupPrivilegeNameSuccess
        (
            ::LookupPrivilegeNameW
            (
                psz_system_name
            ,   ::std::addressof(luid)
            ,   p_buffer
            ,   ::std::addressof(buffer_capacity_items_count)
            )
        ,   nullptr == p_buffer
        );
        return(buffer_capacity_items_count);
    }
};

template<> class
t_LookupPrivilegeNameImpl<t_PrivilegeNameCategoryId::display_name> final
{
    public: static ::std::size_t
    Lookup(const ::LPCWSTR psz_system_name, const ::LPCWSTR psz_display_name, const ::LPWSTR p_buffer, ::DWORD buffer_capacity_items_count)
    {
        assert(psz_display_name);
        assert(L'\0' != psz_display_name[0]);
        assert((0 == buffer_capacity_items_count) || (nullptr != p_buffer));
        ::DWORD language_id{};
        Validate_LookupPrivilegeNameSuccess
        (
            ::LookupPrivilegeDisplayNameW
            (
                psz_system_name
            ,   psz_display_name
            ,   p_buffer
            ,   ::std::addressof(buffer_capacity_items_count)
            ,   ::std::addressof(language_id)
            )
        ,   nullptr == p_buffer
        );
        return(buffer_capacity_items_count);
    }
};

//  Lookup function implementing get size -> resize buffer -> get data algorithm.
template<t_PrivilegeNameCategoryId name_category_id, typename... TArgs> void
Lookup_PrivilegeName(::std::wstring & name, TArgs &&... args)
{
    try
    {
        name.resize(t_LookupPrivilegeNameImpl<name_category_id>::Lookup(::std::forward<TArgs>(args)..., ::LPWSTR{}, ::DWORD{}));
        t_LookupPrivilegeNameImpl<name_category_id>::Lookup(::std::forward<TArgs>(args)..., name.data(), static_cast<::DWORD>(name.size()));
        if(name.empty() || (L'\0' != name.back()))
        {
            On_Error(ERROR_UNIDENTIFIED_ERROR);
        }
        name.pop_back();
    }
    catch(...)
    {
        name.clear();
        throw;
    }
}

int main()
{
    ::LPCWSTR psz_system_name{};
    ::LUID privilege_luid{5}; // a wild guess
    ::std::wstring privilege_name{};
    Lookup_PrivilegeName<t_PrivilegeNameCategoryId::name>(privilege_name, psz_system_name, privilege_luid);
    ::std::wstring privilege_display_name{};
    Lookup_PrivilegeName<t_PrivilegeNameCategoryId::display_name>(privilege_display_name, psz_system_name, privilege_name.c_str());
    ::std::wcout << privilege_name << L"\n" << privilege_display_name << ::std::endl;
    return(0);
}

【讨论】:

  • 您避免了缓冲区管理的重复代码,但您用它换取了更高的代码复杂性。在我看来,这是一个示例,不应该最大程度地遵循DRY,因为它使代码更难推理。我敢打赌,与两个简单的重载相比,您甚至不会节省很多 LOC。
【解决方案3】:

我的俄罗斯朋友向我解释了模板的局限性。他建议使用带有上下文切换的函数来代替它们。在我的情况下,它会是这样的:

BOOL LookupData(DWORD context, PLUID luid, wstring input, wstring & output) {
  BOOL status = TRUE;
  DWORD buflen = 0, lang = 0;
  switch (context) {
     case 0: status = LookupPrivilegeName(NULL, luid, NULL, &buflen); break;
     case 1: status = LookupPrivilegeDisplayName(NULL, input.c_str(), NULL, &buflen, &lang); break;
     default: return FALSE;
  }

  if (!status && ERROR_INSUFFICIENT_BUFFER != GetLastError())  return status;

  auto buffer = make_unique<wchar_t[]>(buflen);
  switch (context) {
     case 0: status = LookupPrivilegeName(NULL, luid, buffer.get(), &buflen); break;
     case 1: status = LookupPrivilegeDispayName(NULL, input.c_str(), buffer.get(), &buflen, &lang); break;
  }

  if (!status) {
      buf.release();
      return status;
  }

  output = buf.get();
  buf.release();

  return status;
}

现在我可以在获得 TOKEN_PRIVILEGES 结构后循环写入:

std::wstring name, desription;
if (!LookupData(0, tp->Privipeges[i].Luid,  L"", name)) break;
if (!LookupData(1, NULL, name, description)) break;
std::wcout << name << L" - " << std::endl;

当然,这是一个肮脏的把戏,但它确实有效。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-08-02
    • 1970-01-01
    • 2011-05-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多