Delphi ile UDF Yazma

Firebird ve Interbase veritabanları ve SQL komutlarıyla ilgli sorularınızı sorabilirsiniz. Delphi tarafındaki sorularınızı lütfen Programlama forumunda sorunuz.
Cevapla
Kullanıcı avatarı
mussimsek
Admin
Mesajlar: 7586
Kayıt: 10 Haz 2003 12:26
Konum: İstanbul
İletişim:

Delphi ile UDF Yazma

Mesaj gönderen mussimsek »

Merhaba,

Makalenin pdf hali : http://www.delphiturkiye.com/dsplus/m.p ... rd_UDF.pdf

Delphi Kodları : http://www.delphiturkiye.com/dsplus/m.p ... df_kod.rar

Firebird’ün güzel yanlarından biri de, Firebird’e kendi yazdığınız fonksiyonları ekleyip, veritabanında her yerde kullanabilmeniz. UDF’leri Delphi ile de yapabilirsiniz. Aşağıda basitçe Delphi ile UDF yazmayı anlatmaya çalıştım. Ekleyeceğiniz veya düzelteceğiniz birşey varsa mutlaka bize ulaştırın.

UDF oluşturmak için bir .dll hazırlayıp, Firebird’ün UDF klasörüne koymak gerekiyor.

Delphi’yi açıp, File -> New -> Other... komutunu verin. Açılan pencereden DLL Wizard seçip, OK butonuna basın. Delphi size boş bir dll kodu oluşturacaktır. Direk bu kısma fonksiyonlarımızı ekleyebiliriz, ama ilerde fonksiyonlarımız arttığında takibin kolay olması açısından gruplara ayırmak ve grupların her biri için yeni bir unit ekleyip, oraya yazmamız daha iyi olur. File -> New -> Unit komutunu vererek yeni bir unit ekleyelim.

Şu an için unit’imizde verilen bir yıl ve aydaki gün sayısını veren bir fonksiyon olacak. Fonksiyonumuz zaten Delphi’de DateUtils unitinde olduğu için uses kısmına DateUtils ekliyoruz. Fonksiyonu ekledikten sonra unitimizin kodu :

Kod: Tümünü seç

unit untTarih;

interface

uses DateUtils;

function BirAydakiGunSayisi(var Yil, Ay:Integer):Integer;stdcall;

implementation

function BirAydakiGunSayisi(var Yil, Ay:Integer):Integer;
begin
  Result := 0;
  if (Ay <= 0) or (Ay > 12) then
    Result := 0
  else
    Result := DaysInAMonth(Yil, Ay);
end;

end.

Bu fonksiyonu dll kısmında export etmemiz lazım. Proje dosyamızın son hali :

Kod: Tümünü seç

library DelphiTurkiye;

uses
  SysUtils,
  Classes,
  untTarih in 'untTarih.pas';

{$R *.res}

exports
  BirAydakiGunSayisi;

begin
end.
Daha sonra Project -> Build All komutunu verip, dll dosyasını oluşturalım. Oluşan .dll dosyasını ismi “DelphiTurkiye.dll” olsun. Bu dll dosyasını Program Files\Firebird içerisindeki UDF klasörüne kopyalayın. Daha sonra IB Expert ile herhangi bir veritabanına bağlanıp, UDF’imizi aşağıdaki kodla tanımlayabiliriz.

Kod: Tümünü seç

DECLARE EXTERNAL FUNCTION BirAydakiGunSayisi
    integer, integer
    RETURNS integer BY VALUE
    ENTRY_POINT 'BirAydakiGunSayisi' MODULE_NAME 'DelphiTurkiye';
Burda fonksiyonumuzun adını, aldığı iki parametrenin tiplerini, döneceği değerin tipini ve hangi dll’de, hangi isimle export ettiğimizi tanımladık. Bu tanımları yaptıktan sonra veritabanı bağlantısını kesip, Denetim masasından Firebird servisini kapatıp açın. Artık fonksiyonumuzu kullanabiliriz.

Kod: Tümünü seç

select BirAydakiGunSayisi(2008, 2)
from rdb$database
Hepsi bu kadar. Şimdi kod yazarken nelere dikkat etmek gerekiyor, onları belirtelim :

* Fonksiyon parametrelerini mutlaka var kelimesi ile geçin. Örneğimizde Yil ve Ay parametrelerini bu şekilde geçtik. Parametre pchar ise var kullanmamanız lazım.

* Firebird C’de yazıldığı için, C notasyonu istiyor. Fonksiyonumuzu dll’den çağırırken sonuna stdcall direktifini eklememiz gerekiyor.

* Fonksiyonu çağırırken near, far ve export direktiflerinin bir etkisi yoktur, bunları kullanmayın.

* Eğer fonksiyonu denerken bir hata alırsanız veya bağlantınız koparsa:
o Kodunuzda hata vardır, kodunuzu kontrol edin.
o .dpr kısmında fonksiyonu export ettiniz mi, bir bakın. Eğer etmediyseniz genelde “invalid request BLR at offset xxx” gibi bir hata alacaksınız.
o Derledikten sonra oluşan .dll dosyasını UDF klasörüne kopyaladınız mı, kontrol edin. Firebird 2.1 için normal kurulumda bu klasörün tam yolu : “C:\Program Files\Firebird\Firebird_2_1\UDF” tir.
o Firebird servisini denetim masası kısmından kapatıp açın.

* Eğer dll’de değişiklik yaparsanız, parametre değişikliği yoksa .dll’i UDF klasörüne kopyalamanız yeterlidir. Eğer açık bağlantı varsa, dll kullanılacağı için Windows .dll’in üstüne yazmanıza izin vermez. Açık veritabanı bağlantılarını kesip, .dll’i öyle kopyalayın.

* Eğer .dll’deki fonksiyonlarda parametre değişikliği yaptıysanız, UDF tanımlarını silin (drop), .dll’i kopyalayıp tanımları değişen parametreleri de dikkate alarak tekrar yapın.

* Pchar bir parametre kullanırsanız, yapacağınız işleme göre Trim kullanıp boşlukları atmak iyi olur.

* Firebird’teki tiplerin Delphi karşılığı :
o integer : integer
o char veya varchar : pchar
o double precision : double

* Firebird’e kullanabileceğiniz, pek çok hazır fonksiyon var. Bunlar işinizi görebilir, bu fonksiyonları incelemeyi unutmayın : viewtopic.php?t=22223

UDF’imizi biraz daha geliştirelim :

String İşlemleri

String işlemleri için Pchar parametre kullanmak gerekiyor. Pchar C tipi string’tir ve sonunda #0 karakteri bulunur. Bu #0 karakteri string’in sonunu gösterir.

Verilen string’in uzunluğunu veren bir UDF yazalım, ismi “Uzunluk” olsun.

Kod: Tümünü seç

function Uzunluk(Str:Pchar):integer;stdcall;
...
function Uzunluk(Str:Pchar):integer;
 var
  i : integer;
begin
  Result := -1;
  i := 0;
  while (Str[i] <> #0) do
    Inc(i);
  Result := i;
end;
Bu fonksiyon #0 karakterine kadar, her karakter için i değişkenini bir artırarak string’in uzunluğunu buluyor ve döndürüyor.

Firebird tarafında UDF’i tanımlarken, Cstring kullanılır :

Kod: Tümünü seç

DECLARE EXTERNAL FUNCTION Uzunluk
 CSTRING(255)
RETURNS integer by value
ENTRY_POINT 'Uzunluk' MODULE_NAME 'DelphiTurkiye';


Tarih İşlemleri

Tarih işlemleri için, Firebird’ten gelen tarih değerini api fonksiyonları kullanarak, Delphi’nin anlayacağı şekle çevirmek gerekiyor.

İki tane fonksiyon yazacağız, biri Delphi’deki FormatDateTime fonksiyonunun yaptığı işi Firebird tarafında yapacak, diğerinde de iki tarih arasındaki ay sayısını bulacağız. Kodlar :

Kod: Tümünü seç

const
  IBASE_DLL = 'gds32.dll'; 
Kullanacağımız Firebird api’leri gds32.dll içerisinde.

Kod: Tümünü seç

type
  Long                 = LongInt; // 32 bit signed
  ULong                = DWord;   // 32 bit unsigned
Date ve Time tiplerini tutan değişkenlerimizin tipleri. Date için Long, Time için Ulong kullanıyoruz.

Kod: Tümünü seç

type
  TM = record
    tm_sec   : integer;  // Saniye
    tm_min   : integer;  // Dakika
    tm_hour  : integer;  // Saat (0-23)
    tm_mday  : integer;  // aydaki gün (1-31)
    tm_mon   : integer;  // Ay (0-11)
    tm_year  : integer;  // Yil (takvim yılından 1900 eksiği)
    tm_wday  : integer;  // Haftanın günü (0-6) Pazar = 0)
    tm_yday  : integer;  // Yılın günü (0-365)
    tm_isdst : integer;  // gün ışığından faydalanıyorsa 0)
  end;
  PTM             = ^TM;

  ISC_TIMESTAMP = record
    timestamp_date : Long;
    timestamp_time : ULong;
  end;
  PISC_TIMESTAMP = ^ISC_TIMESTAMP;
Tarih ve saati tutacağımız record’umuz.

Kod: Tümünü seç

procedure isc_decode_sql_date   (var ib_date: Long; tm_date: PTM); stdcall; external IBASE_DLL;
Tarihi alacağımız Firebird API’si

Kod: Tümünü seç

function DuzenliTarih(FormatStr:Pchar; var ib_date : Long):Pchar;stdcall;
...
function DuzenliTarih(FormatStr:Pchar; var ib_date : Long):Pchar;
 var
   tm_date : TM;
   TempTarih : TDateTime;
begin
  if FormatStr = nil then
    Result := nil
  else
  begin
    isc_decode_sql_date(ib_date, @tm_date);
    TempTarih := EncodeDate(tm_date.tm_year + 1900, tm_date.tm_mon + 1, tm_date.tm_mday);
    Result := PChar(FormatDateTime(string (FormatStr), TempTarih));
  end;
end;
isc_decode_sql_date api’si ile gelen tarih değerini, tm_date record’una aldık ve bunu EncodeDate kullanarak Delphi TDateTime değerine çevirdik. Burda dikkat edeceğiniz iki nokta var. Yıl değeri o anki yıldan, 1900 eksik gelir. Dolayısıyla, tarihteki yılı bulmak için 1900 ile topladık. Ayrıca ay değerleri 0 ile 11 arasındadır. Delphi’de aylar, 1 ile 12 arası olduğu için ay değerine de 1 ekledik.

String ile pchar değerini stringe, pchar ile de stringi pchar’a çevirebilirsiniz.

İkinci tarih UDF’imiz :

Kod: Tümünü seç

function KacAyVar(var IlkTarih, IkinciTarih : Long):integer;stdcall;
...
function KacAyVar(var IlkTarih, IkinciTarih : Long):integer;
 var
   tm_ilk, tm_ikinci : TM;
   TempIlkTarih, TempIkinciTarih : TDateTime;
begin
  Result := -1;

  isc_decode_sql_date(IlkTarih, @tm_ilk);
  isc_decode_sql_date(IkinciTarih, @tm_ikinci);

  TempIlkTarih := EncodeDate(tm_ilk.tm_year + 1900, tm_ilk.tm_mon+1, tm_ilk.tm_mday);
  TempIkinciTarih := EncodeDate(tm_ikinci.tm_year + 1900, tm_ikinci.tm_mon+1, tm_ikinci.tm_mday);

  Result := MonthsBetween(TempIlkTarih, TempIkinciTarih);
end;
Bunları Firebird tarafında tanımlamak için :

Kod: Tümünü seç

DECLARE EXTERNAL FUNCTION DUZENLITARIH
    CSTRING(64),
    DATE
RETURNS CSTRING(128) FREE_IT
ENTRY_POINT 'DuzenliTarih' MODULE_NAME 'DelphiTurkiye';

DECLARE EXTERNAL FUNCTION KACAYVAR
    DATE,
    DATE
RETURNS INTEGER BY VALUE
ENTRY_POINT 'KacAyVar' MODULE_NAME 'DelphiTurkiye';
Hata Ayıklama (Debug)

Bazen bir işlem yaparken, fonksiyon istediğiniz sonuçları döndürmeyebilir veya bağlantı kopabilir. Bu durumda büyük ihtimalle kodunuzda bir yanlış yapmışsınız demektir. Nerede yanlış yaptığınızı görmek için fonksiyon içindeki bazı değerleri görmek isteyebilirsiniz. Bu durumda en kolay yöntem, bir text dosya açıp istediğiniz değerleri oraya yazdırmanızdır.

Mesela DuzenliTarih fonksiyonumuz düzgün çalışmıyor ve format string’imizin düzgün gelip gelmediğini görmek istiyoruz. Aşağıdaki gibi bir dosya açıp, fonksiyonu Firebird’te denedikten sonra fonksiyon c:\debug.txt içerisine gelen FormatStr parametresinin değerini yazar.

Kod: Tümünü seç

function DuzenliTarih(FormatStr:Pchar; var ib_date : Long):Pchar;
 var
   tm_date : TM;
   TempTarih : TDateTime;
   DebugFile : TextFile;
begin
  try
    AssignFile(DebugFile, ‘c:\debug.txt’);
    ReWrite(DebugFile);
    WriteLn(DebugFile, string(FormatStr));
  finally
    CloseFile(DebugFile);
  end;
  ...


Kolay gelsin.
En son mussimsek tarafından 17 Şub 2008 07:51 tarihinde düzenlendi, toplamda 2 kere düzenlendi.
Kullanıcı avatarı
raxs
Üye
Mesajlar: 13
Kayıt: 02 Oca 2008 10:35

Hocam merhaba

Mesaj gönderen raxs »

viewtopic.php?t=22524
da belirttiğim bir tarih sorgusu var bunu udf olarak hazırlaya bilirmiyiz.
Kullanıcı avatarı
mussimsek
Admin
Mesajlar: 7586
Kayıt: 10 Haz 2003 12:26
Konum: İstanbul
İletişim:

Mesaj gönderen mussimsek »

Tarih parametreler için bir takım dönüşümler yapmak gerekiyor. Şu an üzerinde çalışıyorum, bitince nasıl yapılacağını buraya yazayım. Hatta sizin örnek üzerinden giderim.

Kolay gelsin.
Kullanıcı avatarı
raxs
Üye
Mesajlar: 13
Kayıt: 02 Oca 2008 10:35

Mesaj gönderen raxs »

Teşekkür ederim çok makbüle geçecek.
Kullanıcı avatarı
mussimsek
Admin
Mesajlar: 7586
Kayıt: 10 Haz 2003 12:26
Konum: İstanbul
İletişim:

Mesaj gönderen mussimsek »

string ve tarih işlemleri içinde bilgi ve örnek ekledim. UDF'leri denedim ve istediğim sonuçları verdiğini gördüm ama bunların ilk UDF'im olduğunu unutmayın. Eksiklerim varsa mutlaka bana iletin.

Kolay gelsin.
Kullanıcı avatarı
softdestek
Üye
Mesajlar: 155
Kayıt: 17 Eyl 2010 03:53

Re: Delphi ile UDF Yazma

Mesaj gönderen softdestek »

Bu udf çağırınca firebird bağlantısı kopuyor hata nerde acaba?
Udf nin yapacağı iş şu string içindeki nokta sayısnı bulacak..
Bu udf yi delphiturkiye.dll içindeki procedureların yanına ekliyorum hata mesajı alıyorum sebebi nedir
Kullandığım Firebird versiyonu 2.5

select SEVIYEBUL('600.01.01') from rdb$database


FUNCTION SEVIYEBUL(HESAPKODU:Pchar):integer;
var
adet : integer;
sayac:integer;

begin
adet:=0;
sayac:=0;

for sayac:=0 to system.Length(string(hesapkodu))-1 do begin

if string(hesapkodu[sayac])='.' Then begin
adet:=adet+1;
end;

end;
result:=adet;
end;
erkankurtaga
Üye
Mesajlar: 62
Kayıt: 04 Oca 2009 06:36

Re: Delphi ile UDF Yazma

Mesaj gönderen erkankurtaga »

Hocam yukarıda softdestek'inde bahsettiği gibi benimde udflerim çalıştırıldığı zaman databse bağlantısı kopuyor. BU kopma olayını da 2012 serverda yapıyor.
fonksiyon çalışıyor ama geriye değeri almak isterken bağlantı kopuyor. bu problem nasıl halledilebilir
Cevapla