Bir Kiosk ve CreateDesktop macerası..

// 20 Mayıs 2009 // Delphi, Programlama

Bu yazıma gecenin bu saatinde başladığıma göre büyük ihtimalle yarın işe geç kalacağım. Ama tamamlandığında da bu konuyu 3 gündür makaleleştirememenin verdiği sıkıntıdan da kurtulmuş olacağım. Sanırım bu kafi derecede haz verir bana.

Hayatının herhangi bir anında herhangi bir husus hakkında birşeyler karalamış olanlar bilirler ki, bir şeyi biliyor olmak ile anlatabiliyor olmak cidden farklı. Bunlar farklı kaabiliyetler sanıyorum. Ve yine yazmaya gönlü kaymış kişiler bilir ki; bazen bir konuyu layıkı ile anlatamama endişesi “acaba hiç mi yazmasam” düşüncesine hayat verir. Bende böyle gitgeller yaşadım bu makale için ama elimden geldiğince kendi üslubum gereği aklıma gelen tüm detaylara da inerek bir cesaretle ya Allah diyip başlıyorum makaleye. Şimdiden okuyanların gözlerini ve zihinlerini yoracağım için helallik diliyorum.

Aslında anlatacaklarım tam bir kiosk uygulaması değil elbette. Ancak bir ucundan dokunuyor. Daha ziyade Unix türevli işletim sistemlerinin popüler KDE, Gnome gibi arayüzlerinde görmeye alıştığımız multi-desktop meselesini anlatmaya çalışacağım. Pek çok kullanıcı bazen muzurluk adına ve bazen de gerçekten işi gereği çoklu masaüstlerine ihtiyaç duyar. İşyerinde çalışırken, başkalarından gizlemek istediği programları başka bir masaüstünde barındırmak isteyen insanların sayısı gerçekten de az değildir. Hatta irili ufaklı pek çok uygulamada vardır sevgili patron ve müdürlerin gözlerinden sizleri korumak için :)

Bazı yazılımlar ise, muzurluk adına değil de yapmaları icap eden iş gereği başka masaüstlerinde çalışma ihtiyacı hissederler. Kiosk uygulamaları gibi. Bu uygulamalar genelde topluma açık yerlerde herkesin kullanımına sunulduklarından işletim sistemine müdahalenin mümkün mertebe sınırlandırılması gerekir. Bunun için en güzel çözümlerden bir tanesi uygulamanın kendisine has bir masaüstünde çalışmasıdır. Böylelikle bilerek yada bilmeyerek verilecek zararlar minimize edilmiş olur.

Tüm bu düşüncelerin ışığında, bir kaç gün evvel kendime ait bir masaüstü nasıl yaparım diye oturdum araştırma yaptım. Başta herşey iyi gidiyordu. İstediğimi yapabilmiştim. İşletim sistemi üzerinde bir masaüstü oluşturabilmiş, o masaüstüne geçebilmiş ve hatta uygulamamı yalnız başına orada çalıştırmayı başarabilmiştim. Buraya kadar herşey gayet iyi idi. Ancak ne oldu ise ondan sonra oldu :) Programlama sanatına gönül vermiş insanların uzun makaleleri okurken sıkıldıklarını, konudan uzaklaşma eğilimi gösterdiklerini gözlemledim programlama hayatım boyunca. Bu sebeple, kodsal içeriklere başlayarak ilerlemeyi sizlerin sıkılmaması adına tercih ediyorum.

Sizlere ilk bahsedeceğim API CreateDesktop API’si olacak: CreateDesktop API’si Win32 Programmers Referance’da aşağıdaki şekilde tanımlanmıştır.

HDESK CreateDesktop(
    LPCTSTR lpszDesktop,	// name of the new desktop 
    LPCTSTR lpszDevice,	        // reserved; must be NULL. 
    LPDEVMODE pDevMode,    // reserved; must be NULL
    DWORD dwFlags,	        // flags to control interaction with other applications
    DWORD dwDesiredAccess,	// specifies access of returned handle
    LPSECURITY_ATTRIBUTES lpsa	// specifies security attributes of the desktop
   );

Aynı API’nin Delphi’deki tanımı ise:

function CreateDesktop(lpszDesktop, lpszDevice: PChar;
  pDevmode: PDeviceMode; dwFlags: DWORD; dwDesiredAccess:
  DWORD; lpsa: PSecurityAttributes): HDESK; stdcall;

şeklindedir. Adından anlaşılabileceği gibi bu fonksiyon yeni bir masaüstü oluşturmaktadır. Fonksiyonumuzun birinci parametresi desktop’umuza vereceğimiz isimdir. İkinci ve üçüncü paratemtelerin Nil(Null) olması gerektiği bildirilmektedir yardım dökümanında. dwFlags isimli dördüncü parametre ise çalıştırılacak olan uygulamanın yeni desktopta diğer uygulamalar ile nasıl bir işbirliği yapacağını belirtirmiş. Ancak bunu nasıl yapar, derinlerinde ne gibi ipuçları vardır henüz bilmiyorum. Ancak yardım dosyasında denildiği kadarı ile dwFlags’ın alabileceği değer ya sıfır olabilirmiş yada DF_ALLOWOTHERACCOUNTHOOK olabilirmiş. Biz bu parametre için bu değeri kullanacağız. DF_ALLOWOTHERACCOUNTHOOK sabiti Windows.pas’da 1 değeri ile ifade edilmiştir.

dwDesiredAccess isimli parametremiz ise yeni oluşturacağımız desktopumuzun kaabiliyetlerini ifade edecek. Yine yardım dosyasında aşağıdaki değerlere sahip olabileceğini gözlemleyebilirsiniz:

  DESKTOP_READOBJECTS = 1;
  DESKTOP_CREATEWINDOW = 2;
  DESKTOP_CREATEMENU = 4;
  DESKTOP_HOOKCONTROL = 8;
  DESKTOP_JOURNALRECORD = $10;
  DESKTOP_JOURNALPLAYBACK = $20;
  DESKTOP_ENUMERATE = $40;
  DESKTOP_WRITEOBJECTS = 128;
  DESKTOP_SWITCHDESKTOP = $100;

Bu parametrenin alabileceği değerler son derece mühim. Yeni oluşturacağınız desktop’un yapabilecekleri bu parametrelerle belirleniyor. Biz desktopumuzun her türlü hakka sahip olmasını isteyeceğiz bu uygulamamızda. O sebeple aşağıdaki gibi bir tanım yapacağız:

const
  DESKTOP_ALL = 
                DESKTOP_READOBJECTS or 
                DESKTOP_CREATEWINDOW or
                DESKTOP_CREATEMENU or 
                DESKTOP_HOOKCONTROL or
                DESKTOP_JOURNALRECORD or 
                DESKTOP_JOURNALPLAYBACK or
                DESKTOP_ENUMERATE or 
                DESKTOP_WRITEOBJECTS or 
                DESKTOP_SWITCHDESKTOP;

Son parametremiz ise işletim sisteminde tanımlı olan ve genellikle hep nil bıraktığımız güvenlik parametresi. Bu parametre işletim sistemi güvenliği ile ilgili derin güvenlik bilgileri istiyor, açıkçası bu hususta az bir bilgim var o yüzden sizlere bu parametrenin detaylarını anlatamayacağım. Bu parametremizi de Nil(Null) olarak geçeceğiz. Tüm bu bilgilerin ışığında artık desktop’umuzu oluşturan kodumuzu yazabiliriz:

var
  NewDesktop : HDESK;
begin
  NewDesktop := CreateDesktop(PChar('NewDesktop'), nil, nil, DF_ALLOWOTHERACCOUNTHOOK, DESKTOP_ALL, nil);
  ..
  ..
end;

Evet artık işletim sistemi üzerine yeni bir masaüstü daha açtık. Ama tabii ki henüz onu göremiyoruz. Kaç tane masaüstümüz olduğunu da bilmiyoruz şu anda. Bazı arkadaşlarımın, “Nasıl bilmiyoruz ? Bir tane vardı bir de biz oluşturduk etti iki..” dediğinizi duyar gibiyim :) Yok yok iki tane değil ;)

İşletim sistemimizde mevcut birden fazla masaüstü var. Arzu ederseniz daha fazla ilerlemeden önce bu söylemimizin doğruluğunu test edelim. Bu bağlamda hemen Win32 API help’imizi açıyor ve CreateDesktop yazıyoruz. Bu fonksiyonun Group yada See also kısmına baktığımızda EnumDesktops isimli fonksiyonu göreceğiz. Adından da anlaşıldığı gibi bu fonksiyon bize işletim sistemindeki masaüstlerini verecek. Gelin birlikte tanımına bir göz atalım;

BOOL EnumDesktops(
    HWINSTA hwinsta,	// handle to window station to enumerate
    DESKTOPENUMPROC lpEnumFunc,	// points to application's callback function
    LPARAM lParam	// 32-bit value to pass to the callback function
   );

ve Delphi’deki tanımı ise aşağıdaki gibidir.

function EnumDesktops(hwinsta: HWINSTA; lpEnumFunc: TFNDeskTopEnumProc; lParam: LPARAM): BOOL; stdcall;

Bu fonksiyonumuzda yine açıklamakta zorlanacağım, henüz tam manası ile hakim olamadığım Window Statiton kavramına bir atıf var. Tanımda gözlemleyebileceğiniz birinci parametrenin tipine dikkat edin: HWINSTA. Yani bir window station tutacağı(handle). Peki Window Station da neyin nesi ? Window Station kendine has bir clipboard’u olan, kendine has global atom tablosu olan, içinde birden fazla masaüstüne sahip olabilen; klavye, mouse ve ekran ile etkileşim kurabildiğiniz bir nevi kapsayıcı güvenlik nesnesi. Bir makina üzerinde birden fazla Window Station olabiliyor. Ama sadece winsta0 olarak adlandırılan birinci Window Station’un interaktiflik özelliği bulunuyor. Bu güvenlik nesnesinin hangi ihtiyaçlara binaen var olduğunu kestirebildiğimi söylesem pek doğru bir kelam etmiş sayılmam. Ancak Terminal server’da her yapılan bağlantının kendi oturumunda(session) bir window station’a sahip olduğunu ve bunun interaktif(kullanıcı ile etkileşimli) olduğunu öğrenmiş bulunuyorum.

Bu parametre hakkında ilerleyen zamanlarda kendi öğrenebildiklerim kadarı ile yeni paylaşımlar yapmayı arzuluyorum, ama sanırım bu makalemiz için bu kadarı yeterlidir. Zaten kendisini pek de fazla kullanmayacağız. Biz yine işletim sistemimizde birden fazla olduğunu iddaa ettiğimiz masaüstlerimizin nasıl bulunacağı konusuna yani EnumDesktops‘a geri dönelim isterseniz. Birinci parametresini zor da olsa geçebildiğimiz fonksiyonun ikinci parametresi TFNDeskTopEnumProc türünde ve callback fonksiyon diye adlandırılan bir fonksiyon pointer’dır. Yani bir fonksiyona işaret eden bir işaretçi. Win32 API dökümanında aşağıdaki gibi tanımlanmıştır:

BOOL CALLBACK EnumDesktopProc(
    LPTSTR lpszDesktop,	// name of a desktop
    LPARAM lParam	// value specified in EnumDesktops call
   );

Delphi’ye ise aşağıdaki gibi çevireceğiz:

function DesktopProc(DesktopName : LPTSTR; Param : LPARAM) : Boolean; stdcall;

Metodun Delphi’deki tanımından sizinde gözlemleyebildiğiniz üzere metodun isminin bir önemi yoktur. Yani illa EnumDesktopProc olması lüzumu yoktur. Gerçi okunaklığı arttırmak adına genellikle API yardımındaki isimlendirme notasyonu tercih edilir ancak ben sizin özgür olduğunuzu ifade edebilmek adına farklı bir isimlendirmeye gittim. Üçüncü parametre ise EnumDesktops tarafından DesktopProc’a olduğu gibi geçilecek bir parametredir.

EnumDesktops fonksiyonu kısaca, kendisine geçilen ikinci parametredeki DesktopProc fonksiyonunu işletim sistemindeki her desktop nesnesi için bir kere çağıracaktır. Şimdi gelelim bu metodu nasıl kullanacağımıza:

  TDesktop = class
  private
    fDesktops : TStrings;
  public
    constructor Create;
    destructor Destroy; override;

    procedure Refresh;

    property Desktops : TStrings read fDesktops;
  end;

  function DesktopProc(DesktopName : LPTSTR; Param : LPARAM) : Boolean; stdcall;
  begin
    TDesktop(Param).Desktops.Add(DesktopName);
    Result := true; 
    // Burada true döndürmek önemlidir. Desktop arama işleminin devam etmesini sağlar, false döndürülmesi bu metodu çağıran EnumDesktops'un durmasına 
    //ve sonraki desktoplar için DesktopProc'u çağırmamasına neden olur.
  end;

  constructor TDesktop.Create;
  begin
    inherited Create;
    fDesktops := TStringList.Create;
    Refresh;
    ..
    ..
  end;

  procedure TDesktop.Refresh;
  begin
    Desktops.Clear;
    EnumDesktops(GetProcessWindowStation, @DesktopProc, Integer(Self));
  end;

  ..
  ..
  ..
  procedure TForm1.Button1Click(Sender : TObject);
  var
    Desktop : TDesktop;
  begin
    Desktop := TDesktop.Create;
    ListBox1.Items := Desktop.Desktops; // ListBox1 nesnesi TListBox türündedir.
    Desktop.Free;
  end;

Yukarıdaki kodumuzu çağırdımızda 3 adet desktop’un işletim sistemimizde varsayılan olarak bulunduklarını gözlemleyeceksiniz. Bunlar sırası ile Default, Disconnect ve WinLogon masaüstleridir. Default desktop’u; adından da anlayacağınız gibi hergün kullandığımız masaüstümüzdür. Disconnect desktop’u Terminal server bağlantılarında kullanılan desktop, son olarak da WinLogon desktop’u Win+L, Ctrl+Alt+Del ile geçtimiz kullanıcı girişinin olduğu masaüstümüzdür. Bu masaüstlerinin ilginç ve kısıtlayıcı bazı yanlarından ilerleyen satırlarda bahsedeceğim.

Bu arada son verdiğim kod örneğini ileride komple bir sınıf haline getireceğimiz TDesktop sınıfı üzerinden anlatmayı uygun buldum. En son kaldığımız noktada bir desktop create edebilmiştik. Ancak hala o desktop’a geçememiş ve birşeyler görememiştik. Gelin şimdi ekranda görebileceğimiz ve haz duyabileceğimiz desktoplar arası geçişi sağlayan metoda hayat verelim. Bunun için yine bir başka API fonksiyonu olan SwitchDesktop metodunu kullanacağız. Yine herzaman olduğu gibi tanımlarına bir göz gezdirelim:

BOOL SwitchDesktop(
    HDESK hDesktop	// handle of desktop to activate
   );	
 

ve yine herzamanki gibi Delphi tanımımız:

function SwitchDesktop(hDesktop: HDESK): BOOL; stdcall;

Nihayet son derece kısa bir tanıma sahip bir metodumuz oldu. :) Ancak yine de anlatmakta fayda var kanısındayım. Bu fonksiyon, CreateDesktop yada OpenDesktop API fonksiyonlarından geriye dönen desktop’un tutacağını(handle) kendisine parametre olarak alır ve anında belirtilen masaüstüne geçiş yapar. Bunun bir istisnası, geçiş yapılmak istenilen masaüstünün varsayılan Window Station‘da olmaması durumudur ki bu durumda hata verecektir. Şimdi müsaade ederseniz daha önce tanımına yer verdiğimiz CreateDesktop metodunu ve henüz içeriğini yazmadığımız SwitchDesktop metodunu ilgili sınıfımız TDesktop’a ekleyelim:

  TDesktop = class
    ..
    ..
    procedure CreateDesktop(const Name : String);
    procedure SwitchDesktop(const Name : String);
  end;

  procedure TDesktop.CreateDesktop(const Name : String);
  var
    Handle : HDESK;
  begin
    Handle := Windows.CreateDesktop(PChar(Name), nil, nil, DF_ALLOWOTHERACCOUNTHOOK, DESKTOP_ALL, nil);
    if Handle = 0 then
      raise Exception.Create(Name + ' isimli desktop oluşturulamadı.!');

    Desktops.Add(Name);
  end;

  procedure TDesktop.SwitchDesktop(const Name : String);
  var
    Index : Integer;
    Handle: HDESK;
  begin
    Index := Desktops.IndexOf(Name);
    if Index = -1 then
      raise Exception.Create(Name + ' isimli desktop mevcut değil.!');

    Handle := OpenDesktop(PChar(Name), DF_ALLOWOTHERACCOUNTHOOK, false, DESKTOP_ALL);
    if Handle = 0 then
      raise Exception.Create(Name + ' isimli desktop açılamadı.!');

    Windows.SwitchDesktop(Handle);
    CloseDesktop(Handle);
  end;

SwitchDesktop metodumuzda öncelikle geçiş yapmak istediğimiz desktop’un var olup olmadığını kontrol ediyor ardından da OpenDesktop metodu vasıtası ile o desktop’un handle’ını elde ediyoruz. Ardından SwitchDesktop’umuzu çalıştırıyoruz ve yeni masaüstümüz karşımızda. Sonrada hafıza kullanımına değer veren bir programcı olduğumuz için OpenDesktop ile hafızada ayırdığımız bir miktar yeri hemen serbest bırakıyoruz. Ancak sakın bu kodu hemen denemeyin. Biraz daha sabır istirham edeceğim sizlerden. Eğer bu kodu hemen bu hali ile denerseniz, benim defalarca yapmış olduğum gibi makinanızı restart etmekten başka bir çareniz kalmaz. İşletim sisteminde oluşturulan her desktop nesnesi kendi etki alanı içinde çalışmaktadır. Diğer desktoplardaki pencerelerle iletişim halinde değildir, onlarla haberleşemez. Alt+Tab çağrılarınız diğer desktop’taki pencereler arasında geçişinizi sağlamaz. Task Manager’ı açma çabalarınız da sonuçsuz kalacaktır. İşletim sisteminin bir bug’u olduğunu düşündüğüm Task Manager’ı açma çabası da aslında karşılıksız kalmaz ama Task Manager maalesef beklediğiniz desktop’da değil Default desktop’ta açılacaktır. Dolayısı ile bu kodu şu hali ile çalıştırmak, makinanızı restart etmeniz gerektiğinin güçlü bir delilidir. Makinamı 7-8 kez restart etmek durumunda kaldıktan sonra TDesktop sınıfına aşağıdaki metodu yazmayı uygun buldum;

  procedure TDesktop.SwitchDefaultDesktop;
  var
    Handle: HDESK;
  begin
    Handle := OpenDesktop(PChar('WinSta0\Default'), DF_ALLOWOTHERACCOUNTHOOK, false, DESKTOP_ALL);
    if Handle = 0 then // Çifte güvenlik. Bazı makinalarda WinSta0\Default isminde olabileceğini okudum, işi şansa bırakmak istemedim.
    begin
      Handle := OpenDesktop(PChar('Default'), DF_ALLOWOTHERACCOUNTHOOK, false, DESKTOP_ALL);
      if Handle = 0 then
        raise Exception.Create('Default desktop açılamadı.!');
    end;

    Windows.SwitchDesktop(Handle);
    CloseDesktop(Handle);
  end;

Artık arzu ederseniz ilk denemenizi yapabilirsiniz. Şimdi yazacağımız kod ile restart yapmak zorunda kalmayacaksınız. Buyurun bunca uğraşının sonunda artık desktop’umuzu görelim:

  procedure TForm1.Button1Click(Sender : TObject);
  var
    Desktop : TDesktop;
  begin
    Desktop := TDesktop.Create;
    Desktop.CreateDesktop('Deneme');
    Desktop.SwitchDesktop('Deneme');
    Sleep(25000); // 25 sn bekle..
    Desktop.SwitchDefaultDesktop;
    Desktop.Free;
  end;

Kodumuz Deneme isminde bir masaüstü oluşturacak ve ona geçiş yapacak, ardından 25 saniye boyunca siz desktopunuza bakarken varsayılan masaüstünüze geri döneceksiniz. Sizlerin de farkettiği gibi yeni oluşturduğumuz masaüstümüzde hiçbir şey yoktur. Ne masaüstü resmi, ne başlat menüsü, ne masaüstü simgeleri, ne de taskbar. Uygulamamızda orada değil. Şu hali ile bu masaüstü bizim hiç bir işimize yaramıyor. Bizim; yazacağımız uygulamaları yeni desktop’ta çalıştırabilmeye kesinlikle ihtiyacımız var. Aksi taktirde bunca anlatılanın hiç bir ehemmiyeti olmazdı. Arzu ederseniz onu da yapalım. İstediğimiz herhangi bir uygulamayı diğer bir masaüstünde çalıştırabilmek için CreateProcess isimli API metoduna gereksinimimiz olacak. Buyrun tanımına bakalım:

BOOL CreateProcess(
    LPCTSTR lpApplicationName,	// pointer to name of executable module 
    LPTSTR lpCommandLine,	// pointer to command line string
    LPSECURITY_ATTRIBUTES lpProcessAttributes,	// pointer to process security attributes 
    LPSECURITY_ATTRIBUTES lpThreadAttributes,	// pointer to thread security attributes 
    BOOL bInheritHandles,	// handle inheritance flag 
    DWORD dwCreationFlags,	// creation flags 
    LPVOID lpEnvironment,	// pointer to new environment block 
    LPCTSTR lpCurrentDirectory,	// pointer to current directory name 
    LPSTARTUPINFO lpStartupInfo,	// pointer to STARTUPINFO 
    LPPROCESS_INFORMATION lpProcessInformation 	// pointer to PROCESS_INFORMATION  
   );
function CreateProcess(lpApplicationName: PChar; lpCommandLine: PChar;
  lpProcessAttributes, lpThreadAttributes: PSecurityAttributes;
  bInheritHandles: BOOL; dwCreationFlags: DWORD; lpEnvironment: Pointer;
  lpCurrentDirectory: PChar; const lpStartupInfo: TStartupInfo;
  var lpProcessInformation: TProcessInformation): BOOL; stdcall;

Görüldüğü gibi pek çok parametresi var. Ben bunlardan birkaçına değinip diğerlerini araştırmayı siz değerli okuyucularıma bırakacağım. Bu parametrelerden en önemlisi lpCommandLine isimli olan peremetredir. Bu parametre çalıştırmak istediğimiz dosyanın path bilgisini yazacağımız yerdir. İkinci ve en can alıcı parametremiz ise lpStartupInfo isimli parametredir. Bu parametre üzerinde biraz durmamız gerekiyor. Herzamanki gibi TStartupInfo türünde olan parametremizin tanımına bir göz gezdirelim:

  _STARTUPINFOA = record
    cb: DWORD;
    lpReserved: Pointer;
    lpDesktop: Pointer;
    lpTitle: Pointer;
    dwX: DWORD;
    dwY: DWORD;
    dwXSize: DWORD;
    dwYSize: DWORD;
    dwXCountChars: DWORD;
    dwYCountChars: DWORD;
    dwFillAttribute: DWORD;
    dwFlags: DWORD;
    wShowWindow: Word;
    cbReserved2: Word;
    lpReserved2: PByte;
    hStdInput: THandle;
    hStdOutput: THandle;
    hStdError: THandle;
  end;

  TStartupInfo = _STARTUPINFOA;

Gördüğünüz gibi bu bir record. Bu record’un içerisinde uygulamız için hayati derecede öneme sahip olan bir parametre mevcut. O da; pointer türünde tanımlanmış olan lpDesktop. İşte bu bizim desktopumuzun adını geçeceğimiz yerden başkası değil. Bir diğer önemli parametre ise DWord tipli cb isimli parametredir. Bu parametreye de SizeOf(TStartupInfo) gibi bir atama yapacağız. Dilerseniz herhangi bir masaüstünde herhangi bir uygulamanın çalıştırılması becerisine haiz metodumuzu da sınıfımıza ekleyelim:

  procedure TDesktop.ExecuteProgram(const Desktop, ExeName: String);
  var
    Index : Integer;

    StartupInfo : TStartupInfo;
    ProcessInfo	: TProcessInformation;
  begin
    Index := Desktops.IndexOf(Desktop);
    if Index = -1 then
      raise Exception.Create(Desktop + ' isimli desktop mevcut değil.!');

    if not FileExists(ExeName) then
      raise Exception.Create(ExeName + ' isimli dosya mevcut değil.!');

    FillChar(StartupInfo, SizeOf(TStartupInfo), 0); // Recordun içeriğini temizliyoruz..
    StartupInfo.cb := SizeOf(TStartupInfo);
    StartupInfo.lpDesktop := PChar(Desktop); // PChar da bir pointer tipidir.

    if not CreateProcess(nil, PChar(ExeName), nil, nil, false, NORMAL_PRIORITY_CLASS, nil, nil, StartupInfo, ProcessInfo) then
      raise Exception.Create(ExeName + ' isimli uygulama ' + #13#10 + Desktop + ' isimli masaüstünde çalıştırılamadı.!');
  end;

Şimdi bir önceki örneğimize geri dönelim ve o anda çalışan uygulamamızı diğer masaüstünde de çalıştıralım:

  procedure TForm1.Button1Click(Sender : TObject);
  var
    Desktop : TDesktop;
  begin
    Desktop := TDesktop.Create;
    Desktop.CreateDesktop('Deneme');
    Desktop.SwitchDesktop('Deneme');
    Desktop.ExecuteProgram('Deneme', Application.ExeName);
    Sleep(25000); // 25 sn bekle..
    Desktop.SwitchDefaultDesktop;
    Desktop.Free;
  end;

Bu kodu deneyince göreceğiniz gibi, yeni bir masaüstünüz olacak ve çalışan uygulamanızın bir kopyası da diğer masaüstünde hazır halde sizi bekliyor olacaktır. Buraya kadar herşey sorunsuz(7-8 defa restart etmeyi saymaz isek) gitti. Herhangi bir mâni ile karşılaşmadık. Bundan sonra tercihlerinize göre yine karşılaşmayabilirsiniz. Ama sizlerde benim gibi hafıza kullanımı konusunda son derece titizseniz o zaman sizleri bazı sorunlar bekliyor. Benim bu makaleyi yazmaktan imtina ettiğim sorunlar. Herne kadar o sorunların hepsini aşsam da izahı son derece zor hususlar. Ancak yine de elimden geldiğince dilim döndüğünce izaha gayret edeyim.

Nedir bu sorunlar diye iyice meraklanmış olabilirsiniz. Sizleri daha fazla merakta bırakmadan anlatayım. Şimdi bir senaryo üzerinden hareket edelim. Bu senaryoda yeni oluşturduğumuz masaüstümüzde başlat menüsünü, masaüstü simgelerini ve görev çubuğunu görmek istediğimizi düşünelim. O zaman daha evvel yazdığımız ExecuteProgram metodumuz ile explorer.exe‘nin de ilgili masaüstünde çalıştırılması gerekecektir.

Bunu yapmamız kolay, ancak sorun çalıştırmış olduğumuz explorer.exe’nin sonlandırılması sırasında ortaya çıkacak. Zaten varsayılan masaüstümüzde çalışan bir explorer.exe’miz vardı, şimdi ikinci masaüstünde çalışan bir tane daha oldu ve biz hafızayı iktisatlı kullanmak istiyoruz. Dolayısı ile ikinci masaüstü için çalıştırdığımız explorer.exe’nin sonlandırılması gerekli.

Bu problem aklımıza daha önce kullanımını anlattığımız TStartupInfo yapısını getiriyor ve araştırmalara başlıyoruz. Acaba çalışan uygulamalardan elde edebileceğimiz TStarupInfo gibi bir yapı var mı ? Bu bağlamda API help’te dolaşırken GetStartupInfo gibi bir metoda rastlıyoruz. Tanımı aşağıdaki gibidir:

VOID GetStartupInfo(
    LPSTARTUPINFO lpStartupInfo 	// address of STARTUPINFO structure 
   );
procedure GetStartupInfo(var lpStartupInfo: TStartupInfo); stdcall;

Ancak incelemelerimiz bu metodun o anda çalışan uygulamanın bilgilerini döndüreceğini söylüyor. Oysaki bizim explorer.exe’nin bilgilerine ihtiyacımız vardı.! İşte bu noktada aslında bir başka makalenin konusu olabilecek kadar derin bir araştırma ve ayrıma gitmemiz gerekiyor. Uzunca bir müddet düşündükten sonra, sonlandırmak istediğimiz explorer.exe’nin hangisi olduğuna karar verilemediği için hafızada çalışır halde bulunan her iki uygulamaya da bulaşan bir dll yazmaya karar verdim. Bu DLL ilgili exe’lere bulaştıktan sonra GetStartupInfo metodunu o exe içinde çalıştırmalı ve sonucu explorer.exe’yi çalıştıran uygulamaya yada diğer masaüstünde hali hazırda çalışan uygulamamızın kopyasına göndermeliydi. Hafızada çalışan uygulamaya bulaşan kodu uzunca bir araştırma sürecinden sonra mantığı ile anlayıp kodlamıştım ancak bu sefer de bulaşan DLL’in bulaştığı kaynaktan silinmesi gerekiyordu.

diagram

Bu sorun da aşıldıktan sonra, artık hafızaki istediğimiz herhangi bir uygulamaya yüklenebilen ve tekrar silinebilen bir mekanizmaya sahiptik. Artık bu mekanizma bize bulaştığı uygulamaların içinde GetStartupInfo metodundan elde ettiği değerli desktop name bilgisini döndürebilirdi. Bir DLL ile bir EXE’nin birbirleri arasında haberleşme birden fazla yöntem ile sağlanabilir. Bu tarz işlemlere genellikle Interprocess Comminication (IPC) adı verilir. Hafızada paylaşılan bir alana yazılan veriler diğer uygulama tarafından görünür kılınabilir, yada daha basit bir yöntem ile DLL ile EXE arasında mesajlaşma API’leri vasıtası ile(SendMessage, PostMessage) haberleşilebilir. Ancak burada kesinlikle unutulmaması gereken bir şeyi daha önce söylediğim halde yeniden ifade etmekte yarar görüyorum. Birbirinden farklı desktoplardaki uygulamalar, birbirleri ile bu şekillerde kesinlikle haberleşemezler. Bu durum hasıl olduğunda socket haberleşmesi daha genel ve etkin bir çözüm olabilir. Bu vesile ile bende iletişim yöntemi olarak socket haberleşmesini tercih ettim. Böylece hangi desktop’ta olduğunuzdan bağımsız olarak sistemin hatasız çalışmasını garanti altına almış olduk.

Sorun üzerinde bunca dönüp dolaştıktan sonra birazda çözüme doğru yol alalım.Şimdi yapmamız gereken 3 şey var.

  • Çalışan uygulamalara bulaşan bir kod yazmak.
  • Çalışan uygulamalara bulaşan kodun temizlik kodunu yazmak
  • Sonlandırmak istediğimiz process’in bulunmasına müteakip çalışacak sonlandırma kodunu yazmak.
  • Sırası ile hepsini yazalım:

    procedure InjectProcess(const RemoteProcessID : Cardinal; const DLLName : String);
    var
      RemoteProcessHandle,
      BytesWritten,
      ThreadID,
      RemoteThreadHandle : Cardinal;
      pDLL : Pointer;
    begin
      if not FileExists(DLLName) then
        raise Exception.Create(InttoStr(RemoteProcessID) + '->' + DLLName + ' bulunamadı.!');
    
      RemoteProcessHandle := OpenProcess(PROCESS_ALL_ACCESS, false, RemoteProcessID);
    
      if RemoteProcessHandle <> 0 then
      begin
        pDLL := VirtualAllocEx(RemoteProcessHandle, nil, Length(DLLName), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    
        if Assigned(pDLL) then
          if WriteProcessMemory(RemoteProcessHandle, pDLL, PChar(DLLName), Length(DLLName), BytesWritten) then
          begin
            RemoteThreadHandle := CreateRemoteThread(RemoteProcessHandle, nil, 0, GetProcAddress(GetModuleHandle('kernel32.dll'), 'LoadLibraryA'), pDLL, 0, ThreadID);
            if RemoteThreadHandle <> 0 then
            begin
              WaitForSingleObject(RemoteThreadHandle, 2000);
              CloseHandle(RemoteThreadHandle);
            end;
          end;
      end; // if RemoteProcessHandle <> 0 then
    
      if Assigned(pDLL) then
        VirtualFreeEx(RemoteProcessHandle, pDLL, 0, MEM_RELEASE );
    
      CloseHandle(RemoteProcessHandle);
    end;
    

    Şimdi sıra temizlik kodunda:

    procedure CleanProcess(const RemoteProcessID : Cardinal; const DLLName : String);
    var
      SnapshotHandle,
      RemoteProcessHandle,
      RemoteThreadHandle,
      ThreadID : Cardinal;
    
      meCurrent : TModuleEntry32;
      found : Boolean;
      Module: String;
    begin
      if not FileExists(DLLName) then
        raise Exception.Create(InttoStr(RemoteProcessID) + '->' + DLLName + ' bulunamadı.!');
    
      SnapshotHandle := CreateToolHelp32SnapShot(TH32CS_SNAPMODULE, RemoteProcessID);
      meCurrent.dwSize := SizeOf(TModuleEntry32);
    
      Module32First(SnapshotHandle, meCurrent);
    
      found := false;
      while 1 <> 2 do
      begin
        Module := meCurrent.szExePath;
        if LowerCase(Module) = LowerCase(DLLName) then
        begin
          found := true;
          Break;
        end;
    
        if not Module32Next(SnapshotHandle, meCurrent) then Break;
      end;
    
      if not found then
      begin
        CloseHandle(SnapshotHandle);
        raise Exception.Create('Remote uygulamada istenilen modül bulunamadı.!');
      end;
    
      RemoteProcessHandle := OpenProcess(PROCESS_ALL_ACCESS, false, RemoteProcessID);
    
      if RemoteProcessHandle <> 0 then
      begin
        RemoteThreadHandle := CreateRemoteThread(RemoteProcessHandle, nil, 0, GetProcAddress(GetModuleHandle('kernel32.dll'), 'FreeLibrary'), meCurrent.modBaseAddr, 0, ThreadID);
        if RemoteThreadHandle <> 0 then
        begin
          WaitForSingleObject(RemoteThreadHandle, 2000);
          CloseHandle(RemoteThreadHandle);
        end;
      end;
    
      CloseHandle(RemoteProcessHandle);
      CloseHandle(SnapshotHandle);
    end;
    

    Ve nihayet bulunan process’in yok edilmesini sağlayan metodumuz:

    procedure KillProcess(const ProcessID : Cardinal);
    var
      ProcessHandle : Cardinal;
    begin
      if ProcessId <> 0 then
      begin
        ProcessHandle := OpenProcess(PROCESS_TERMINATE, false, ProcessID);
        if ProcessHandle <> 0 then
        begin
          try
          	TerminateProcess(ProcessHandle, 0);
          finally
          	CloseHandle(ProcessHandle);
          end;
        end;
      end;
    end;
    

    Daha fazla ilerlemeden önce ne yaptığımız hakkında az çok malümat vermeye çalışalım. Bulaşma kodunda, öncelikle bulaşacağımız process’i OpenProcess ile açıyoruz. Böylelikle bulaşacağımız process’in handle’ını elde etmiş oluyoruz. Hemen ardından VirtualAllocEx ile o process’in kullandığı hafıza bloğunda DLL’imizin path’i ile birlikte adının sığabileceği kadar bir hafıza tahsis ediyoruz. Bu tahsisattan geriye dönen pointer ayrılmış hafıza bölgesini işaret ediyor.Daha sonra WriteProcessMemory ile bulaşacağımız exe’nin ayrılmış olan hafıza bölgesine DLL’imizin adını yazıyoruz. Ve en can alıcı kısım olan CreateRemoteThread metodu vasıtası ile DLL’imizi çalıştırıyoruz. CreateRemoteThread belirtilen process’in belirtilen hafıza bölgesindeki değeri parametre alarak belirtilen metodu çalıştırmakla yükümlüdür. Yani bizim örneğimizde pDLL isimli hafıza bölgesini(ki içinde DLL’imizin adı var) LoadLibraryA metoduna parametre olarak geçecek. Bildiğiniz gibi LoadLibraryA DLL kütüphanelerimizi dinamik olarak yüklemek için kullandığımız Windows API metodudur. Bulaşma işleminin temelinde bu mekanizma yatmaktadır. İlk bakışta karmaşık görünse de , anlamaya çalışarak ilerlediğinizde o derece zor ve karmaşık olmadığını göreceksiniz. Yükleme kodunun tam zıttı olan kodun beni biraz daha uğraştırdığını itiraf etmeliyim. Bu kodda processler içindeki modüllerin hepsini dolaşmak ve kendi modülünüzü(yani DLL’inizi) bulmak ve ardından yine CreateRemoteThread API’sine bu sefer FreeLibrary metodunu geçerek temizlik işlemini yapmanız gerekiyor.

    Sizlerinde farkedeceği gibi, DLL’imizin yüklenmesi sırasında çalışan bazı kodlar var ve biz henüz bu kodların neye benzedikleri hakkında fikir sahibi değiliz. Arzu ederseniz biraz da DLL’imizin kodlarına göz gezdirelim:

    library RemoteDLL;
    
    uses
      SysUtils,
      Classes,
      Windows,
      Dialogs,
      Messages,
      IdTcpClient;
    
    {$R *.res}
    
    const
      LogFile = 'c:\CreateRemoteThread.txt';
    
    procedure Log(const Message : String);
    begin
      with TStringList.Create do
      begin
        if FileExists(LogFile) then LoadFromFile(LogFile);
        Add(Message);
        SaveToFile(LogFile);
        Free;
      end;
    end;
    
    procedure EntryPointProc(Reason : Integer);
    var
      si : TStartupInfo;
      p  : PChar;
      str: String;
      client : TIdTCPClient;
    begin
      case Reason of
        DLL_PROCESS_ATTACH:  //1
          begin
          	FillChar(si, SizeOf(TStartupInfo), 0);
            si.cb := SizeOf(TStartupInfo);
            GetStartupInfo(si);
            p := si.lpDesktop;
            str := p;
    
            try
              client := TIdTCPClient.Create(nil);
              try
                client.Host := '127.0.0.1';
                client.Port := 9999;
                client.Connect();
                client.WriteLn(str + ',' + InttoStr(GetCurrentProcessId()));
                client.Disconnect;
              except on E: Exception do Log('Connection Problem:' + E.Message);
              end;
            finally
              client.Disconnect;
              client.Free;
            end;
    
            Log('DLL_PROCESS_ATTACH:' + str + '/' + TimeToStr(Time) + '/' + InttoStr(GetCurrentProcessId()));
          end;
        DLL_THREAD_ATTACH:   //2
          begin
          end;
        DLL_PROCESS_DETACH:  //3
          begin
          	Log('DLL_PROCESS_DETACH:' + TimeToStr(Time));
          end;
        DLL_THREAD_DETACH:   //0
          begin
          end;
      end;
    end;
    
    begin
      DLLProc := @EntryPointProc;
      DLLProc(DLL_PROCESS_ATTACH);
    end.
    

    Görüldüğü gibi DLL’imiz hafızaya yüklenir yüklenmez DLL_PROCESS_ATTACH bölümüne gidiyor ve bir socket bağlantısı vasıtası ile gönderilmesi gereken mesajı gönderiyor. Şimdi sıra TDesktop sınıfımızın CloseRunningProcesses isimli metodunu yazmaya geldi. Buyurun onu da yazıp makalemizin sonlarına doğru ilerleyelim:

    procedure TDesktop.CloseRunningProcesses(const Desktop: String;
      Processes: array of String; const InjectDLLFile : String);
    var
      Index,
      iCounter : Integer;
      ProcessName : String;
      ProcessList : TStrings;
    
      peCurrent : TProcessEntry32;
      SnapshotHandle : Cardinal;
      TCPServer : TIdTCPServer;
      pItem	: TProcessItem;
    begin
      Index := Desktops.IndexOf(Desktop);
      if Index = -1 then
        raise Exception.Create(Desktop + ' isimli desktop mevcut değil.!');
    
      ProcessList := TStringList.Create;
      TCPServer := TIdTCPServer.Create(nil);
    
      try
        TCPServer.Bindings.Clear;
        with TCPServer.Bindings.Add do
        begin
          IP := '127.0.0.1';
          Port := 9999;
        end;
        TCPServer.OnExecute := TCPServerExecuteEvent;
        TCPServer.Active := true;
    
        for iCounter := Low(Processes) to High(Processes) do
        begin
          ProcessName := Processes[iCounter];
          ProcessName := ExtractFileName(ProcessName); // Path şeklinde olmamalı..
          ProcessList.Add(ProcessName);
        end;
    
        InjectCount := 0;
        SnapshotHandle := CreateToolHelp32SnapShot(TH32CS_SNAPPROCESS, 0);
        peCurrent.dwSize := SizeOf(TProcessEntry32);
    
        Process32First(SnapshotHandle, peCurrent);
        while 1 <> 2 do
        begin
          ProcessName := peCurrent.szExeFile;
          if ProcessList.IndexOf(ProcessName) <> -1 then
          begin
              Inc(InjectCount);
              InjectProcess(peCurrent.th32ProcessID, InjectDLLFile); // Load &amp;amp;amp;amp;amp;amp; Execute DLL
              CleanProcess (peCurrent.th32ProcessID, InjectDLLFile); // UnLoad DLL
          end;
    
          if not Process32Next(SnapshotHandle, peCurrent) then Break;
        end;
    
        while InjectCount > 0 do Application.ProcessMessages;
    
        for iCounter := 0 to KillProcessList.Count - 1 do
        begin
          try
            pItem := TProcessItem(KillProcessList[iCounter]);
            if pItem <> nil then
              if pItem.Name = Desktop then
                KillProcess(pItem.Handle);
          except
          end;
        end;
      finally
        ProcessList.Free;
        TCPServer.Active := false;
        TCPServer.OnExecute := nil;
        TCPServer.Free;
      end;
    
      CloseHandle(SnapshotHandle);
    end;
    

    Geriye kalan kodlar pek de ehemmiyetli kodlar değil, çeşitli kontroller, silinmesi istenilen uygulamaların listesini tutan burada anlatmaya değer olmayan kod parçacıkları. Bu sebeple onları anlatma gereği duymuyorum. Ancak kodları daha rahat ve bir bütün halinde inceleyebilmeniz adına ilgili sınıfın pas dosyasını da buraya koyuyorum.

    Çok uzun bir makale oldu gerçekten, sabırla okuyabilenlere teşekkür ederim.

    Saygılar, sevgiler..

    Not: CleanProcess metodunu explorer.exe ve diğer şüphelendiğiniz processler için çalıştırabilirsiniz. Benim explorer.exe ile ilgili testlerim sırasında FlashGet programının bir DLL’inin explorer.exe içerisinde yüklü olduğunu gördüm. Bu normal mi değil mi tam emin olmamakla birlikte yine de şüphe çekici..

    “Bir Kiosk ve CreateDesktop macerası..” için 36 Yorum

    1. Özkan Danacı diyor ki:

      Okuyorum.. Okuyorum.. hala Okuyorum.. :)

    2. Tuğrul HELVACI diyor ki:

      Sanırım biraz uzun yazmışım :)

    3. Olcay DAĞLI diyor ki:

      Uzun uğraşılar sonucu elde etmiş olduğun böylesi bir bilgiyi daha zor ve uzun bir uğraşı ile yazıya döküp anlatabildiğin için tebrik ederim hocam…
      Yanlız bir şey belirteyim makale o kadar uzun gelmiyor okuyunca, zira gayet akıcı yazmışsın, eline sağlık hocam ;)

    4. Veli BOZATLI diyor ki:

      Sanal alemde gerçekten böyle yararlı ve değerli (araştırılması, oluşturulması, yazılması vs. zor) bilgileri paylaşan kişilerin olduğunu görmek beni çok mutlu ediyor.
      Elimizden sadece bir kuru teşekkür etmek geliyor :
      Hiçbir karşılık beklemeksizin bilgilerini paylaşanlardan Allah razı olsun…

    5. Tuğrul HELVACI diyor ki:

      Allah, “Allah razı olsun” diyenden de demiyenden de razı olsun..

    6. Numan diyor ki:

      Makaleyi henüz okumadım ama hemen teşekkür etmek istedim. Ben Delphi de yeniyim, CreateDesktop olayını visual basic de kullanıyordum, ancak bunu Delphi ye bir çırpıda dönüştürmek şu an benim için çok zor bir olaydı. Bu makaleyi henüz okumadım ancak olayı bildiğimden dolayı bunun bana çok faydalı olacağından emin gibiyim, şimdiden teşekkürler aro.

    7. Tuğrul HELVACI diyor ki:

      Allah cümlemizden razı olsun.

    8. Soylu OItu KAYA diyor ki:

      Açıkcası çok güzel açıklamalar ve farklı bir yaklaşım.
      Programlama konusunda bu tarz makaleleri okurken bazen bir satırın açıklamasını gördüğümde o satırdaki komutun araştırmasına giriyorum ve bir bakıyorum 6 gün geçmiş ve ben o satırlardaki tüm komutları en detaylı şekilde öğrenmişim. Tabiki bunu kendi yazılımıma adapte etme aşamasına gelince iş biraz farklılaşıyor kendi yazılımımda 5 satır kodu kullanıyorum öğrendiğim 500 sayfa açıklama gereksiz kalıyor :) Biraz düşündüm ve dedim ki kendi kendime bu arkadaş bu kadar yazmış bunun proje dosyasını yada pas dosyasını niye koymamış sonralarda fark ettim ki en altta 1 satırcık yere
      ” ilgili sınıfın pas dosyasını da buraya koyuyorum.” ibaresi ile pas dosyasını koymuş tebrikler. Proje dosyalarından inceleme yapmak bazen çok daha seri oluyor. Her komuta her yerde ihtiyaç olmayabiliyor. Çalışmalarında başarılar.

    9. Bora ÇAYIR diyor ki:

      Delphiyle ilgili hereşeye varım siteyi bugün keşfettim
      konular guzel

    10. Merhaba Tuğrul,
      Yazmış olduğun makleyi ve bu sayfanı, Kendi projemde geliştimek istediğim bir çalışmaya ait başka bir win32 API hakkında bilgi ararken rastgeldim, doğrusu konuya hakim ve akıcı bir uslupta ayrıntılara girmişsin ki çok başarılı eline sağlık.

      Ben C++ yazılım dilinde BDS 2006 ortamında proje geliştiriyorum. Her ne kadar Delphi’ci değilsem de okuyabiliyorum.

      Windows XP için çeşitili ayar ve düzenlemeler yapan Shurzanop 2.0 için makalenizde geçen çalışmayı da uygulamak istiyorum. Eğer bir mahsuru yoksa kodlamayı C++ dili ile geliştirip kullanacağım.

      Bu makaleniz için teşekkürler, başka makalelerde bekliyoruz.

      • Tuğrul HELVACI diyor ki:

        Merhaba İsmail bey, elbette gönlünüzce kullanabilirsiniz. Çalışmalarınızda başarılar dilerim.

    11. mkysoft diyor ki:

      Çok güzel bir makale olmuş, teşekkür ederim.

    12. mesut diyor ki:

      hocam çok teşekkür ederim çok güzle bir makale olmuş.
      benim ufak bir sorum var .
      ben TMemoryStream nesnesi ile şifrelediğim bir dosyayı ram da çalıştırmak istiyorum yani bir yere kopyalanıp oradan çalışmasını istemiyorum.şifrelenmiş dosyayı direk çalıştıracak. makalenizde writeproccesmemory komutunu ksıa analtmışsınız acaba bu komutu kulalnarak direk ram da çalıştırabilirmiyim veya hangi kodu kullanmalıyım
      teşekkür ederim.. ayrıetten msn desteğiniz olursa sevinirim.

    13. Tuğrul HELVACI diyor ki:

      Mesut bey, WriteProcessMemory ile ilgili daha detaylı bilgi paylaşımı virüs ve trojan yapımcılarının işine yarar ve onların ilgi alanıdır genellikle. Zaten konuyu izah edebilmek adına biraz da olsa zararlı olabilecek içeriklere girdik, daha detaylarına girmeyelim ;)

    14. Mehmet Erdem Korkmaz diyor ki:

      Güzel bir çalışma olmuş. Emeğine sağlık. Bana yol gösterici olmasına rağmen hala yapmayı başaramadığım bir meseleyi sormak isterim.

      Benim yapmak istediğim şey winlogon ekranına geçilse bile o ekranın görüntüsünü alabilmek. Bunu bir türlü başaramadım.

    15. MURAT diyor ki:

      Helal olsun kardeş,
      Harika olmuş baya uğraşmıssın
      eline sağlık.

    16. tek kelime ile helal olsun diyorum.Bu bilgiler bana altın madeni bulmuş gibi geldi.Saygılar.

    17. fatih diyor ki:

      Güzel makale.Elinize sağlık.

    18. Ahmet Yeşilçimen diyor ki:

      Hocam Hastanım Senin yaaa Scrollbarı bile aşşağı çekmem 1 dakikakımı aldı :D ama canla basla hevesle okuyacağım emekelriniz boşa gitmesn ve tüm insanlar yarar grsn Allah Sizden Razı Olsun Hocammmmmmmmmmmmmmm
      Ne Muradın Varsa Versin Seni Çok Seviyorum Abicim.

    19. Ahmet Yeşilçimen diyor ki:

      Forever Tuğrul :D +REPPPPPPPPPP :)

    20. Ahmet Yeşilçimen diyor ki:

      Hocam Söyleseydiniz Sabah Sizi Özel Arabayla Aldırırdık :D Geç Kalmayın Sizin gibi adama, ülke ihtiyac duyuyor. sayınız az sayınızın devamını getireceğim ins :)

    21. Ahmet Yeşilçimen diyor ki:

      Hocam Kızmıyorsun Değil mi Seni Çok Seviyoruz Biz Özellikle Ben :) Çok Paylaşımcı Bir İnsan. Allah Senin Gibilerini Başımızdan Eksik Etmesin

      • Tuğrul HELVACI diyor ki:

        Hayır neden kızayım canım. Lâkin o kadar da övgüye layık işler yapmıyorum. Sadece bildiklerimi paylaşıyorum.

    22. Ahmet Yeşilçimen diyor ki:

      Sağolun Hocam Benim İçin Gerçekden Çok Şey Yapıyorsunuz En Önemlisi Zaman Harcayıp Bizim İçin Birşeyler Paylaşıyorsunuz Başka Ne Bekliyebiliriz Ki Senden Tek Umudum Senin Gibi Olmak Senden Daha İyi Olmak Allah Razı Olsun.

    23. Ramazan Apaydın diyor ki:

      Gerçekten çok güzel bi anlatım, memory konusunda türkçe kaynak bulmak gerçekten zor.

    24. Barış ATALAY diyor ki:

      Yıl olmuş 2013 hala çok seviliyorsunuz selamlar saygılar örnek aldığımız insan :)

      • Tuğrul HELVACI diyor ki:

        Teşekkürler Allah razı olsun. “Yıl olmuş 2013 hâla…” diyince, kendimi yaşlı hissettim bir anda :)

    25. volki diyor ki:

      Merhabalar Tuğrul bey beni hatırlamanız zor ama sene 98 de falan size programlama.com üzerinden ulaşmış ve icq ile çok desteğinizi almış bir kişiyim yıllar sonra tekrar buldum sizi hala delphi ile ilgilenmeniz hoşuma gitti. Mümkün olursa sizden writeproccesmemory writeproccesmemory ile ilgili bir sorunum var çok araştırdım ama dediğiniz gibi virüs yazılabildiği için çok bi kaynak bulamadım size ulaşabileceğim bir skype, msn, email adresi varsa sorumu sormam mümkün mü acaba.

      İyi Çalışmalar
      Volkan ÇITIRKI

      • Tuğrul HELVACI diyor ki:

        Merhaba volkan bey; isminiz hiç de yabancı gelmedi bana ama tabii neler konuştuk o zamanlar, iletişimimiz nasıldı bunları hatırlayamıyorum. Yaşlılık işte :) Size bir mail gönderdim.

    26. Barış ATALAY diyor ki:

      Hocam ziyaretçi defteri kapalı olduğu için buradan yazıyorum. OmniThreadLibrary Kitabı ne alemde sabırsızlıkla bekliyoruzda :(

      • Tuğrul HELVACI diyor ki:

        Maalesef çeviriyi Ramazan GÜLBAHAR arkadaşımız yapacaktı. Ancak o zamandan bu zamana değin kendisinden herhangi bir haber alamadık. Zaten Primoz da ümidi kesmiş olacak ki leanpub’da artık türkçe ile ilgili herhangi bir ibare bulunmuyor.

        • Barış ATALAY diyor ki:

          Gerçekten mi çok üzüldüm hocam kaç zamandır bekliyordum. Yıkılmak deyimi bu olsa gerek :-/
          Diğer arkadaşlarında dediği gibi;
          “Yıl olmuş 2013 hala çok seviliyorsunuz selamlar saygılar”
          :)

    27. erkansenturk diyor ki:

      makalelerini geç..
      oldu ama yeni keşfettim..açıkçası oldulça iyi ve yenilikçisiniz..daha dogrusu ezberci kod anlayışını benimsememeniz anlatım tarzınıza yansımış..bu makale için explorer.exe belllek tasarrufu için servis uygulamasına process ları gömsek daha şık olmazmı!ayrıca bu makalenizin mümkünde pas dosyasını maille gönderebilirmisiniz..şimdiden çok teşekkür ederim

      • Tuğrul HELVACI diyor ki:

        Explorer.exe’nin çalıştırılmasını ilgili desktop’ta alışık olduğumuz öğeleri göstermek amacı ile yapmıştık. Açtığımız explorer.exe’yi kapatmamız gerektiğine göre(eğer memory’e saygılı isek); bunun birden fazla yolu olabilir. Tamamen programcının tercihi, ister sürekli çalışan bir uygulamaya bir mesaj atarak ilgili exe’nin hayat döngüsünü kontrol edersiniz isterseniz de başka yöntemler seçebilirsiniz. Ben en ideal yöntemin bu olduğunu iddia etmiyorum, zaten makalelerim de hazır çözümlere yönelik değil, daha ziyade “bir şeyler öğretebilirsem ne mutlu bana” tadında makaleler yazmaya gayret ediyorum.

        Kaynak kodu için ise makalenin sonunda küçük bir paylaşımım var, oradan indirebilirsiniz. Tüm proje kodlarını bilerek indirime açmıyorum, copy/paste tembelliğine neden olmamak için. Dediğim gibi kısıtlı bilgilerimle de olsa bir şeyler öğretebilmek adına bu makaleler var..

    28. erkansenturk diyor ki:

      ben yeni masaustunde explorer exe kopyasi calistirmak istemedim.tipki linux ortamindaki gibi kabuk mantigi olusturmak istedim.. 1)problem:default masaustune gectigimde bir onceki masaustu kapanmiyor….enumdesktop fonk. kontrol ediyorum..acik donuyor..2)problem:yeni olusturduğum masaustunde istedigim kisayollar,klasor vb. olusturmama ragmen gorunmuyor..

    Numan için yorum yazın