FFI nasıl çalışıyor?

Merhaba, ctypes ı kullanırken nasıl çalıştığını merak ettim. Biraz araştırınca FFI’ı buldum. FFI’ı biraz araştırdım (wikipedia ve stackoverflow dan birde birkaç siteden daha okudum) ancak pek bir şey anlamadım. Python nasıl C kodu çalıştırabiliyor ? Python C de kodlandığı için mi ? Çünkü bir kod örneği gördüm ve C koduydu

static PyObject *
spam_system(PyObject *self, PyObject *args)
{
    const char *command;
    int sts;

    if (!PyArg_ParseTuple(args, "s", &command))
        return NULL;
    sts = system(command);
    return Py_BuildValue("i", sts);
}

PyObject bir C objesi sanırım.
Eğer Python C ile geliştirilmiş olmasaydı ctypes modülünü yapmak mümkün olmayacak mıydı ? DLL leri, C nin veri tiplerini nasıl Python’da kullanabiliyoruz ? Python zaten C de kodlandığı ve tüm veri tipleri zaten C de tanımlı olduğu için mi ? Birde GitHub da da bir kod buldum

lib.rs


#[no_mangle]
pub extern fn double_input(input: i32) -> i32 {
    input * 2
}

main.py

from ctypes import cdll
from sys import platform

if platform == 'darwin':
    prefix = 'lib'
    ext = 'dylib'
elif platform == 'win32':
    prefix = ''
    ext = 'dll'
else:
    prefix = 'lib'
    ext = 'so'

lib = cdll.LoadLibrary('target/debug/{}double_input.{}'.format(prefix, ext))
double_input = lib.double_input

input = 4
output = double_input(input)
print('{} * 2 = {}'.format(input, output))

Rust da yazılmış bir kodu nasıl ctypes ile çalıştırabiliyorlar ?
Ayrıca, araştırma yaparken incelediğim linkler:
https://stackoverflow.com/questions/5440968/understand-foreign-function-interface-ffi-and-language-binding
https://en.wikipedia.org/wiki/Foreign_function_interface
http://www.dia.uniroma3.it/~scorzell/cscheme/ffi.htm
https://spin.atomicobject.com/2013/02/15/ffi-foreign-function-interfaces/
Birde eğer sizde araştırmak istiyorsanız FFI in programming diye arayın FFI diye araştırınca hastalık çıkıyor.

Programlarin, birbirlerini ve kutuphaneleri (aslinda hepsi ayni sey) cagirdiklari arayuze ABI (Application Binary Interface) deniyor. ABI, parametrelerin nasil yollanacagini, hedef adrese hangi CPU instruction’i ile gidilecegini, hangi data turlerinin hangi sekilde kullanildigini vs. tanimliyor. Ayni ABI’ye sahip her kod bir digeriyle iletisim kurabilir.

Python C’de yazildigi icin degil, sistemin geri kalaninin kullandigi ABI’yi kullandigi icin C kodu calistirabiliyor. Aslinda calistirdigi kod C kodu degil, platformun calistirilabilir (executable) dosyasi (PE veya ELF).

Buradaki no_mangle direktifi fonksiyonun executable icindeki tablolarda isminin double_input olmasini sagliyor. Disaridan ismiyle arayan bulsun diye. Yoksa kim bilir belki super-double_input olacakti, Rust’in her fonksiyonun ismine “super-” ekledigini ve bu fonksiyonun Rust’ta yazildigini bilmeyen kimse bunu tahmin edemeyecekti.

Buradaki i32 kotu bir secim; 4 byte’lik bir sayi bekliyor/sunuyor. Bunun hangi ABI type’ina denk geldigi sisteme bagli. C int’ine denk gelmesini istiyorduysak c_int kullanmaliydik.

Burada sistemin dynamic linking mekanizmasi kullaniliyor.
bkz: WIN32 LoadLibrary (simdilik LoadLibraryA function (libloaderapi.h) - Win32 apps | Microsoft Learn)
bkz: https://linux.die.net/man/3/dlopen

Bu konuyu CPython objelerini (PyXxx) araya karistirmadan ogrenmeni tavsiye ediyorum. (ctypes’i kullanabilirsin, Python’in FFI’si o.)

Bu arada bu Python kodu. C’de yazilmis olabilir ama CPython objelerini kullanarak, Python koduyla haberlesmek icin yazilmis. FFI ve aslinda ogrenmek istedigin mevzularla sadece uzaktan alakali. Veya simdilik oyle diyelim, detaylarini anlatmam mumkun degil. (Hem ben iyi bilmiyorum, hem de senin biraz daha FFI bilmen gerekiyor.)

4 Beğeni

Suna da bakmak isteyebilirsin: C++ Libraryler hakkında sorular(aklıma takılan sorular) - aib tarafından #4

1 Beğeni

Olaya çok yanlış açıdan bakmışım. Mesela Rust değil de herhangi bir derlenen dili de aynı ABI yi kullanıyorlarsa çağırabiliriz değil mi ?

Mesela windows un executable dosyası exe. O zaman C ile bir program yazıp exe ye derlersem ve şunu çalıştırırsam:

import os
os.system("cd C:\\Dosyanın\\Konumu\\&programadi.exe")

bu ctypes ın yaptığı ile aynı mı oluyor ?
Birde yukarıdaki rust örneğinde input * 2 return ediliyor sanırım, sonuçta python programında input * 2 nin sonucunu görebiliyoruz. Ancak os.system i kullanınca return edilen değer 1 veya 0 oluyor (asdasdasd gibi çalışmayacak bir komut girince 1, echo hello world gibi çalışan bir komutta 0 döndürüyor)

Edit: az önce basit bir program yazdım

int main()
{
	return 5;
}

ve

import os
print(os.system("yeni.exe"))

Sonuç

5
>>>

oldu. Ama kodu biraz düzenleyince zorlaştı mesela yeni bir fonksiyon ekleyince onu çağıramadım. main fonksiyonu başlangıçta çalıştığından 5 i döndürdü ama

int main()
{
	return 5;
}

int yenifonksiyon()
{
	return 15;
}

yenifonksiyon u çağıramıyorum.

Evet. ABI’nin gorevi/tanimi bu.

Hayir. Bu ayri bir process yaratiyor. LoadLibrary executable’i mevcut process’in hafizasina yukluyor. Sonrasinda main fonksiyonunu bulup cagirirsan cok benzer bir sey yapmis olabilirsin.

Evet

Python programiyla rust programi yani program. Bir tane exe var (python.exe). Kendi icinde fonksiyon cagirip return value’sunu kullaniyor.

os.system’in return ettigi deger yani calistirilan process’in cikis kodu.

os.system fonksiyon cagirma arayuzu degil, yeni process calistirip, bitmesini bekleyip sonucunu dondurme (yanlis hatirlamiyorsam) arayuzu. Varligini unutabilirsin; konumuzla alakasi yok.

Öyleyse os.system ı boşveriyorum.

Yani aslında ctypes ın yaptığı python.exe içinde verdiğimiz dll veya exe den fonksiyonu çağırmak ve return value’sunu almak/fonksiyonun yaptıklarını yapmak.

ctypes’in bir suru gorevi var, en onemlisi C ABI’sindeki int, float*, char[8] gibi turleri (verileri) python objelerine cevirmek (ve tersi).

lib = LoadLibrary; lib.fonksiyon_ismi() yaptiginda ne yaptigini linkledigim LoadLibrary veya dlopen sayfasindan okuyabilirsin.

LoadLibrary + GetProcAddress (Win32) = dlopen + dlsym (POSIX)

Bunun isiginda yazdigim koda tekrar bak.

Yazdigim kodun dinamik kisminin (hello.c, main_dyn.c) Windows versiyonunu yaz ve calistir. (hello.so → hello.dll, main_dyn → main_dyn.exe)
Sonra ctypes ile calistir.
Sonra sayi paslayip dondur.
Sonra liste paslayip toplamini dondur.
Butun bunlari python+ctypes disinda bir C programiyla da yap.

1 Beğeni

C programı olarak yaptım ama python+ctypes olarak çalışmadı (sayı ve liste döndürmeyi yaptım).

Edit: LoadLibraryA yı Türkçe çeviri ile 2.kez okudum ve daha iyi anladım. Sanırım LoadLibraryA yı python.exe den çağırırsak o zaman belirttiğimiz dll i python.exe nin içinde çalıştırıyor, ayrı bir process olarak değil. Ayrıca artık LoadLibrary + GetProcAddress (Win32) = dlopen + dlsym (POSIX) çok daha anlamlı geliyor.

python.exe ile aynı process e dll i yüklüyor ve fonksiyon_ismi ni çağırıyoruz.

Evet, her sey aynen dedigin gibi!
C program(lar)inin da daha fazla vakit almasini beklerdim, takdirler.

Soylediklerimin biraz ileri veya karisik gelebildiginin farkindayim, ama bir takim seyleri okuyup/ogrenip geri gelindiginde de ise yaramalarini amacliyorum. Giris yazisi yazmaya veya tercume etmeye gerek yok; giristen ilerideki insanlarin kafalarinin takilabilecegi noktalara deginmeyi seviyorum. (Giristekilerin akillarini karistirma pahasina.) ← Bu paragraf ne kadar anlamdi oldu bilmiyorum, erken kalktim :​D

Burada paylas istersen, beraber bakalim.

C yi nerdeyse hiç bilmeyince zorladı baya.

Olsun biraz daha araştırmayla öğrenirim.

Sorun yok :smiley:

#include <stdio.h>

int kod2()
{
    int arr[3];
    arr[0] = 10;
    arr[1] = 20;
    arr[2] = 30;
    int sonuc = 0;
    for (int i = 0;i < 3;i++)
    {
        sonuc += arr[i];
    }
    return sonuc;
}

int kod1()
{
    return 5;
}

int main()
{
    int a = kod1();
    int b = kod2();
    printf("%d\n", a);
    printf("%d", b);
	return 0;
}

Bu kodda çalışmayan ne?

Derleyince çalışıyor ama ctypes ta bu fonksiyonlardan birini çağırmaya çalışınca hata veriyor.

Kodu ve hata mesajini yazmayi unutmussun :slight_smile:

Bunu calistiran kod nerede?
DLL/SO olarak derleyip baska bir executable’da LoadLibrary/dlopen + GetProcAddress/dlsym kullanarak calistirman lazim fonksiyonlari.

Pardon ben sadece C kısmını atmışım :smiley:

Bunu main in içinde çalıştırıyorum zaten, fazladan bir şey mi eklemem gerekiyor ?

DLL olarak derleyememiştim ama tekrar bir deneyeyim.

Pythonda yazdığım kod:

from ctypes import cdll

lib = cdll.LoadLibrary("C:\\Users\\Furkan\\Desktop\\yeni2.dll")
kod2 = lib.kod2

print(kod2)

Hata: OSError: [WinError 193] %1 geçerli bir Win32 uygulaması değil

Birde bir messagebox çıkıyor onda da yazanlar şunlar:
C:\Users\Furkan\Desktop\yeni2.dll programı Windows üzerinde çalışmak üzere tasarlanmadı veya hata içeriyor. Programı özgün yükleme medyasını kullanarak yüklemeyi deneyin veya destek için sistem yöneticinize veya yazılım satıcısına başvurun. Hata durumu: 0xc000012f.

DLL olarak derlerken aynı kodu kullandım belki ondan hata veriyordur bilmiyorum belki de değişiklik yapmam lazımdı. Öyle düşünmemin sebebi şu linkteki yazı:
https://www.codementor.io/@a_hathon/building-and-using-dlls-in-c-d7rrd4caz
Mesela burda void message(void); değilde

#define EXPORT __declspec(dllexport)

EXPORT void message (void);

yapmış. Ama DLL oluşturmayı denemeye devam edicem belki daha kolay bir yol bulurum :smiley:
Birde

Nasıl hem LoadLibrary hem de GetProcAddress olarak çalıştırabilirim ki ? Mesela

lib = cdll.LoadLibrary("C:\\Users\\Furkan\\Desktop\\yeni2.dll")

yerine

lib = cdll.LoadLibrary("C:\\Users\\Furkan\\Desktop\\yeni2.dll") + GetProcAddress("C:\\Users\\Furkan\\Desktop\\yeni2.dll")

gibi mi ?

Python’da degil.

  1. LoadLibraryA function (libloaderapi.h) - Win32 apps | Microsoft Learn
  2. GetProcAddress function (libloaderapi.h) - Win32 apps | Microsoft Learn
  3. C++ Libraryler hakkında sorular(aklıma takılan sorular) - aib tarafından #4

DLL olmamis gibi.

LoadLibraryA ve GetProcAddress in ne işe yaradığını anladım ama bunları c de yazdığım kodda mı kullanmam gerekiyor ?

Sonunda oldu! https://www.cygwin.com/cygwin-ug-net/dll.html adresindeki kodu denedim ve bir DLL oluşturabildim. Ayrıca python da kullanmayı denerken de hata vermiyor.
Kod:

#include <stdio.h>

int
hello()
{
	printf("Hello World!\n");
}

Ayrıca fonksiyonu çağırıp ekrana Hello World! yazdıramamış olsam da en azından fonksiyonun adresini alabildim: <_FuncPtr object at 0x0000020B480BBD40>

Evet, ctypes’in yaptigini yapmis olacaksin.

lib.hello() ?

Valla bende yukardaki kod1/kod2 “cat” diye calisti. Parametre/return’lerini soylemem bile gerekmedi. (Herhalde int(void)'e default ediyor)

0 yazdırıyor.

Peki kodun neresine eklemem gerekiyor ? Ne işe yaradıklarını anladım da nereye eklendiklerini anlamadım.

Kodu terminalde çalıştırın.

Return değeri ve paslanan bütün argümanlar default olarak int.