Exetools

Exetools (https://forum.exetools.com/index.php)
-   Source Code (https://forum.exetools.com/forumdisplay.php?f=46)
-   -   UniqueResource, a C++ template class as a universal resource manager (https://forum.exetools.com/showthread.php?t=21028)

WhoCares 06-07-2024 14:41

UniqueResource, a C++ template class as a universal resource manager
 
https://github.com/z16166/UniqueResource

UniqueResource, a C++ template class which works as a universal resource manager to own and free all kinds of resources.

C++14 and above, header-only.

Made by me, inspired by std::experimental::unique_resource and similar projects.

atom0s 06-07-2024 18:05

You can also do something similar to this by using the built-in 'std::shared_ptr' type. It allows for initialization with a custom deleter function. With that, you can pass a type cleanup call for types that have one. (You can also just define your own callback to cleanup custom/more unique types as needed.)

For example, using a 'HANDLE' type, which is cleaned up using 'CloseHandle':

PHP Code:

const std::shared_ptr<voidhandle(::OpenProcess(PROCESS_VM_READFALSEpid), ::CloseHandle); 

Usage is then similar to your setup by just using 'handle.get()' to obtain the actual handle.

WhoCares 06-07-2024 19:56

Yes. I have done that for Windows native RPC handles.

The problem is, we have to specify the custom deleter each time when we declare a new variable. It's tedious. Maybe a typedef or "using"?
And, std::unique_ptr lacks abstraction for a default_value.

Also, we can add custom conversion operator for UniqueResource to avoid explicitly using
Get() method each time, just like "operator LPCSTR()", and "operator LPCWSTR()" of the ancient CString class from Microsoft.

Code:

    explicit operator ResourceType() const noexcept
    {
        return resource_;
    }

"explicit" can be omitted if you prefer convenience to safety.

samples of custom deleters for windows native RPC( "pointer" is defined for each functor):
Code:

struct StringBindingDeleter {
  typedef RPC_WSTR pointer;
  void operator()(RPC_WSTR stringBinding) {
    [[maybe_unused]] RPC_STATUS status = RpcStringFree(&stringBinding);
    assert(status == RPC_S_OK);
  }
};

struct RpcBindingDeleter {
  typedef RPC_BINDING_HANDLE pointer;
  void operator()(RPC_BINDING_HANDLE h) {
    [[maybe_unused]] RPC_STATUS status = RpcBindingFree(&h);
    assert(status == RPC_S_OK);
  }
};


usage:
Code:

  RPC_WSTR stringBinding = nullptr;
  status = RpcStringBindingCompose(nullptr, (RPC_WSTR)L"ncalrpc", nullptr,
                                  (RPC_WSTR)L"9038a9dd-b0f3-4745-827e-d4109e662cea", nullptr,
                                  (RPC_WSTR *)&stringBinding);
  if (status != RPC_S_OK || !stringBinding)
    return E_FAIL;

  std::unique_ptr stringBindingOwner(stringBinding, StringBindingDeleter);

  RPC_BINDING_HANDLE rpcBinding = nullptr;
  status = RpcBindingFromStringBinding(stringBinding, &rpcBinding);
  if (status != RPC_S_OK)
    return E_FAIL;

  std::unique_ptr rpcBindingOwner(rpcBinding, RpcBindingDeleter);

Quote:

Originally Posted by atom0s (Post 131073)
You can also do something similar to this by using the built-in 'std::shared_ptr' type. It allows for initialization with a custom deleter function. With that, you can pass a type cleanup call for types that have one. (You can also just define your own callback to cleanup custom/more unique types as needed.)

For example, using a 'HANDLE' type, which is cleaned up using 'CloseHandle':

Code:

const std::shared_ptr<void> handle(::OpenProcess(PROCESS_VM_READ, FALSE, pid), ::CloseHandle);
Usage is then similar to your setup by just using 'handle.get()' to obtain the actual handle.


atom0s 06-08-2024 05:35

Using templates can remove the need to manually define the delete function. It can also be used to further extend into a custom wrapper to deal with the other parts you mentioned as well such as:

PHP Code:

template<typename T>
struct auto_cleanup_impl;

template<>
struct auto_cleanup_impl<HANDLE>
{
    static 
void cleanup(HANDLE value)
    {
        if (
value != INVALID_HANDLE_VALUE) ::CloseHandle(value);
    }
};

template<typename T>
class 
auto_cleanup
{
    
std::shared_ptr<voidvalue_;

public:
    
auto_cleanup(T value)
        : 
value_valueauto_cleanup_impl<T>::cleanup }
    {}
    ~
auto_cleanup(void)
    {}

    
T get(void) const
    {
        return 
this->value_.get();
    }
}; 

Then in the same manner can be used as:
PHP Code:

auto_cleanup val(::OpenProcess(PROCESS_VM_OPERATIONFALSE1)); 

To allow for direct value usage of the underlying shared pointer, one can add the overloads needed:

PHP Code:

    operator T() const { return value_.get(); }

    
auto_cleanup<T>& operator=(const& val)
    {
        
this->value_.reset(valauto_cleanup_impl<T>::cleanup);
        return *
this;
    } 

Then additional constructors can be added if you need default value handling or other kinds of initialization.

PHP Code:

    // Implementations will depend on the supported types etc.
    
auto_cleanup()
        : 
value_nullptrauto_cleanup_impl<T>::cleanup }
    {} 

To extend the types supported, you only need to add an auto_cleanup_impl entry for the given type. For example, to add support for a FILE pointer, it can be added via:

PHP Code:

template<>
struct auto_cleanup_impl<FILE*>
{
    static 
void cleanup(FILEvalue)
    {
        if (
value) ::fclose(value);
    }
}; 

Some examples of using this with a HANDLE type would be:

PHP Code:

    // Wrapped initialization; type determined by the return of the called function..
    
auto_cleanup val1(::OpenProcess(PROCESS_VM_OPERATIONFALSE1));

    
// Default initialization; type determined by the value type..
    
auto_cleanup val2INVALID_HANDLE_VALUE };

    
// Default initialization; type determined by explicit template usage..
    
auto_cleanup<HANDLEval3{};

    
// Value usage after creation..
    
uint8_t buffer[1024]{};
    
auto_cleanup val4INVALID_HANDLE_VALUE };
    
val4 = ::OpenProcess(PROCESS_VM_OPERATIONFALSE1);
    ::
ReadProcessMemory(val4reinterpret_cast<LPCVOID>(0x1234ABCD), &buffer1024nullptr); 


WhoCares 06-11-2024 13:45

Your coding a wrapper/aggregator for std::unique_ptr is a good choice.

Some types are same, but with different default values and different cleanup APIs.

For OpenProcess()/OpenThreadToken() etc., the type is HANDLE, the default value is NULL, the cleanup API is CloseHandle().

For CreateFile()/FindFirstFile()/CreateToolhelp32Snapshot() etc., the type is also HANDLE, but the default value is INVALID_HANDLE_VALUE, and the cleanup API for FindFirstFile() is FindClose(), instead of CloseHandle().

So that's why I use traits/adapter to describe the difference.

And, I need the operator&() override to auto reset to the default value when reusing the same UniqueResource variable.


Quote:

Originally Posted by atom0s (Post 131083)
Using templates can remove the need to manually define the delete function. It can also be used to further extend into a custom wrapper to deal with the other parts you mentioned as well such as:


chants 06-12-2024 13:41

Is it hard to abstract the whole Win API I would think without having a way to define dependencies. Resources sometimes form a hierarchy and have to be closed in that order e.g. CloseServiceHandle on the service then the manager. But it's hard to do as eventually you are doing reference dependency checking similar to a garbage collector. M$FT wrapped the Win API using MFC for that reason as they could construct the hierarchy and encapsulate handles. Not to say this technique isn't usable in some common and simple everyday scenarios.

WhoCares 06-12-2024 19:19

There is no silver bullet :D

Such unique/scope resource managers never consider dependencies.

There is also std::experimental::scope_exit() for RAII.


Quote:

Originally Posted by chants (Post 131100)
Is it hard to abstract the whole Win API I would think without having a way to define dependencies. Resources sometimes form a hierarchy and have to be closed in that order e.g. CloseServiceHandle on the service then the manager. But it's hard to do as eventually you are doing reference dependency checking similar to a garbage collector. M$FT wrapped the Win API using MFC for that reason as they could construct the hierarchy and encapsulate handles. Not to say this technique isn't usable in some common and simple everyday scenarios.



All times are GMT +8. The time now is 09:00.

Powered by vBulletin® Version 3.8.8
Copyright ©2000 - 2026, vBulletin Solutions, Inc.
Always Your Best Friend: Aaron, JMI, ahmadmansoor, ZeNiX