Allgemeine Handle-Template Klasse für init/uninit style API



  • Hallo,

    ich würde gerne eine Template schreiben, vom Prinzip her wie unique_ptr<> aber allgemeiner.

    Bsp.:

    OpenClipboard/CloseClipboard
    GlobalLock/GlobalUnlock
    GlobalAlloc/GlobalFree
    etc.
    

    diese kann ich dann so verwenden:

    {
        Handle<HGLOBAL, GlobalFree> hMem{GlobalAlloc(GMEM_MOVEABLE, len)};
        Handle<LPVOID, GlobalUnlock> lock{GlobalLock(hMem)};  // implicit conversion to HGLOBAL
        Handle<void, CloseClipboard> clipboard{OpenClipboard(nullptr)};  // denke das könnte schwierig werden
    // GlobalUnlock is implicitly called on Handle's LPVOID
    // GlobalFree is implicitly called on Handle's HGLOBAL
    }
    

    Es ist halt so, momentan schreibe ich für jedes Ding eine eigene Klasse, und irgendwie scheint mir das aber vom Prinzip her immer sehr ähnlich zu sein.

    Falls mein vorhaben zu kompliziert ist, habe ich auch kein Problem damit.

    LG
    HarteWare



  • Aha



  • Achso klar, keiner wird mir einfach den Code schreiben, ich muss erst selber eine Template entwerfen mit konkretem Problem wie konnte ich nur vergessen. 💡



  • template<class HandleType>
    struct ObjectWrapper
    {
    	HandleType Handle = 0;
    	void(*Deleter)(HandleType);
    
    	ObjectWrapper(HandleType handle, void(*del)(HandleType)) : Handle(handle), Deleter(del) {}
    	~ObjectWrapper()
    	{
    		if(Handle)
    			Deleter(Handle);
    	}
    
    	void release() { Handle = 0; }
    };
    

    Irgendwie sowas.



  • Techel schrieb:

    template<class HandleType>
    struct ObjectWrapper
    {
    	HandleType Handle = 0;
    	void(*Deleter)(HandleType);
    
    	ObjectWrapper(HandleType handle, void(*del)(HandleType)) : Handle(handle), Deleter(del) {}
    	~ObjectWrapper()
    	{
    		if(Handle)
    			Deleter(Handle);
    	}
    
    	void release() { Handle = 0; }
    };
    

    Irgendwie sowas.

    Vielen Dank für deine Antwort trotz schlechter Fragestellung. Funktioniert eigentlich recht gut, man muss nur schauen, dass man eventuell bestimmte Funktionen so "wrapped", dass sie einen "void" rückgabetyp haben (oder einen weiteren Template-Parameter für den Rückgabetyp einführen?).



  • Du kannst sicher noch ein Templateparameter für den Rückgabewert einfügen. Man könnte sogar die Funktion selbst als Templateparameter übergeben.



  • OK soweit habe ich jetzt das hier:
    Pro: Funktioniert soweit ich es mir vorgestellt habe, mit folgendem Zusatz:

    void close_clipboard_wrapper(BOOL) { CloseClipboard(); }
    

    Contra: Es gibt bestimmt viele Arten wie man das Ding falsch verwenden kann.

    template<class HandleType, class Ret>
    class Handle {
    public:
        using Deleter = std::function<Ret(HandleType)>;
        Handle(HandleType h, const Deleter& d)
            :handle{h}, deleter{d} { }
        ~Handle() { if (handle) deleter(handle); }
    
        operator HandleType() { return handle; }
        void release() { handle = nullptr; }
    
    private:
        HandleType handle;
        Deleter deleter;
    };
    // ...
    Handle<HGLOBAL, HGLOBAL> hMem{GlobalAlloc(GMEM_MOVEABLE, len), GlobalFree};
    Handle<BOOL, void> cb{OpenClipboard(nullptr), close_clipboard_wrapper};
    Handle<LPVOID, BOOL> lock{ GlobalLock(data), GlobalUnlock };
    

    Immerhin soll das nur zum Implementieren von anderen Dingen dienen, und nicht direkt als Interface.





  • Hi,

    ich hatte auch mal was fuer ein Projekt gebastelt:

    /**
    * For insperation @see http://stackoverflow.com/questions/14841396/stdunique-ptr-deleters-and-the-win32-api
    */
    struct HandleDeleter {
        using pointer = HANDLE ;
        void operator()(pointer ptr) {
            CloseHandle(ptr);		
        }       
    };
    namespace resource {
    
    	struct Handle {
    		using deleter = HandleDeleter;
    		using pointer = deleter::pointer;
    		static const deleter::pointer ErrorValue() { return INVALID_HANDLE_VALUE; };
    	};
    
    	struct NullHandle {
    		using deleter = HandleDeleter;
    		using pointer = deleter::pointer;
    		static const deleter::pointer ErrorValue() { return nullptr; };
    	};
    }
    
    template<typename T>
    using unique_resource = std::unique_ptr<typename T::pointer, typename T::deleter>;
    
    template<typename T>
    unique_resource<T> make_resource(typename T::pointer handle, bool throwOnError = true) {
    
    	if (throwOnError && handle == T::ErrorValue())
    		throw os::Exception("make_resource");
    
    	return unique_resource<T>(handle);
    }
    
    //Usage
    auto process = make_resource<resource::Handle>(
        OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid)
    );
    

    Der Trick dabei ist das der Deleter Type vom unique_ptr "pointer" definiert. Dadurch verwendet der unique_ptr nicht HANDLE* (also void**) sonder HANDLE.

    Vielleicht hilft dir das weiter.



  • Hallo,

    danke euch für eure Antworten, sehr interessant!

    Ich hatte zuerst auch eine release() Funktion geschrieben, welche den Handle zurück gibt. Ich sehe aber folgendes Problem, wenn man solchen Code schreibt:

    // Nun möchte eine C API wieder Besitz von der Resource ergreifen
    consume_resource(resource.release());
    

    Was ist, wenn consume_resource fehlschlägt? Ganz konkret, SetClipboardData übernimmt nur ownership, wenn erfolgreich. D.h., resource würde im Handle zwar auf nullptr gesetzt werden und damit nicht mehr duch die Handler Klasse freigegeben, jedoch würde das System ebenfalls sich nicht dazu verantwortlich fühlen. Dann würde man doch leaken, oder? Deshalb:

    if (consume_resource(resource))  // angenommen es wird mit return codes gearbeitet
        resource.release();
    else
        // error
    

    Ist mir jetzt nur aufgefallen, weil ich diese Situation genau vor ein paar Stunden hatte.

    Nochmals vielen Dank.

    LG


Anmelden zum Antworten