Problem ze wskaźnikami na funkcje

Problem ze wskaźnikami na funkcje
Kamil B
  • Rejestracja:ponad 4 lata
  • Ostatnio:ponad 4 lata
  • Postów:80
0

(VS2017, GLFW, GLAD, OpenGL)

Posiadam taką klasę:

Kopiuj
class Klasa
{
private:
    //składowe prywatne
public:
    void framebuffer_size_callback(GLFWwindow* win, int w, int h);
};

Definicja void framebuffer_size_callback(GLFWwindow* win, int w, int h);

Kopiuj
void framebuffer_size_callback(GLFWwindow* win, int w, int h)
{
    glViewport(0, 0, w, h)
}

Następnie w innym fragmencie kodu chcę przekazać wskaźnik na tą funkcję do innej funkcji:

Kopiuj
    glfwSetFramebufferSizeCallback(win /* jakaś zmienna wcześniej zadeklarowana*/, this->framebuffer_size_callback /* przy samym 
    framebuffer_size_callback wyskakuje inny błąd*/);

IntelliSense się przymyka, ale jak zaczynam kompilację, wyskakuje błąd:
"niestandardowa składnia; użyj znaku „&”, aby utworzyć wskaźnik do składowej"

O co chodzi? Funkcja framebuffer_size_callback() musi być składową klasy Klasa ponieważ korzysta z jej składowych, (niekoniecznie prywatnych) a przez parametry referencji do obiektu przekazać nie mogę bo mam ściśle określony prototyp.

kq
Moderator C/C++
  • Rejestracja:prawie 12 lat
  • Ostatnio:dzień
  • Lokalizacja:Szczecin
0

Masz użyć

Kopiuj
&Klasa::framebuffer_size_callback 

Przy czym jest to wskaźnik typu

Kopiuj
void (Klasa::*)(GLFWwindow*, int, int)

a twoje api pewnie oczekuje

Kopiuj
void(*)(GLFWwindow*, int, int)

edytowany 1x, ostatnio: kq
Kamil B
  • Rejestracja:ponad 4 lata
  • Ostatnio:ponad 4 lata
  • Postów:80
0

Teraz kompilator krzyczy: GLFWframebuffersizefun glfwSetFramebufferSizeCallback(GLFWwindow *,GLFWframebuffersizefun)”: nie można dokonać konwersji argumentu 2 z „void (__cdecl Klasa::* )(GLFWwindow *,int,int)” do „GLFWframebuffersizefun",

przy czym GLFWframebuffersizefun to: typedef void (*GLFWframebuffersizefun)(GLFWwindow*, int, int)

edytowany 2x, ostatnio: kq
kq
Moderator C/C++
  • Rejestracja:prawie 12 lat
  • Ostatnio:dzień
  • Lokalizacja:Szczecin
0

Czyli to co pisałem. Nie możesz przekazać niestatycznej funkcji klasy, bo to inny typ. Zmień ją w funkcję statyczną lub użyj funkcji wolnej.


Kamil B
  • Rejestracja:ponad 4 lata
  • Ostatnio:ponad 4 lata
  • Postów:80
0

Statyczna być nie może, bo metoda musi mieć dostęp do danych związanych z obiektem, nie z klasą. Z funkcją wolną tak samo. Jest jakiś sposób na przekazanie obiektu do tej funkcji inaczej niż przez argumenty?

edytowany 1x, ostatnio: Kamil B
kq
Moderator C/C++
  • Rejestracja:prawie 12 lat
  • Ostatnio:dzień
  • Lokalizacja:Szczecin
0

Ustaw na swoim oknie user pointer za pomocą glfwSetWindowUserPointer(), a potem w callbacku go odzyskaj używając glfwGetWindowUserPointer()
https://www.glfw.org/docs/3.3/group__window.html#ga3d2fc6026e690ab31a13f78bc9fd3651


MarekR22
Moderator C/C++
  • Rejestracja:około 17 lat
  • Ostatnio:około 2 godziny
0

"C++ have pointers to functions, but C++ programmers do not use them if they are sane" Uncle bob

Jednak to glfwSetFramebufferSizeCallback to jest API C https://www.glfw.org/docs/latest/group__window.html#gab3fb7c3366577daef18c0023e2a8591f

Teraz twoje framebuffer_size_callback to jest metoda, a nie funkcja! Efekt jest taki, że jako funkcja ma dodatkowy ukryty parametr this i dlatego nie może być być skonwertowana do typu akceptowanego przez glfwSetFramebufferSizeCallback.

Najprostsze rozwiązanie to uczynić framebuffer_size_callback metodą statyczną (brak parametry this).
Problemy zaczynają się, jeśli chcesz mieć dostęp do pól obiektu twojej klasy Klasa. Niestety glfwSetFramebufferSizeCallback nie dostarcza standardowego rozwiązania w postaci dowolnego kontekstu.

Jedyne znane mi rozwiązanie, jest brzydkie i straszne, dodać stan globalny, który zmapuje wskaźnik GLFWwindow * do wskaźnika twojej klasy.

Inne mniej paskudne, to niech twoja klasa dziedziczy po GLFWwindow, wtedy konwersja będzie możliwa:

Kopiuj
class Klasa : public GLFWwindow
{
public:
      void framebuffer_size_callback(int, int); // może być potrzebne extern "C"  lub __cdecl
      static void framebuffer_size_callback2(GLFWwindow *self, int x, int  y) {
             static_cast<Klasa>(self)->framebuffer_size_callback(x, y);
      }

      void registerCallback() {
             glfwSetFramebufferSizeCallback(this, &Klasa::framebuffer_size_callback);
             //albo
             glfwSetFramebufferSizeCallback(this, &Klasa::framebuffer_size_callback2);
      }
};



Jeśli chcesz pomocy, NIE pisz na priva, ale zadaj dobre pytanie na forum.
edytowany 3x, ostatnio: MarekR22
Zobacz pozostały 1 komentarz
DO
"C++ have pointers to functions, but C++ programmers do not use them if they are sane" ciekaw, nie znałem tego. Ale jak to ma się np. do operowania na sygnałach i slotach? Tam przecież przekazuje się slot w posatci wskaźnika do funkcji.
MarekR22
Nie no sygnały i sloty to nie są po prostu wskaźniki. Pierwotnie w Qt było używane makra SIGNAL i SLOT do identyfikacji sygnału/slotu. To, że teraz podaje się wskaźniki, jest tylko innym sposobem odnalezienia slotu/sygnału, pod spodem dzieje się co bardziej skomplikowanego. Źródło: https://youtu.be/zHiWqnTWsn4?t=2889
DO
ale to mówisz o Qt. Ja jakiś czas temu pisałem dla siebie własny system sygnałów i slotów i używałem wskaźników do funkcji, aby móc to jakoś przechowywać. Oczywiście nie korzystałem z "gołych" wskaźników tylko z std::function. Więc w sumie pytanie czy chodzi właśnie o "gołe wskaźniki" czy również o std::function?
MarekR22
Uncle Bob miał na myśli gołe wskaźniki na funkcje. Pod spodem std::function sygnałów/slotów w tym lub innej wersji kryją się wskaźniki.
DO
ok, to teraz ma to rzeczywiście sens. Dzięki.
Kamil B
  • Rejestracja:ponad 4 lata
  • Ostatnio:ponad 4 lata
  • Postów:80
0

Nie za bardzo rozumiem jak miałoby mi to pomóc w rozwiązaniu mojego problemu. Mógłbyś mi to bardziej wytłumaczyć? Uczę się właśnie OpenGLa.

kq
Moderator C/C++
  • Rejestracja:prawie 12 lat
  • Ostatnio:dzień
  • Lokalizacja:Szczecin
2

glfwSetFramebufferSizeCallback nie oferuje możliwości przekazywania kontekstu. Jedyne miejsce, gdzie możesz to zrobić to obiekt GLFWindow, więc będzie to wyglądało jakoś tak:

Kopiuj
glfwSetWindowUserPointer(win, this);
glfwSetFramebufferSizeCallback(win, &Klasa::framebuffer_size_callback);

A sama funkcja musi być wolna/statyczna:

Kopiuj
/* static */
void Klasa::framebuffer_size_callback(GLFWwindow* win, int w, int h)
{
    Klasa* this_ = static_cast<Klasa*>(glfwGetWindowUserPointer(win));
    glViewport(0, 0, w, h)
}

Kamil B
  • Rejestracja:ponad 4 lata
  • Ostatnio:ponad 4 lata
  • Postów:80
0

Czyli funkcja glfwSetWindowUserPointer ustawia mi coś w rodzaju takiego kontenera na moje dane i GLFW ignoruje jego zawartość?

edytowany 1x, ostatnio: Kamil B
kq
Moderator C/C++
  • Rejestracja:prawie 12 lat
  • Ostatnio:dzień
  • Lokalizacja:Szczecin
0

Nie, ta funkcja ustawia callback na operację resize okna. Przecież to jest napisane w dokumentacji.


Kamil B
Sorry pomyliłem się chodziło mi o glfwSetWindowUserPointer
kq
Zgodnie z nazwą, ustawia (dowolny) wskaźnik użytkownika na oknie, właśnie do takich celów.
Kamil B
Ok, już rozumiem dzięki za pomoc
Kamil B
Kurczę ta funkcja jest świetna...
_13th_Dragon
  • Rejestracja:ponad 19 lat
  • Ostatnio:3 miesiące
0

WP polecenie:

Kopiuj
#include <iostream>
using namespace std;

class Klasa
{
	private:
    int x;
	public:
	Klasa(int x):x(x){}
    void callback(int w, int h)
    {
    	cout<<x<<endl;
    }
};

class Caller
{
	private:
	Klasa *klasa;
	public:
	Caller(Klasa *klasa):klasa(klasa) {}
	void call(int w, int h) { klasa->callback(w,h); }
};


int main()
{
	Klasa k(13);
	Caller caller(&k);
	caller.call(666,666);
	return 0;
}

Wykonuję programy na zamówienie, pisać na Priv.
Asm/C/C++/Pascal/Delphi/Java/C#/PHP/JS oraz inne języki.
99xmarcin
  • Rejestracja:około 5 lat
  • Ostatnio:5 miesięcy
  • Postów:2420
0

Lepiej późno niż wcale :trollface:

Kopiuj
#include <cstdio>
#include <cstdlib>
#include <memory>
#include <cstring>

typedef unsigned char* uint8_t;

class Klass {
    public:
        Klass(int value): _value(value) { }

        void set_value(int value);

        void print_value() {
            printf("value = %d\n", this->_value);
        }

    private:
        int _value;
};

void thunk__Klass__set_value(int value) {
    Klass* this_ptr = (Klass*) 0x11aabbccddeeff11;
    this_ptr->set_value(value);
}

// Checked using disassembler/Ghidra
#define THUNK_SIZE (0x6f6 - 0x6ca + 1 + 100)

// This MUST be put after thunk (or in diff file) - to force linker to use full addresses (instead of relative)
// in CALL op-code
void Klass::set_value(int value) { this->_value = value; }

typedef void (*fptr_t)(int);

int main() {
    Klass klass(7);

    klass.set_value(107);
    klass.print_value();

    // Create in-memory copy of thunk__Klass__set_value
    uint8_t* mem = (uint8_t *) malloc(THUNK_SIZE+1); // Will be aligned
    if (mem == 0) {
        printf("error: cannot malloc");
        exit(1);
    }
    memcpy((void *)mem, (void *)&thunk__Klass__set_value, THUNK_SIZE);
    mem[THUNK_SIZE] = 0x00;    

    printf("overwrite placeholder\n");
    Klass** tmp = (Klass**) strstr((char*)mem, "\x11\xff\xee\xdd\xcc\xbb\xaa");
    if (tmp == 0) {
        printf("error: cannot find placeholder\n");
        exit(2);
    }
    else {
        *tmp = &klass;
    }
   
    // Voila we have our own custom thunk that will call class method
    fptr_t fptr = (fptr_t)mem;

    fptr(33);
    printf("after call\n");
    klass.print_value();

    fptr(42);
    printf("after call\n");
    klass.print_value();

    return 0;
}

Skompilowane na Linuxie:

Kopiuj
gcc  -fno-stack-protector -z execstack main.cpp

Daje:

Kopiuj
value = 107
overwrite placeholder
after call
value = 33
after call
value = 42

:trollface:


Holy sh*t, with every month serenityos.org gets better & better...
edytowany 2x, ostatnio: 99xmarcin
MarekR22
Moderator C/C++
  • Rejestracja:około 17 lat
  • Ostatnio:około 2 godziny
1

@0xmarcin nie wiem co to ma udowadniać?
Twój kod zadziała, tylko dla konkretnego kompilatora i na dodatek dla konkretnych opcji kompilatora.
Jako, że thunk__Klass__set_value jest klasycznym przykładem Undefined Behavior, kompilator ma prawo usunąć cały ten kod.

Ani nie jest to "pro", ani pomocne, tylko mąci w głowie początkującemu. Mi to wygląda na nieudaną próbę "szpanu".


Jeśli chcesz pomocy, NIE pisz na priva, ale zadaj dobre pytanie na forum.
99xmarcin
Wrzuciłem to ten kod ma jednak pewną wartość edukacyjną. I pokazuje jak by to kompilator mógł zrobić. Być może następna wersja C++ będzie już pozwalała na konwersję lambd na waskaźniki do funkcji....
99xmarcin
Poza tym miałem fun jak pisałem...
kq
C++11 pozwala na konwersję lambd na wskaźniki do funkcji.
99xmarcin
@kq: "A lambda can only be converted to a function pointer if it does not capture"
kq
No tak. Nie napisałeś, że ma być stateful. Zresztą, zdecydowanie preferowałbym zobaczyć jakiś przykład użycia libjit niż konfudnowanie newba.
Kliknij, aby dodać treść...

Pomoc 1.18.8

Typografia

Edytor obsługuje składnie Markdown, w której pojedynczy akcent *kursywa* oraz _kursywa_ to pochylenie. Z kolei podwójny akcent **pogrubienie** oraz __pogrubienie__ to pogrubienie. Dodanie znaczników ~~strike~~ to przekreślenie.

Możesz dodać formatowanie komendami , , oraz .

Ponieważ dekoracja podkreślenia jest przeznaczona na linki, markdown nie zawiera specjalnej składni dla podkreślenia. Dlatego by dodać podkreślenie, użyj <u>underline</u>.

Komendy formatujące reagują na skróty klawiszowe: Ctrl+B, Ctrl+I, Ctrl+U oraz Ctrl+S.

Linki

By dodać link w edytorze użyj komendy lub użyj składni [title](link). URL umieszczony w linku lub nawet URL umieszczony bezpośrednio w tekście będzie aktywny i klikalny.

Jeżeli chcesz, możesz samodzielnie dodać link: <a href="link">title</a>.

Wewnętrzne odnośniki

Możesz umieścić odnośnik do wewnętrznej podstrony, używając następującej składni: [[Delphi/Kompendium]] lub [[Delphi/Kompendium|kliknij, aby przejść do kompendium]]. Odnośniki mogą prowadzić do Forum 4programmers.net lub np. do Kompendium.

Wspomnienia użytkowników

By wspomnieć użytkownika forum, wpisz w formularzu znak @. Zobaczysz okienko samouzupełniające nazwy użytkowników. Samouzupełnienie dobierze odpowiedni format wspomnienia, zależnie od tego czy w nazwie użytkownika znajduje się spacja.

Znaczniki HTML

Dozwolone jest używanie niektórych znaczników HTML: <a>, <b>, <i>, <kbd>, <del>, <strong>, <dfn>, <pre>, <blockquote>, <hr/>, <sub>, <sup> oraz <img/>.

Skróty klawiszowe

Dodaj kombinację klawiszy komendą notacji klawiszy lub skrótem klawiszowym Alt+K.

Reprezentuj kombinacje klawiszowe używając taga <kbd>. Oddziel od siebie klawisze znakiem plus, np <kbd>Alt+Tab</kbd>.

Indeks górny oraz dolny

Przykład: wpisując H<sub>2</sub>O i m<sup>2</sup> otrzymasz: H2O i m2.

Składnia Tex

By precyzyjnie wyrazić działanie matematyczne, użyj składni Tex.

<tex>arcctg(x) = argtan(\frac{1}{x}) = arcsin(\frac{1}{\sqrt{1+x^2}})</tex>

Kod źródłowy

Krótkie fragmenty kodu

Wszelkie jednolinijkowe instrukcje języka programowania powinny być zawarte pomiędzy obróconymi apostrofami: `kod instrukcji` lub ``console.log(`string`);``.

Kod wielolinijkowy

Dodaj fragment kodu komendą . Fragmenty kodu zajmujące całą lub więcej linijek powinny być umieszczone w wielolinijkowym fragmencie kodu. Znaczniki ``` lub ~~~ umożliwiają kolorowanie różnych języków programowania. Możemy nadać nazwę języka programowania używając auto-uzupełnienia, kod został pokolorowany używając konkretnych ustawień kolorowania składni:

```javascript
document.write('Hello World');
```

Możesz zaznaczyć również już wklejony kod w edytorze, i użyć komendy  by zamienić go w kod. Użyj kombinacji Ctrl+`, by dodać fragment kodu bez oznaczników języka.

Tabelki

Dodaj przykładową tabelkę używając komendy . Przykładowa tabelka składa się z dwóch kolumn, nagłówka i jednego wiersza.

Wygeneruj tabelkę na podstawie szablonu. Oddziel komórki separatorem ; lub |, a następnie zaznacz szablonu.

nazwisko;dziedzina;odkrycie
Pitagoras;mathematics;Pythagorean Theorem
Albert Einstein;physics;General Relativity
Marie Curie, Pierre Curie;chemistry;Radium, Polonium

Użyj komendy by zamienić zaznaczony szablon na tabelkę Markdown.

Lista uporządkowana i nieuporządkowana

Możliwe jest tworzenie listy numerowanych oraz wypunktowanych. Wystarczy, że pierwszym znakiem linii będzie * lub - dla listy nieuporządkowanej oraz 1. dla listy uporządkowanej.

Użyj komendy by dodać listę uporządkowaną.

1. Lista numerowana
2. Lista numerowana

Użyj komendy by dodać listę nieuporządkowaną.

* Lista wypunktowana
* Lista wypunktowana
** Lista wypunktowana (drugi poziom)

Składnia Markdown

Edytor obsługuje składnię Markdown, która składa się ze znaków specjalnych. Dostępne komendy, jak formatowanie , dodanie tabelki lub fragmentu kodu są w pewnym sensie świadome otaczającej jej składni, i postarają się unikać uszkodzenia jej.

Dla przykładu, używając tylko dostępnych komend, nie możemy dodać formatowania pogrubienia do kodu wielolinijkowego, albo dodać listy do tabelki - mogłoby to doprowadzić do uszkodzenia składni.

W pewnych odosobnionych przypadkach brak nowej linii przed elementami markdown również mógłby uszkodzić składnie, dlatego edytor dodaje brakujące nowe linie. Dla przykładu, dodanie formatowania pochylenia zaraz po tabelce, mogłoby zostać błędne zinterpretowane, więc edytor doda oddzielającą nową linię pomiędzy tabelką, a pochyleniem.

Skróty klawiszowe

Skróty formatujące, kiedy w edytorze znajduje się pojedynczy kursor, wstawiają sformatowany tekst przykładowy. Jeśli w edytorze znajduje się zaznaczenie (słowo, linijka, paragraf), wtedy zaznaczenie zostaje sformatowane.

  • Ctrl+B - dodaj pogrubienie lub pogrub zaznaczenie
  • Ctrl+I - dodaj pochylenie lub pochyl zaznaczenie
  • Ctrl+U - dodaj podkreślenie lub podkreśl zaznaczenie
  • Ctrl+S - dodaj przekreślenie lub przekreśl zaznaczenie

Notacja Klawiszy

  • Alt+K - dodaj notację klawiszy

Fragment kodu bez oznacznika

  • Alt+C - dodaj pusty fragment kodu

Skróty operujące na kodzie i linijkach:

  • Alt+L - zaznaczenie całej linii
  • Alt+, Alt+ - przeniesienie linijki w której znajduje się kursor w górę/dół.
  • Tab/⌘+] - dodaj wcięcie (wcięcie w prawo)
  • Shit+Tab/⌘+[ - usunięcie wcięcia (wycięcie w lewo)

Dodawanie postów:

  • Ctrl+Enter - dodaj post
  • ⌘+Enter - dodaj post (MacOS)