"Delphi'de MQL5 için DLL yazma kılavuzu" makalesi için tartışma

 

Yeni makale Delphi'de MQL5 için DLL yazma kılavuzu yayınlandı:

Makalede, Delphi programlama ortamında popüler ObjectPascal programlama dili kullanılarak bir DLL modülü oluşturma mekanizması incelenmektedir. Bu makalede sağlanan materyallerin, öncelikli olarak, dış DLL modüllerini bağlayarak MQL5'in gömülü programlama dilinin sınırlarını aşan problemleri çözmeye çalışan yeni başlayan programcıları hedeflemesi amaçlanmıştır.

Göstergenin çalışmasının sonucu, Şekil 6'da gösterildiği gibi mavi bir regresyon kanalının oluşturulmasıdır. Kanalın yapısının doğruluğunu doğrulamak için grafik, MetaTrader 5 teknik analiz enstrümanlarının personel cephaneliğinden kırmızı ile işaretlenmiş bir "Regresyon Kanalı" göstermektedir.

Şekilde görüldüğü gibi, kanalın merkez çizgileri birleşmektedir. Bu arada, hesaplamasındaki farklı yaklaşımlardan dolayı kanalın genişliğinde (birkaç nokta) küçük bir fark vardır. 

 Şekil 6. Regresyon kanallarının karşılaştırması

Şekil 6. Regresyon kanallarının karşılaştırması

Yazar: Andrey Voytenko

 
Uzun zamandır böyle bir makale bekliyordum. Yazara teşekkürler.
 
DC2008:
Uzun zamandır böyle bir makale bekliyordum. Yazara teşekkür ederim.

Ben teşekkür ederim. Bana ilk teşekkür eden siz değilsiniz. Makalenin materyali hakkındaki tüm dilekleri ve eleştirel yorumları dinlemekten memnuniyet duyacağım.

Gelecekte Delphi'de programlama konusunu geliştirmek istiyorum MT5 için siteye yeni bilgiler ekleyerek.

 
Hata ayıklama hakkında en azından bir paragraf eklemelisiniz. Makalede AV'nin ortaya çıkabileceği bir durumdan bahsediliyor, ancak diğer potansiyel hata kaynaklarını bir kenara bıraksak bile, hatanın yerini manuel olarak (gözle veya zihinsel olarak) aramaya çalışmak çok uzun zaman alabilir ve başarılı olamaz.
 

Birçok kişi için faydalı bir makale olduğunu düşünüyorum. Birkaç yorum:

1. SysUtils ve Classes birimleri projede bırakılmalıydı. Varlıkları projeyi biraz "şişirmesine" rağmen, birçok küçük ama önemli işlevleri vardır. Örneğin, SysUtils'in varlığı projeye otomatik olarak istisnaların işlenmesini ekler. Bildiğiniz gibi, bir istisna dll'de işlenmezse, mt5'e aktarılır ve burada mql5 programının yürütülmesinin durmasına neden olur.

2. DllEntryPoint (diğer adıyla DllMain) içinde her türlü prosedürü kullanmamalısınız. Microsoft'un belgelerinde belirttiği gibi, bu çeşitli hoş olmayan etkilerle doludur. İşte bu konuyla ilgili makalelerin küçük bir listesi:

Microsoft tarafından DLL Oluşturmak için En İyi Uygulamalar - http://www.microsoft.com/whdc/driver/kernel/DLL_bestprac.mspx.

DllMain ve doğumdan önceki yaşam - http://transl-gunsmoker.blogspot.com/2009/01/dllmain.html

DllMain - bir uyku vakti hikayesi - http://transl-gunsmoker.blogspot.com/2009/01/dllmain_04.html

DllMain'inizde korkutucu bir şey yapmamak için birkaç neden - http://transl-gunsmoker.blogspot.com/2009/01/dllmain_05.html

DllMain'inizde korkutucu bir şey yapmamak için daha fazla neden: yanlışlıkla kilitleme -

http://transl-gunsmoker.blogspot.com/2009/01/dllmain_7983.html

 

Bitmemiş bir makaleden bir alıntı yapmıştım, sanırım dörtlü forumda. Burada tekrar edeceğim.

başla...bitir

DLL derleme amaçlı bir Delphi projesi oluştururken, .DPR proje dosyasında begin...end bölümü görünür. Bu bölüm, DLL işlem adres alanına ilk kez yansıtıldığında her zaman çalıştırılır. Başka bir deyişle, tüm birimlerin sahip olduğu bir tür başlatma bölümü olarak düşünülebilir. Burada, mevcut süreç için en başta ve yalnızca bir kez gerçekleştirilmesi gereken bazı eylemleri gerçekleştirebilirsiniz. DLL başka bir sürecin adres alanına yüklenirken, bu bölüm orada tekrar çalıştırılacaktır. Ancak süreçlerin adres uzayları birbirinden ayrılmış olduğundan, bir süreçteki başlatma diğer süreci hiçbir şekilde etkilemeyecektir.

Bu bölümde bilmeniz ve dikkate almanız gereken bazı sınırlamalar vardır. Bu sınırlamalar Windows DLL yükleme mekanizmasının incelikleriyle ilgilidir. Bunlar hakkında daha sonra daha ayrıntılı olarak konuşacağız.

 

başlatma/sonlandırma

Her birim Delphi 'de başlatma ve sonlandırma bölümleri olarak adlandırılan özel bölümlere sahiptir. Herhangi bir birim projeye bağlanır bağlanmaz bu bölümler ana modülün özel yükleme ve boşaltma mekanizmasına bağlanır. Ve bu bölümler ana begin...end bölümü çalışmaya başlamadan önce ve çalışma bittikten sonra çalıştırılır . Bu çok kullanışlıdır, çünkü programın kendi içinde başlatma ve sonlandırma yazma ihtiyacını ortadan kaldırır. Aynı zamanda, bağlantı ve bağlantı kesme otomatik olarak gerçekleştirilir, sadece üniteyi projeye bağlamanız veya bağlantıyı kesmeniz gerekir. Ve bu sadece geleneksel EXE dosyalarında değil, aynı zamanda DLL'lerde de olur. DLL'nin belleğe "yüklenirken" başlatılma sırası aşağıdaki gibidir, önce ünitenin tüm başlatma bölümleri, projenin kullanımlarında işaretlendikleri sırayla yürütülür, ardından başlangıç...bitiş bölümü yürütülür. DLL proje dosyasında özel olarak tasarlanmış bir sonlandırma fonksiyonu olmaması dışında, sonlandırma ters sırada yapılır. Bu, genel olarak, DLL projelerinin bir proje dosyası ve bir kullanım birimi olarak ayrılmasının önerilmesinin bir başka nedenidir.

 

DllMain

Bu, DLL giriş noktası olarak adlandırılır. Mesele şu ki, Windows'un zaman zaman süreç içinde meydana gelen herhangi bir olayı DLL'nin kendisine bildirmesi gerekir. Bunu yapmak için bir giriş noktası vardır. Yani, her DLL'nin sahip olduğu ve mesajları işleyebilen özel olarak önceden tanımlanmış bir işlev. Delphi'de yazılmış bir DLL'de bu fonksiyonu henüz görmemiş olsak da, yine de böyle bir noktası vardır. Sadece işleyiş mekanizması örtülüdür, ancak her zaman ona ulaşabilirsiniz. Sorunun cevabı - hiç gerekli mi? - göründüğü kadar açık değildir.

Öncelikle Windows'un DLL'e ne anlatmaya çalıştığını anlamaya çalışalım. İşletim sisteminin DLL'e geldiği toplam 4 mesaj vardır. Bunlardan ilki olan DLL_PROCESS_ATTACH bildirimi, sistem bir DLL'i çağıran sürecin adres alanına eklediğinde gönderilir. MQL4durumunda bu örtük bir yüklemedir. Bu DLL'in başka bir sürecin adres alanına zaten yüklenmiş olması önemli değildir, mesaj yine de gönderilecektir. Ve aslında Windows'un belirli bir DLL'yi belleğe yalnızca bir kez yüklemiş olması da önemli değildir, bu DLL'yi adres alanlarına yüklemek isteyen tüm süreçler yalnızca bu DLL'nin bir yansımasını alır. Bu aynı koddur, ancak DLL'nin sahip olabileceği veriler her işlem için benzersizdir (ortak verilerin mevcut olması mümkün olsa da). İkincisi, DLL_PROCESS_DETACH bildirimi, DLL'e çağıran sürecin adres alanından ayrılmasını söyler. Aslında, bu mesaj Windows DLL'i boşaltmaya başlamadan önce alınır. Aslında, DLL başka süreçler tarafından kullanılıyorsa, herhangi bir boşaltma gerçekleşmez, Windows sadece DLL'in sürecin adres alanında var olduğunu "unutur". İki bildirim daha, DLL_THREAD_ATTACH veDLL_THREAD_DETACH, DLL' yi yükleyen süreç, süreç içinde iş parçacıkları oluşturduğunda veya yok ettiğinde alınır. Konu bildirimlerinin alınma sırası ile ilgili birkaç ince konu vardır, ancak bunları ele almayacağız.

Şimdi Delphi'de yazılan DLL'lerin nasıl düzenlendiğine ve genellikle programcılardan neyin gizlendiğine gelelim. Windows DLL'i "çağıran sürecin adres alanına yansıttıktan" ya da basitçe DLL'i belleğe yükledikten sonra, bu noktada giriş noktasında bulunan fonksiyona bir çağrı yapılır ve DLL_PROCESS_ATTACH bildirimi bu fonksiyona iletilir. Delphi'de yazılmış bir DLL'de, bu giriş noktası, birimlerin başlatılmasını başlatmak da dahil olmak üzere birçok farklı şey yapan özel kod içerir. DLL'in başlatma ve ilk çalıştırma işleminin yapıldığını hatırlar ve ana proje dosyasının begin...end kısmını çalıştırır. Böylece, bu ilk yükleme kodu yalnızca bir kez çalıştırılır, giriş noktasına yapılan diğer tüm Windows çağrıları, sonraki bildirimleri işleyen başka bir işleve yapılır - aslında, birimi sonlandıran DLL_PROCESS_DETACH mesajı dışında bunları yok sayar. Delphi'de yazılmış bir DLL'yi yükleme mekanizması genel anlamda bu şekilde görünür. Çoğu durumda, DLL'leri MQL4'te yazmak ve kullanmak yeterlidir.

Eğer hala C'deki gibi bir DllMain'e ihtiyacınız varsa, bunu düzenlemek zor değildir. Bu aşağıdaki gibi yapılır. Bir DLL ilk kez yüklenirken, diğer şeylerin yanı sıra, System modülü (her zaman bir programda veya DLL'de bulunur) otomatik olarak nil ile başlatılan global bir prosedürel değişken DllProc oluşturur. Bu, DllMain bildirimleri için var olandan başka hiçbir ek işleme gerek olmadığı anlamına gelir. İşlevin adresi bu değişkene atanır atanmaz, Windows'tan DLL'ler için gelen tüm bildirimler bu işleve gidecektir. Giriş noktası için gerekli olan da budur. Ancak, DLL_PROCESS_DETACH bildirimi, sonlandırmaya izin vermek için daha önce olduğu gibi DLL sonlandırma işlevi tarafından izlenmeye devam edecektir.

prosedürDllEntryPoint(Reason: DWORD);

başla

durum Sebep

DLL_PROCESS_ATTACH : ;//'Bağlantı süreç'

DLL_THREAD_ATTACH : ;//'Bağlanıyor iplik'

DLL_THREAD_DETACH : ;//'Bir iş parçacığınınbağlantısı kesiliyor'. akış'

DLL_PROCESS_DETACH : ;//'Bağlantının kesilmesi süreç'

son;

Bitti;

başla

if not Assigned(DllProc) then begin

DllProc :=@DllEntryPoint;

DllEntryPoint (DLL_PROCESS_ATTACH);

end;

Bitti.

İş parçacığı bildirimleriyle ilgilenmiyorsak, tüm bunlar gereksizdir. Süreç bağlantı ve bağlantı kesme olayları otomatik olarak izleneceğinden, yalnızca başlatma / sonlandırma bölümlerini birim olarak düzenlemek gerekir.

 

DllMain'in hainliği ve ihaneti

Şimdi, belki de programlama literatüründe şaşırtıcı derecede az ele alınan bir konuya değinmenin zamanı gelmiştir. Bu konu sadece Delphi veya C ile değil, DLL oluşturabilen tüm programlama dilleriyle ilgilidir. Bu, Windows DLL yükleyicisinin bir özelliğidir. Windows ortamında programlama üzerine çevrilmiş ciddi ve yaygın literatürde, sadece bir yazar bu konudan bahsetmeyi başarmıştır ve o da en belirsiz terimlerle. Bu yazar J. Richter'dir ve kendisi affedilebilir, çünkü harika kitabı 2001 yılında, genel olarak 32-bit Windows'un bu kadar yaygın olmadığı bir dönemde yayınlanmıştır.

İlginçtir ki MS, DllMain ile ilgili sorunun varlığını hiçbir zaman saklamadı ve hatta "DllMain'i kullanmanın en iyi yolu" gibi özel bir belge yayınladı. Bu belgede DllMain'de nelerin yapılabileceği ve nelerin tavsiye edilmediği açıklanmıştır. Ve tavsiye edilmeyen şeylerin görülmesi zor ve tutarsız hatalara yol açtığına dikkat çekildi. Bu belgeyi okumak isteyenler buraya bakabilirler. Konuyla ilgili çeşitli alarmist raporların birkaç çevirisinin daha popüler bir özeti burada özetlenmiştir.

Sorunun özü çok basittir. Mesele şu ki, DllMain, özellikle bir DLL yüklenirken, özel bir yerdir. Karmaşık ve olağanüstü bir şey yapmamanız gereken bir yer. Örneğin, diğer DLL'leri CreateProcess veya LoadLibraryyapmanız önerilmez . Ayrıca CreateThread veya CoInitialise COM da önerilmez . Ve böyle devam eder.

En basit şeyleri yapabilirsiniz. Aksi takdirde, hiçbir şey garanti edilmez. Bu nedenle, DllMain'e gereksiz hiçbir şey koymayın, aksi takdirde DLL'nizi kullanan uygulamaların çöktüğünü görünce şaşıracaksınız. Güvende olmak ve ana uygulama tarafından doğru anlarda çağrılacak olan özel dışa aktarılmış başlatma ve sonlandırma işlevleri oluşturmak daha iyidir. Bu en azından DllMain ile ilgili sorunları önlemeye yardımcı olacaktır.
 

ExitProc, ExitCode,MainInstance,HInstance....

Derleme zamanında DLL'nize her zaman bağlanan System modülü, kullanabileceğiniz bazı yararlı global değişkenlere sahiptir.

ExitCode, - yükleme sırasında 0 dışında bir sayı koyabileceğiniz bir değişken, sonuç olarak DLL yüklemesi duracaktır.

ExitProc, - çıkışta çalıştırılacak fonksiyonun adresini saklayabilen prosedürel bir değişkendir. Bu değişken uzak geçmişin bir kalıntısıdır, DLL'lerde işlev görmez ve dahası, Delphi geliştiricileri olası sorunlar nedeniyle DLL'lerde kullanılmasını önermemektedir.

HInstance, - yüklemeden sonra DLL'nin kendisinin tanımlayıcısının depolandığı bir değişken. Çok faydalı olabilir.

MainInstance, - DLL'yi adres alanına yükleyen uygulamanın tanımlayıcısı.

IsMultiThread, - DLL'nin derlenmesi iş parçacıklarıyla çalışmayı algılarsa otomatik olarak True olarak ayarlanan bir değişken. Bu değişkenin değerine bağlı olarak DLLbellek yöneticisi çok iş parçacıklı moda geçer. Prensip olarak, DLL'de iş parçacıkları açıkça kullanılmasa bile bellek yöneticisini çok iş parçacıklı moda geçmeye zorlamak mümkündür. IsMultiThread:=True; Doğal olarak, çok iş parçacıklı mod, iş parçacıklarının birbiriyle senkronize olması nedeniyle tek iş parçacıklı moddan daha yavaştır.

MainThreadID, - uygulamanın ana iş parçacığının tanımlayıcısı.

Ve bunun gibi. Genel olarak, Sistem modülü C'deki CRT ile yaklaşık olarak aynı işlevleri yerine getirir. Bellek yönetimi işlevleri dahil. Derlenen DLL'de bulunan tüm işlevlerin ve değişkenlerin bir listesi, yalnızca dışa aktarılanlar değil, hepsi, Proje ayarlarında Bağlayıcı seçeneği olan Eşleme dosyası - Ayrıntılı'yı açarsanız elde edilebilir.
 

Bellek yönetimi

Genellikle zorluklara neden olan bir sonraki, oldukça ciddi konu DLL'lerdeki bellek yönetimidir. Daha doğrusu, bellek yönetiminin kendisi herhangi bir soruna neden olmaz, ancak DLL, uygulamanın bellek yöneticisi tarafından tahsis edilen bellekle aktif olarak çalışmaya çalışır çalışmaz - sorunların genellikle başladığı yer burasıdır.

Mesele şu ki, genellikle uygulamalar içinde MemoryManager ile derlenir. Derlenen DLL de kendi MemoryManager'ını içerir. Bu özellikle farklı programlama ortamlarında oluşturulan uygulamalar ve DLL'ler için geçerlidir. Bizim durumumuzda olduğu gibi, terminal MSVC'de, DLL ise Delphi'dedir. Bunların yapıları gereği farklı yöneticiler olduğu açıktır, ancak aynı zamanda fiziksel olarak farklı yöneticilerdir ve her biri sürecin ortak adres alanı içinde kendi belleğini yönetir. Prensip olarak, birbirlerine müdahale etmezler, birbirlerinin belleğini almazlar, birbirlerine paralel olarak var olurlar ve genellikle rakiplerin varlığı hakkında hiçbir şey bilmezler. Bu mümkündür çünkü her iki yönetici de belleğe aynı kaynaktan, Windows bellek yöneticisinden erişir.

Bir DLL işlevi veya bir uygulama farklı bir bellek yöneticisi tarafından dağıtılan bellek bölümlerini yönetmeye çalıştığında sorunlar başlar. Bu nedenle, programcılar arasında "bellek bir kod modülünün sınırlarını geçmemelidir" şeklinde bir genel kural vardır.

Bu iyi bir kuraldır ancak tam olarak doğru değildir. Uygulamanın kullandığı DLL'de aynı MemoryManager'ı kullanmak daha doğru olacaktır. Aslında, MT4bellek yöneticisini Delphibellek yöneticisine FastMMbağlama fikrini oldukça beğendim , ancak bu hiç de uygulanabilir bir fikir değil. Her neyse, bellek yönetimi tek bir şey olmalıdır.

Delphi'de varsayılan bellek yöneticisini bazı gereksinimleri karşılayan başka bir bellek yöneticisi ile değiştirmek mümkündür. Böylece DLL ve uygulamanın tek bir bellek yöneticisine sahip olmasını sağlamak mümkündür ve bu MT4 bellek yöneticisi olacaktır.