[Builder C++] obsługa Message'ów

0

Witam.

Piszę program, który steruje pewnym urządzeniem. Konkretne komendy pochodzą z biblioteki dll.
Po wywołaniu konkretnej komendy muszę mieć pewność że wykonała się ona cała. Informacje zwrotne dostaje za pośrednictwem funkcji:

GatewayCallback(DWORD dwState, ULONG ulChannel, DWORD dwResult, LRESULT lResult);
{
//(...)
switch ( dwState )
            {
             case STATE_LOGIN:
             if ( dwResult == 1 )
               {
                Logged = TRUE;
               }
             break;
//(...)
}

Czekanie na Logged = true dzieje się tak:

#define WM_GATEWAYMESSAGE       (WM_USER+0x3333) // z dokumentacji
MSG msg;
Komenda_z_dll(..);  
do                    
{
    if (PeekMessage( &msg, NULL, WM_GATEWAYMESSAGE, WM_GATEWAYMESSAGE, PM_NOREMOVE) )
            {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
            }
}while (Logged == FALSE )
//..dalsza część programu

Stosując obsługe Message'ów, a nie jakiejś zwykłej pętli opóźniającej mam pewność, że nie blokuję wykonania komendy.

Mam pytanie czy wygląda to w porzadku bo niestety program zachowuje się tak jakby wpadał w pętle nieskonczoną (..zawias). Zmienna Logged jest globalna i na pewno wpada w TRUE (sprawdzałem innymi trywialnymi metodami).

Czy są jakieś obostrzenia w Builderze C++ (ver. 6) co do stosowania msg?

0

Główny problem był w tym, że nie ściągałeś komunikatów z kolejki. Druga sprawa to nie dałeś oddechu procesorowi.

#define WM_GATEWAYMESSAGE       (WM_USER+0x3333) // z dokumentacji
MSG msg;
hLoginEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
Komenda_z_dll(..);
//czekamy na nadejście komunikatu lub zasygnalizowanie zdarzenia hLoginEvent
while(MsgWaitForMultipleObjectsEx(1, &hEvent, FALSE, INFINITE, QS_ALLINPUT, MWMO_ALERTABLE) != WAIT_OBJECT_0)
{
   if(GetMessage( &msg, NULL, WM_GATEWAYMESSAGE, WM_GATEWAYMESSAGE, PM_NOREMOVE)) 
   {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
   }
}
CloseHandle(hLoginEvent); //niszczymy zdarzenie hLoginEvent (pod warunkiem, że nie będzie już używane!)
//..dalsza część programu

GatewayCallback(DWORD dwState, ULONG ulChannel, DWORD dwResult, LRESULT lResult);
{
//(...)
switch ( dwState )
            {
             case STATE_LOGIN:
             if ( dwResult == 1 )
               {
                SetEvent(hLoginEvent); //sygnalizujemy zdarzenie hLoginEvent
               }
             break;
//(...)
}
0

Fajno. Dzięki. Sprawdze.. na razie nie mam dostępu do kompilatora więc póki co sobie gdybam.

A jak do tego ma się Application->ProcessMessages(); ?

Czy to:

Komenda_z_dll(..);  
do                    
{
    Application->ProcessMessages();

}while (Logged == FALSE )
//..dalsza część programu

.. ma prawo działać tak jak bym tego chciał (jak opisałem wcześniej)??

0

Wtedy pozostaje problem wydajności - nie dajesz wytchnienia procesorowi, bo pętla cały czas działa. Możesz skorzystać z rozwiązania zaproponowanego przeze mnie, tyle że zamiast

if(GetMessage ... { TranslateMessage ... DispatchMessage ... }

daćApplication->ProcessMessages();


Z tym, że coś mi się wydaj, że przekombinowałeś. Raczej powinieneś zrobić to bez osobnej pętli komunikatów (tj. bez Application->ProcessMessages()). Nie dasz rady tego "//..dalsza część programu" wrzucić w funkcję <i>GatewayCallback</i>? Opisz mniej więcej jaki efekt chcesz osiągnąć.
0

Chodzi o to, że jest kilka funkcji z DLL, które muszę wywołać jedną po drugiej. Ale żeby taka funkcja się wykonała cała, a co ważniejsze żeby w ogóle wystartowała to urządzenie musi być w stanie IDLE. W stan taki wskakuje tylko po całkowitym wykonaniu zadań przypisanych danej funkcji.

Mam więć pewną pule zadań, które musze wykonać i gwarantem wykonania takiego zadania jest kompletne i pomyślne wykonanie zadania poprzedniego. Dlatego muszę czekać.

0

To zaczynaj kolejne zadanie jak poprzednie się zakończy w GatewayCallback.
Jeśli nie da się wystartować nowego zadania zanim nie wyjdzie się z GatewayCallback to użyj komunikatów. Zdefiniuj sobie własne komunikaty, każdy przyporządkowany do zakończenia innego zadania. Jak jakieś zadanie się zakończy to wyślij komunikat do jakiegoś okna np. formy i tam go przechwyć np. przeładowując metodę WndProc.
A najlepiej jakbyś zrobił sobie do tego specjalne okno do samych komunikatów (AllocateHWnd), coś takiego:

class TFoo : public TObject
{
private:
   int FDane; //twoje dane, w którym miejscu jesteś, co już zostało zrobione, jaka jest oczekiwana wiadomość itp.
   HWND FHandle;

   static HWND FGlobalHandle;

   void __fastcall WindowProcedure(Messages::TMessage &Message)
   {
      switch(Message.Msg)
      {
         case MM_LOGIN:
         
         //...
      }
   }
public:
   TFoo()
   {
      FHandle = (FGlobalHandle == NULL) ? NULL : AllocateHWnd(WindowProcedure);
   }

   ~TFoo()
   {
      if (FHandle != NULL) 
      {
         DellocateHWnd(FHandle);
         FGlobalHandle = NULL;
      }
   }
   
   static HWND GetHandle() { return FGlobalHandle; }
};
HWND TFoo::FGlobalHandle = NULL;

//...
GatewayCallback(DWORD dwState, ULONG ulChannel, DWORD dwResult, LRESULT lResult);
{
//(...)
switch ( dwState )
            {
             case STATE_LOGIN:
             if ( dwResult == 1 )
               {
                   SendMessage(TFoo::GetHandle(), MM_LOGIN, 0, 0);
               }
             break;
//(...)
}

Oczywiście trzeba jeszcze utworzyć obiekt TFoo.

0

Sprawa powróciła. Szerze mówiąc, myślałem że dam rade zrobić to trywialnymi metodami.

W przykładzie powyżej występuje konflikt funkcji wirtualnej TFoo::~TFoo() z classą TObject.

Virtual function 'TFoo::~TFoo()' conflicts with base class 'TObject'

Czy wyrzucenie go w tym przypadku jest zdrowe?

0

Nie, wyrzucenie tego zapewne nie bedzie zdrowe, poniewaz mimo wszystko smieci warto po sobie sprzatac. Obejrzyj kod destruktora i sam pomysl nad odpowiedzia.

A jezeli zerknalbys na google na to co ten blad oznacza, to bys pewnie skojarzyl o co chodzi: ~TFoo nie pasuje do deklaracji destruktora klasy bazowej. Tak sie sklada, ze TObject deklaruje destruktor jako wirtualny (i slusznie!), a to Ciebie zobowiazuje, aby jego sygnatura Twoich dziedziczacych po TObject byla w 100% zgodna z ~TObject. Jesli jest on stdcall albo fastcall, to Twoj tez ma taki byc. Jesli jest on nothrow(), to Twoj tez ma taki byc, itp itd.

0

quetzalcoatl napisał:

...aby ...sygnatura Twoich dziedziczących po TObject byla w 100% zgodna z ~TObject

Dzięki za pouczenie. Dodałem _fastcall i działa.

Problem mam natomiast z

adf88 napisał:

void __fastcall WindowProcedure(Messages::TMessage &Message)
{
switch(Message.Msg)
{
case MM_LOGIN:

     //...
  }

}

TFoo()
{
FHandle = (FGlobalHandle == NULL) ? NULL : AllocateHWnd(WindowProcedure);
}

Niestety okno ( TFoo *MessWnd ) nie reaguje na komunikat wysłany z głównej formy: PostMessage(MessWnd->GetHandle(), MM_LOGIN, 0 , 0), dla przykładu:

      switch(Message.Msg)
      {
         case MM_LOGIN:
                     ShowMessage("Hello world!");
                     break;
      }

Czy nie powinienem zrobić tego nie na zasadzie przeładowania ale definicji własnej funkcji CALLBACK ?

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
   switch (message)
       {
        case MM_LOGIN:
        //...
        break;

        default:
            return DefWindowProc(hWnd, message, wParam, lParam);
        }
return 0;
}

Niestety zwraca błąd o niezgodności typów w metodzie AllocateHWnd:
Cannot convert 'long (__stdcall * (_closure )(void *,unsigned int,unsigned int,long))(void *,unsigned int,unsigned int,long)' to 'void (_fastcall * (_closure )(TMessage &))(TMessage &)'.
Czyli przeładowanie jednak void __fastcall WindowProcedure(Messages::TMessage &Message) jest słuszniejsze dla AllocateHWnd, tylko że nie śmiga.

0

hym.. ani na BCB ani na Delphi sie nie znam, ale google podalo takie cos:
http://www.delphidabbler.com/articles?article=1

The Delphi library function AllocateHWnd is used to create a hidden window for us and the related DeallocateHWnd disposes of the window when we've finished with it.

co sugeruje, ze Twoj FHandle:

FHandle = (FGlobalHandle == NULL) ? NULL : AllocateHWnd(WindowProcedure);

bedzie uchwytem do ukrytego okna, i do tego uchwytu i do tego okna wlasnie mialbys kierowac wiadomosci. Czy linia: PostMessage(MessWnd->GetHandle(), MM_LOGIN, ..

 gwarantowala, ze MessWnd->GetHandle() zwracalo tamto FHandle ?

jesli tak - to sie dziwię wielce.. na pewno AllocHWnd sie powiodl i FHandle jest poprawne? ( !=0, != -1 ?)

jezeli nie -- sprawdz, czy faktycznie chesz uzywac AllocHWnd, moze nie jej szukales. moze klasa bazowa na ktorej stworzyles klase Twojego okna ma metode WndProc ktora jest np. wirtualna i wystarczy ja przykryć swoją?
0

Problem częściowo rozwiązany. Mianowicie tworzę sobie własny komponent a w nim void __fastcall WndProc (TMessage& Msg); i ustawiam w konstruktorze FHandle = AllocateHWnd(WndProc). Jak wysyłam Msg do komponentu to WndProc go ładnie przechwytuje i wszystko działa. Mimo wszystko potrzebuje aby działało to tez w drugą stronę i potrzebuje przechwycić komunikaty wysłane od komponentu do formy głównej (TForm), a tu jak nazłość analogiczne rozwiązanie jak z definicją WndProc nie działa :/. A może tak jak radzi quetzalcoatl no ale nie potrafię znaleźć w Borlandzie tej wbudowanej pętli rozkazów. Widział ja ktoś gdzieś?

Odp.
Udało się. Definiuje własną WndProc w klasie TForm1. Wcześniej komunikaty nie były wysyłane od komponentu do formy z powodu globalnego uchwytu (zadeklarowany globalnie w MyComponent.h, zdefiniowany przez MyComponent::ReceiveHandler(HWND)), który reprezentować miał uchwyt do Form1. Teraz jak zrobiłem go elementem klasy MyComponent działa, choć nie rozumiem dlaczego wcześniej nie chciał.

1 użytkowników online, w tym zalogowanych: 0, gości: 1