Etiket arşivi: Delphi

SendMessage() ile Uygulamalar Arası Veri Paylaşımı

Bazen, iki uygulama arasında veri alış verişi yapmam gerekiyor. Örneğin zamanında bir donanımla haberleşen bir program yazmışız ama sonra aynı donanımdan alınan veriyle başka bir şey yapan uygulamaya ihtiyacımız oluyor. Veya bir cihazdan gelen verileri kaydeden bir uygulamada radikal bir değişiklik isteniyor ve söz gelimi önceden binary dosyaya yaptığımız kaydı artık bir veritabanına yapmamız gerekiyor gibi.
Burada da belki örneğini vereceğim bir başka örnek durumda da, bazen bir programı denemek için normalde gerçek dünyadan gelecek olan verileri bilgisayarda simüle eden bir şeyler yazıyorum.

Uygulama hali hazırda dışarıdan aldığı verileri TCP ya da UDP üzerinden alıyorsa yerel sunucu/istemci üzerinden yine TCP/UDP haberleşmesi yapmak en basit yöntem. Ama daha genel bir uygulamalar arası veri iletişimi çözümü olarak Windows’un mesajlaşma mekanizmasını kullanmayı tercih ediyorum.

Windows’ta süreçlerin birbirleriyle asenkron olarak haberleşmesi için bir mesajlaşma yapısı var. Çeşitli olayları yanıtlayan nesneler yazarken bunları zaten kullanıyoruz. Ben özellikle TThread sınıfından türettiğim bazı veri işleme nesnelerinde mesajları veri giriş çıkışı için kullanıyorum. Bu mesajlaşma mekanizması aynı zamada uygulamalar arasında da veri iletişimi için kullanılabilir. Bunun için ben WM_COPYDATA mesajını gönderiyor/alıyorum:

WM_COPYDATA mesajının msg alanında TCopyDataStruct diye bir veri türü var. Mesaj içeriğinin aktarımını temelde bu record ayarlıyor.
Gönderici kısımda bu veri türünden bir değişkeni doldurup SENDMESSAGE( ) ile yayınlıyorum.

copyData: TCopyDataStruct;
// dataexchange ünitesindeki copyData değişkenini dolduruyorum:
dataexchange.copyData.dwData:= $0325;   // packet type identifier
dataexchange.copyData.cbData:= event_exchange.Size;
dataexchange.copyData.lpData:= event_exchange.Memory;

Windows.pas içinde tanımlı TCopyDataStruct diye bir tip var. Bu tipten bir değişken tanımlamam gerekiyor: copyData
Bunun dwData alanında mesajla iletilen veri tipini belirtiyorum.
cbData alanında da verilen pointer’ın işaretlediği verinin boyunu belirtiyorum.
lpData alanında da payload işaretçisini paylaşıyorum.
Bu arada, uygulama tarafındaki event_exchange nesnesi de bir TMemoryStream. Yollamak istediğim veriyi buraya yüklüyorum. Genel bir tanımlama olması için bir stream üzerinde çalışmayı uygun buldum. Yerel bir değişken tipi de tanımlayabilirdik.

SendMessage( ) ‘ı kullanmadan önce, hedeflediğimiz alıcı pencerenin o anda canlı olup olmadığına bakabiliriz. Bunun için bir başka windows api’si var:
FINDWINDOW( )
Bu fonksiyonun parametrelerinden söz etmem gerek: Harici bir uygulamanın penceresine erişmek için (buna Delphi’de form diyoruz) bize iki parametre gerekiyor. Biri hedef formun sınıf türü, diğeri de pencere (form) adı. FindWindow() api’si bu iki parametreyi istiyor ve bize dönüş olarak handle sayısını döndürüyor. Alıcı uygulama hangi platformda hazırlanmışsa, o platformun win api tanımını bilip bu iki değeri PChar olarak verebiliyor olmamız gerekir.

SendMessage( ) fonksiyonunun parametrelerinden biri FindWindow ile bulunan receiver_handle. sender_handle büyük ihtimalle gönderici ana formunun handle’ı olur. Daha sonra veri göndermek tek bir fonksiyon çağrısına kalır:

res := SENDMESSAGE(receiverHandle, WM_COPYDATA, Integer(Handle), Integer(@copyData));

Alıcı tarafta yapılması gereken, gönderici tarafta “alıcı form” olarak işaretlenen formun üstünde tanımlanmış bir mesaj yakalama procedure’ü tanımlamaktır:

 procedure WMCopyData(var Msg : TWMCopyData) ; message WM_COPYDATA;

WM_COPYDATA mesajını yakalaması için yazdığımız fonksiyona bir değişken parametre aktarmalıyız ki bu, Msg diye tanımladığım bir TWMCopyData tipinde bir kayıttır. Bu kayıt tipinin .copyDataStruct alanı göndericinin oluşturduğu copyData değişkeninin ta kendisidir!

  // gönderici handle numarası:
  sender:= Msg.From;
  // mesajın data içeriğinin dwData alanını mesaj tipini belirtmede kullanıyorum:
  packet_type:= Msg.CopyDataStruct.dwData;
  packet_size:= Msg.CopyDataStruct.cbData;
  if packet_type = MSG_EVENT_BUFFER then
  begin
   // paket işaretçisini cardinal tipli bir pointer'a eşliyorum:
    pc:= Msg.CopyDataStruct.lpData;  
    inc(pc);
    // ardından "asıl" payload geliyor (burada packet_size'a bakmak gerekebilir)
    pEvent:= PEventRec(pc);
    Process_Event_Data( pEvent , packet_size );
  end;

Yukarıdaki koddan anlayabileceğiniz gibi, gönderici uygulamanın bize gönderdiği payload’u pEvent diye bir işaretçiyle alıyor ve onu işleyen procedure’e veriyorum. En başta cardinal tipli bir sayaç verisi var, onu burada alıyorum. Payload birden çok Event datası içerebilir, o yüzden packet_size değerini de procedure’e veriyorum.

Bir arayüz simülasyonunda bu çalışmanın hayata geçmiş halini şuradan görebilirsiniz. Event receiver ve Event Simulator birbirinden bağımsız iki uygulama. Simulator ile geliştirmekte olduğum kullanıcı arayüzü ve kontrol birimini istediğim türde veriler ile besleyebiliyorum.

TNotifyEvent

Data üreten bir nesnem var: Diyelim ki buna haberleşme ile dışarıdan veri geliyor. Bu nesne üzerinde gerekli parsing işlerini yapıyorum, integrity check yapıyorum ve veriyi bir buffer’a yüklüyorum. Bu nesne bir formun parçası ya da ayrı bir ünitede tanımlı bir sınıf.
Öte yandan uygulamamda bu veriyi başka yerlerde, başka başka şekillerde kullanmam gerek. Mesela bir trend grafiğine çizim, bir faceplate nesnesini güncelleme ya da bu veriyi giriş kabul eden bir kontrol çevriminde….
Veriyi alma ve protokol dahilinde haberleşme kontrolünü yapma işini veriyi kullanma işinden basit biçimde ayırmak istiyorsam bunu şöyle yapıyorum:

Veriyle ilk uğraştığım yerdeki sınıfın private alanında bir TNotifyEvent değişken tanımlıyorum:


private
   FPrivateEvent : TNotifyEvent;

Sonra, nesnenin published alanında onMyEvent diye bir bir property tanımlıyorum:

published
   property onMyEvent: TNotifyEvent read FPrivateEvent write FPrivateEvent;

Dış dünyayı bu olayı kullanarak uyarmak istediğim yerde (mesela gelen veriyi tarayan parser’ın sorgularının başarılı döndüğü durumda) şunu yapıyorum:

   if Assigned(onMyEvent) then onMyEvent(self);

Bu olay tetiklendiğinde işlem yapmak istediğim yerde, öncelikle yapacağım işlemi içeren procedure’ü tanımlıyorum:

   private
     procedure ProcessMyEvent(Sender: TObject);

Birden çok kaynak nesne söz konusuysa buradaki sender parametresini ayırt etmede kullanabilirim. Öte yandan, burada veriyi taşıma gereksinimi duymadığım, sadece olayın tetiklendiğini belirtmek istediğim durumdaki event tetiklemesini anlattım. Eğer datayı taşımam gerekiyorsa kendi event handler’ımı oluşturabilirim. Bu başka bir hikaye…

Eğer handler ayrı bir formda tanımlıysa o formun başlangıcında (onActivate iyi bir yer, eğer ana form üzerinde tanımlama yapıyorsak creation order canımızı sıkmamış olur) elimizdeki event handler procedure’ünü (ProcessMyEvent) veri kaynağı olan nesnenin event’ine atamamız gerek:

   mySourceObject.onMyEvent:= userform.ProcessMyEvent;

Ben bu yöntemi kendi cihazlarımla haberleştiğim PC programlarını geliştirirken kullanıyorum. İşin haberleşme arayüzünü bir form (ya da görsel bile olması gerekmeyen bir nesne) üzerinde bir kere hallettikten sonra bunu hiç değiştirmeden farklı arayüzler ya da uygulamalarda aynı haberleşmeyi kullanabiliyorum.

Seninle niye konuşayım ki?

“Çok sosyal biri olmadığım” eleştirilerini alıyorum. Evet.. Çevremde görüp de sohbet etmeye can atmadığım, anlatacakları şeyleri hiç merak etmediğim pek çok insan var.

Bir şeyi yapabilecek durumda olmamakla o şeyi yapmayı tercih etmemek arasında çok büyük bir fark vardır. İnsanlar sonuç odaklı olmanın iyi bir şey olduğunu öğrenerek yetiştikleri için çoğu zaman bu ayrımın farkında olmazlar çünkü sonuçta iki durum da aynı sonucu doğurmaktadır.

Çevrenizde gördüğünüz namuslu insanların pek çoğu namussuzluk yapmaktan korktukları için namusludurlar. Bu insanlar için siz doğanıza kolay gelen bir şeyi yapmıyorsanız bu yapamadığınız içindir..

İnsanlarla niçin konuşuruz? Onları dinlemek niye hoşumuza gider? Sorunun, konuştuğunuz kişiye bağlı olarak pek çok cevabı olabilir.  Ama eğer cevaplardan biri kendini yalnız hissetmemekse bu çoğu durumda benim için kesinlikle geçerli olmuyor. İnsan, tercihlerinin arkasında durmalıdır.

Şu cümleye bir bakın:

viewconfig.datawatch[i]:=
       TBigBarFacePlate.Create(
       mainform.PageControl1.Pages[watchconfig[i].pageindex],
       i, watchconfig[i].left, watchconfig[i].top);

Delphi compiler’ı bununla ne kastettiğimi anlıyor. Bir hedefe indeksli olarak yönlenmenin ne demek istediğini çok sağlam bir biçimde biliyor. Ne istediğim çok somut. Kullandığım her sözcüğün ve noktalama işaretinin bir anlamı var. Bir şey ima etmiyorum. Manipülasyon yapmıyorum. Sadece önceden belirlenmiş kurallara bağlı kalarak derdimi ona anlatıyorum. O da anlıyor.  İşte bu, mükemmel iletişimdir. Ve bazı insanların neden “nerd” olmayı tercih ettiklerini, bazılarınınsa niye onları hiç anlayamadıklarını bize çok iyi anlatır.