Easy to use wrapper over LoadLibrary () and GetProcAddress ()

Preamble


Using dynamically linked libraries (DLLs), as you know, involves one of two ways to connect: load-time linking and run-time linking . In the latter case, you need to use the API provided by the operating system to load the desired module (library) and search for the address of the necessary procedure in it. There are many wrappers, but, unfortunately, all the ones I encountered are very complicated and overloaded with unnecessary code. The proposed solution was originally intended to call functions stored in DLLs from executable modules (EXE), it is distinguished by relative ease of implementation, and (more importantly) ease of use in client code.

The solution using the pure Win32 API looks something like this (practically, this is a repetition of a fragment from MSDN):

typedef int (__cdecl *some_proc_t)(LPWSTR);
HINSTANCE hlib = LoadLibrary(_T("some.dll"));
myproc_t proc_addr = NULL;
int result = -1;
if (hlib) {
    proc_addr = (some_proc_t) GetProcAddress(hlib, "SomeProcName");
    if (proc_addr) {
        result = proc_addr(L"send some string to DLL function");
        printf("Successfully called DLL procedure with result %d", result);
    }
    FreeLibrary("some.dll");
}

This article provides an easy-to-use wrapper over these system calls. Usage example:

ntprocedure some_proc_("SomeProcName", _T("some.dll"));
try {
    int result = some_proc_(L"send some string to DLL function");
    printf("Successfully called DLL procedure with result %d", result);
} catch (...) {
    printf("Failed to call DLL procedure");
}

As you can see from the listing, all you need to do is create an ntprocedure object with template parameters that correspond to the type of the called function, passing its name and the library name in the constructor.

Implementation


Before starting to describe the implementation of the wrapper, I will give a small header file with trivial declarations that seem useless to many, which are easy to get rid of, but are used by me in the code.

Common.h file
#pragma once
#include "tchar.h"
#include 
#define NS_BEGIN_(A) namespace A {
#define NS_BEGIN_A_ namespace {
#define NS_END_ }
#define NO_EXCEPT_ throw()
#define THROW_(E) throw(E)
#define PROHIBITED_ = delete
//=============================================================================
typedef std::basic_string<
  TCHAR, std::char_traits, std::allocator > tstring;


We’ll think about how to ensure that the template class being developed behaves like a function at the point of call and can support an arbitrary number and type of arguments. The first thing that comes to mind is to use a generalized functor. Authors of known implementations of such wrappers do just that. In this case, either partial specialization of the functor template is used depending on the number of arguments, or multiple overloading of the function call operator. The matter, as a rule, is not complete without the help of macros. Fortunately, templates with a variable number of arguments appeared in C ++ 11 that greatly simplify life:

R operator () (Args ... args)

In fact, in our case, you can do much easier, namely, use the cast operator instead of the function call operator. If T is a type of function pointer, and address is a variable in which its address is stored, the following statement can be defined:

operator T()
{
    return reinterpret_cast(address);
}

The following is the complete code for the ntprocedure.h header file.

Ntprocedure.h file
#pragma once
#include "common.h"
#include 
#include 
#include 
NS_BEGIN_(ntutils)
NS_BEGIN_(detail)
class ntmodule;
class ntprocedure_base {
  ntprocedure_base(const ntprocedure_base&) PROHIBITED_;
  void operator=(const ntprocedure_base&) PROHIBITED_;
public:
  ntprocedure_base(const std::string& a_proc_name, const tstring& a_lib_name);
  // Constructor.
  virtual ~ntprocedure_base() = 0;
  // Destructor.
  FARPROC WINAPI address();
  // Get the procedure address.
  const std::string& name() const;
  // Get the procedure name.
private:
  std::string m_name;
  std::shared_ptr m_module;
};
NS_END_
template class ntprocedure : public detail::ntprocedure_base {
public:
  typedef typename std::remove_pointer::type callable_t;
  typedef callable_t *callable_ptr_t;
  ntprocedure(const std::string& a_proc_name, const tstring& a_lib_name)
  : ntprocedure_base(a_proc_name, a_lib_name),
    m_function(nullptr)
  {    
  }
  // Constructor.
  virtual ~ntprocedure()
  {
  }
  // Destructor.
  operator callable_ptr_t()
  {
    if (!m_function) {
      m_function = reinterpret_cast(address());
    }
    return m_function;
  }
  // Return stored function to invoke.
private:
  callable_ptr_t m_function;    
};
NS_END_


A couple of points that the attentive reader noticed - the address of the procedure is stored in the m_function variable and is calculated once, and the second moment - a shared pointer to an object of the ntmodule class is stored in the base class . It is easy to guess that it stores information about the loaded module. Using shared_ptr allows you to automatically unload a module after destroying all the procedure objects that use it.

Ntmodule.h file
#pragma once
#include "common.h"
#include "resource_ptr.h"
#include 
#include 
NS_BEGIN_(ntutils)
NS_BEGIN_(detail)
class ntmodule : public std::enable_shared_from_this {
  ntmodule(const ntmodule&) PROHIBITED_;
  void operator=(const ntmodule&) PROHIBITED_;
public:
  typedef std::list container_t;
  ntmodule(const tstring& a_name);
  // Constructor.
  ~ntmodule();
  // Destructor.
  const tstring& name() const;
  // Get the module name.      
  FARPROC WINAPI address(const std::string& a_name);
  // Get the procedure address.
  std::shared_ptr share();
  // Share this object.
  static container_t& cached();
  // Return the reference to the cache.
private:
  tstring m_name;
  hmodule_ptr m_handle;
};
NS_END_
NS_END_


Consider the definition of the ntmodule class:

Ntmodule.cpp file
#include "stdafx.h"
#include "ntmodule.h"
#include "ntprocedure.h"
#include 
#include 
ntutils::detail::ntmodule::ntmodule(const tstring& a_name)
: m_name(a_name)
{
  assert(!a_name.empty());
  cached().push_back(this);
}
ntutils::detail::ntmodule::~ntmodule()
{
  cached().remove(this);
}
const tstring& ntutils::detail::ntmodule::name() const
{
  return m_name;
}
FARPROC WINAPI ntutils::detail::ntmodule::address(
  const std::string& a_name
)
{
  assert(!a_name.empty());
  if (!m_handle) {
    m_handle.reset(::LoadLibrary(m_name.c_str()));    
  }
  if (!m_handle) {
    std::string err("LoadLibrary failed");
    throw std::runtime_error(err);
  }
  return m_handle ? ::GetProcAddress(m_handle, a_name.c_str()) : 0;
}
std::shared_ptr
ntutils::detail::ntmodule::share()
{
  return shared_from_this();
}
ntutils::detail::ntmodule::container_t&
ntutils::detail::ntmodule::cached()
{
  static container_t* modules = new container_t;
  return *modules;
}


As you can see, pointers to all used modules are stored in a static list. This provides caching. The constructor of the ntmodule class places a pointer to its object in the list, and the destructor deletes it. The definition of the ntprocedure class will fully clarify the picture.

Ntprocedure.cpp file
#include "stdafx.h"
#include "ntmodule.h"
#include "ntprocedure.h"
#include 
#include 
ntutils::detail::ntprocedure_base::ntprocedure_base(
  const std::string& a_proc_name, const tstring& a_lib_name
)
: m_name(a_proc_name),
  m_module(nullptr)
{
  assert(!a_proc_name.empty());
  assert(!a_lib_name.empty());
  for (auto module : ntmodule::cached()) {
    // Perform case insensitive comparison:
    if (!lstrcmpi(module->name().c_str(), a_lib_name.c_str())) {
      m_module = module->share();
      break;
    }
  }
  if (!m_module) {
    m_module = std::make_shared(a_lib_name);
  }
}
ntutils::detail::ntprocedure_base::~ntprocedure_base()
{
}
FARPROC WINAPI ntutils::detail::ntprocedure_base::address()
{
  FARPROC addr = m_module->address(m_name);
  if (!addr) {
    std::string err("GetProcAddress failed");
    throw std::runtime_error(err);
  }
  return addr;
}
const std::string& ntutils::detail::ntprocedure_base::name() const
{
  return m_name;
}


In the ntprocedure_base constructor , the required module is searched for in the static list by its name. If such a module is found, then the call module-> share () creates a shared pointer based on the pointer in the list; if there is no such module yet, a new object is created.

Note that for each module we use for the first time, we call LoadLibrary () , without relying on the GetModuleHandle () function, and only then we control the created objects using shared_ptr . This makes it safe to use the created wrapper together in one project with code using direct calls to LoadLibrary () and FreeLibrary () .

That's all. Oh yes, the type resouce_ptr appears in the code . This is nothing more than a RAII wrapper over types like HANDLE, HMODULE, and so on. For those who are intereno, I bring the implementation:

Resource_ptr.h file
#pragma once
#include "common.h"
#include "windows.h"
#include 
#include 
NS_BEGIN_(ntutils)
template
struct resource_close {
  void operator()(typename HTag_::handle_t) const NO_EXCEPT_;
};
struct handle_tag {
  typedef HANDLE resource_t;
};
struct hmodule_tag {
  typedef HMODULE resource_t;
};
template<> struct resource_close {
  void operator()(handle_tag::resource_t a_handle) const NO_EXCEPT_
  {
    bool status = !!::CloseHandle(a_handle);
    assert(status);
  }
};
template<> struct resource_close {
  void operator()(hmodule_tag::resource_t a_handle) const NO_EXCEPT_
  {
    bool status = !!::FreeLibrary(a_handle);
    assert(status);
  }
};
template<
  typename RTag_,
  typename RTag_::resource_t RInvalid_,
  typename RFree_ = resource_close
>
class resource_ptr {
  typedef typename RTag_::resource_t resource_t;
  typedef RFree_ deletor_t;
  resource_ptr(const resource_ptr&) PROHIBITED_;
  void operator=(const resource_ptr&) PROHIBITED_;
public:
  resource_ptr() NO_EXCEPT_
  : m_resource(RInvalid_)
  {
  }
  resource_ptr(resource_t a_resource) NO_EXCEPT_
  : m_resource(a_resource)
  {  
  }
  // Constructor.
  explicit operator bool() const NO_EXCEPT_
  {
    return m_resource && m_resource != RInvalid_;
  }
  // Operator bool().
  operator const resource_t&() const NO_EXCEPT_
  {
    return m_resource;
  }
  // Get the stored handle value.
  void reset(resource_t a_resource = resource_t()) NO_EXCEPT_
  {
    resource_t old = m_resource;
    m_resource = a_resource;
    if (old != resource_t() && old != RInvalid_) {
      m_deletor(old);
    }
  }
  ~resource_ptr() NO_EXCEPT_
  {
    if (m_resource != resource_t() && m_resource != RInvalid_) {
      m_deletor(m_resource);
    }
  }
  // Destructor.
private:
  resource_t m_resource;
  deletor_t m_deletor;
};
typedef resource_ptr handle_ptr;
typedef resource_ptr hmodule_ptr;
NS_END_


That's all for sure. Thank you for your attention, I will be glad to hear your comments!

Also popular now: