Etiket arşivi: Object Pascal

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.