Son birkaç gündür, pekçok dil tarafından çağrılabilmesini istediğim fonksiyonlar içeren bir kütüphanenin yazımı ile uğraşıyorum. Bu yüzden VByi değil, pek hakim olmadığım C++ dilini seçtim. (VB ile yazınca sadece visual studionun desteklediği dillerde kullanılabiliyor.) Kütüphaneyi yazdıktan sonra kullanım örneklerini hazırlarken bazı problemlerle karşılaştım. C++ fonksiyonları tarafından dödürülen bazı veri tipleri, Visual Basic tarafından kullanılamıyor. Örneğin C++ ile yazdığım kütüphanenin fonksiyonundan, onu çağıran VB fonksiyonuna bir türlü double dizisi döndüremedim. Ya da C++taki int tipinin VB6daki karşılığı integer değil.
Yazı genel olarak bu uyumsuzluklarla ilgili olacak. Örnekleri hep C++ ve VB6 üzerinden vereceğim ama aynı mantığı diğer dillerde de kullanmak mümkün...
Her programlama dili aynı veri tiplerini kullanmıyor veya bellekte aynı biçimde tutmuyor olabilir. Örneğin C++ ile oluşturulmuş herhangi bir tipteki dizi VB tarafından kullanılamadığı için bu dille yazılan bir kütüphanenin fonksiyonlarında dizi döndürülmemesi, uyumluluk açısından önemli. Dizi döndürmek yerine dizinin ilk elemanının adresini döndürmek daha mantıklı. Çünkü dönen adresi 4 baytlık tamsayı tipinde bir değişkene aktarıp bu adresin işaret ettiği bölgeden veri okuyabiliriz. Bellek üzerinde bir alanda veri okuma/yazma işlemleri için Windows'un kernel32 kütüphanesindeki RtlMoveMemory fonksiyonu kullanılır. Bizim için oldukça önemli olan bu fonksiyon sayesinde tipleri umursamadan bellek üzerinde istediğimiz uzunluktaki bir alanda okuma/yazma işlemi gerçekleştirebiliyor ve böylece C++taki referans ve dereferans operatörlerini rahatlıkla taklit edebiliyoruz. VB6da referans ve dereferans operatörlerinin işlevlerinin yerine getirilmesi ile ilgili daha fazla bilgi edinmek isterseniz Visual Basicte İşaretçiler (Pointerlar) yazısını okuyabilirsiniz.
C++:
extern "C" __stdcall double* __declspec(dllexport) Fonk1() { //double* CikisDizisi=new double[5]; //Alanı öyle ayırınca, VB6da hatalı okundu... double* CikisDizisi=(double*) malloc(5); for(int i=0; i<5; i++) CikisDizisi[i] = i; return CikisDizisi; }
extern "C" ile derleyiciden fonksiyonumuzu C fonksiyonu gibi derlemesini istedik (Böylece fonksiyon yeniden adlandırılırken, fazla karmaşık bir isim almıyor. Ayrıntılı bilgi için: Name Mangling). __declspec(dllexport) ile fonksiyonu dll dışından erişime açtık. __stdcall ile fonksiyonun standart çağrılma yöntemine uygun olmasını sağladık. (VB6 C++'ın kullandığı diğer yöntem olan __cdecl yöntemini desteklemez. Ayrıntılar için: Calling Conventions (Wikipedia), Calling Conventions (MSDN)) Dönüş tipimiz de bir double işaretçisi.
Visual Basic 6:
Private Declare Sub Kopyala Lib "kernel32" Alias "RtlMoveMemory" _ (Hedef As Any, Kaynak As Any, ByVal Uzunluk As Long) Private Declare Function Fonk1 Lib "Kutuphanem.dll" () As Long Sub Yordamim Dim adres As Long adres = Fonk1() Dim i As Integer Dim d As Double Dim Dizi(4) As Double For i = 0 To 4 Kopyala d, ByVal adres + i * 8, 8 'c++ dilindeki dereference operatörünün işlevi Dizi(i) = d Next i End Sub
C++'taki işaretçiler, 4 baytlık tamsayılar ile ifade edilebilirler. VB6da long tipi 4 baytlık alan kaplar. Yani döndürülecek işaretçi değeri long tipinde bir değişkene atmak mümkündür. Bu sayede, C++ kodunda double işaretçisi tipindeki değişkeni, VB6da, alakasız gibi görünen long tipindeki bir değişkenle temsil edebiliyoruz. Asıl zor olan görev, yani işaret edilen bölgeden veri okuma görevi ise kernel32 kütüphanesindeki RtlMoveMemory fonksiyonuna düşüyor...
Burada dikkat edilmesi gereken bir diğer şey, double tipinin her iki dilde de 8 bayt yer kapladığı. C++ kodunda double işaretçisi, aslında arka arkaya dizili olan 5 double değişkeninin başlangıç konumunu tutar. VB6 kodunda da herbiri 8 bayt yer kaplayan bu veri dilimleri sekizerli biçimde okunup double dizisinin elemanlarına aktarılır.
RtlMoveMemory fonksiyonu ile istediğimiz uzunlukta bir alanda işlem yapabileceğimizi söylemiştik. Örnekte, double dizisinin elemanlarını tek tek kopyalamak yerine, arka arkaya dizilmiş 8er baytlık dilimlerden oluşan bu alanı bir tek çağrıda doldurmak mümkündür. Yani yukarıdaki yordam şu şekilde kısaltılabilir:
Sub Yordamim Dim adres As Long adres = Fonk1() Dim Dizi(4) As Double Kopyala Dizi(0), ByVal adres, 40 ' 5 tane double, 5*8=40 End Sub
Aynı konuya bir örnek daha verelim. C++ ile yazdığımız fonksiyonda eğer double değil de int işaretçisi döndürseydik, VB6da dönüş tipini long tipi ile temsil etmeye devam ederdik. Ama referanstan okuma işleminde dilimleri sekizerli değil, dörderli gruplar halinde kopyalar ve long tipine atardık. Çünkü C++'ta int tipi 4 bayt yer kaplar ve bu tip değişkenler VB6da, 2 baytlık integer tipi ile değil ancak aynı boyuttaki long tipi ile temsil edilebilir.
Meydana gelebilecek tip uyumsuzlukları aynı mantık yürütülerek, tiplerin boyutları arasında yapılacak karşılaştırmalarla çözüme kavuşturulabilir...
Parametreler, yordamlar arasında referans aktarımı ve değer aktarımı olmak üzere iki şekilde aktarılır. (Değer aktarımında aktarımı yapılan değerler için bellekte yeni bir alan açılır. Referans aktarımında ise değerin saklandığı bellek alanının adresi aktarılır ve böylece yeni bir alana gerek duyulmaz.) Varsayılan aktarım biçimi tüm dillerde aynı değildir.
C++ ve VB6da karşılaşılabilecek bir diğer uyumsuzluk varsayılan değer aktarımlarının farklı oluşundan kaynaklanır. VB6da referans aktarımı, C++ta ise değer aktarımı varsayılandır. Bu yüzden C++ ile yazılmış bir fonksiyonunun VB6da bildirimi yapılırken işaretçileri temsil edecek parametreler hariç diğer parametrelerin başına "ByVal" sözcüğü eklenir. Böylece değişken aktarımı C++ fonksiyonunun beklediği biçimde yapılır.
C++ fonksiyonu işaretçi değerler alıyorsa, VB6da yapılacak fonksiyon bildiriminde, işaretçileri temsil edecek değişkenlere "ByVal" değil, "ByRef" sözcüğü eklenir (hiçbir şey eklenmese de olur; varsayılan zaten ByRef'tir). Böylece işaretçiye değişkenin adresi aktarılır.
C++:
extern "C" __stdcall int* __declspec(dllexport) Fonk2(float parametre1, short int* parametre2, short int parametre3) {...}
Visual Basic 6 ile yukarıdaki fonksiyonun bildirimi:
Private Declare Function Fonk2 Lib "Kutuphanem.dll" (ByVal parametre1 As Single, _ ByRef parametre2 As Integer, _ ByVal parametre3 As Integer) As Long
float tipinin aynı boyuttaki (4 bayt) single tipi ile ve short int tipinin aynı boyuttaki (2 bayt) integer tipi ile temsil edildiğine dikkat edin. Ayrıca işaretçi kısa tamsayı değişkenine, VB6da, işaret edilen değişken tipi ile aynı miktarda yer kaplayan (2 bayt) integer tipli değişkenin referansının yollandığı da dikkat edilmesi gereken bir diğer nokta...