English Русский 中文 Español Deutsch 日本語 Português 한국어 Français Italiano
Yönetilmeyen dışa aktarmaları kullanarak C# kodunu MQL5'e gösterme

Yönetilmeyen dışa aktarmaları kullanarak C# kodunu MQL5'e gösterme

MetaTrader 5Entegrasyon | 16 Aralık 2021, 14:56
233 0
investeo
investeo

Tanıtım

MQL5'te yönetilen mod C# DLL'lerini kullanmamı sağlayacak basit bir çözüm bulmak için uzun zamandır arıyordum. Birçok makaleyi okuduktan sonra, beni saatlerce çalışmaktan kurtaran mükemmel bir çözümle karşılaştığımda, yönetilen DLL için C++ sarmalayıcısını uygulamaya hazırdım.

Çözüm, yönetilmeyen uygulama tarafından tüketilmek üzere yönetilen C# kodunu dışa aktarmanın basit bir örneğini sağladı. Bu makalede, yönetilen mod DLL'leri hakkında bir arka plan sunacağım, neden doğrudan MetaTrader'dan erişilemediklerini açıklayacağım ve MetaTrader'dan yönetilen kodu kullanmayı sağlayan bulduğum çözümleri tanıtacağım.

Yönetilmeyen dışa aktarma şablonunun basit kullanımına bir örnek vereceğim ve keşfettiğim her şeye devam edeceğim. Bu, MetaTrader 5'te C# DLL kodunu kullanmaya çalışan herkes için sağlam bir arka plan sağlamalıdır.


1. Yönetilen ve Yönetilmeyen kod karşılaştırması

Okuyucuların çoğu yönetilen ve yönetilmeyen kod arasındaki farkın farkında olmayabilir, bunu birkaç cümleyle anlatacağım. Temel olarak MetaTrader, ticaret kurallarını, göstergeleri, uzman danışmanları ve komut dosyalarını uygulamak için MQL dilini kullanır. Bununla birlikte, diğer dillerde zaten uygulanmış kitaplıkları kullanabilir ve bunları çalışma zamanı sırasında dinamik olarak bağlayabilir. Bu kitaplıklara DLL veya Dynamic Link Libraries de denir.

Kitaplıklar aslında, belirli işlemleri gerçekleştirmek için bir dizi harici program tarafından çağrılabilen derlenmiş kaynak kodunu içeren ikili dosyalardır. Örneğin, sinir ağı kitaplığı, sinir ağı eğitimi ve testi için işlevleri dışa aktarabilir, türev kitaplığı, farklı türevlerin hesaplamalarını dışa aktarabilir, matris kitaplığı, matrisler üzerindeki işlemleri dışa aktarabilir. MetaTrader için DLL'ler, göstergelerin veya uzman danışmanların uygulama bölümlerini gizlemeyi mümkün kıldıkları için giderek daha popüler hale geldi. Kitaplıkları kullanmanın ana nedeni, mevcut kodu tekrar tekrar uygulamaya gerek kalmadan yeniden kullanmaktır.

.NET'ten önce, Visual Basic, Delphi, VC++ tarafından derlenen tüm DLL'ler, COM, Win32 veya düz C++, doğrudan işletim sistemi tarafından yürütülebilirdi. Bu kodu yönetilmeyen veya yerel kod olarak adlandırıyoruz. Sonra .NET ortaya çıktı ve çok farklı bir ortam sağladı.

Kod, .NET Common Language Runtime - hCLR tarafından kontrol edilir (veya yönetilir). CLR derleyicilerinin birkaç farklı dilde yazılabilen kaynak kodundan, Meta Veriler ve Ortak Ara Dilden - CIL - üretmeleri gerekir.

CIL, makineden bağımsız üst düzey bir dildir ve Meta Veriler, Ortak Tip Spesifikasyonuna göre CIL tarafından açıklanan nesnelerin türlerini tam olarak tanımlar: CTS. CLR, türler hakkında her şeyi bildiğinden, bize yönetilen yürütme ortamı sağlayabilir. Yönetim, çöp toplama - otomatik bellek yönetimi ve nesnelerin silinmesi ve güvenliğin sağlanması - yerel dillerde yönetici ayrıcalıklarıyla yabancı kod yürütülmesine veya yalnızca bellek geçersiz kılmasına neden olabilecek yaygın hatalara karşı koruma olarak düşünülebilir.

CIL kodunun hiçbir zaman doğrudan yürütülmediğini belirtmek gerekir - her zaman JIT (Just-In-Time) derlemesine göre veya CIL ön derlemesiyle başlayıp derleme yapılarak yerel makine koduna çevrilir. Bunu ilk kez okuyan bir kişi için yönetilen mod kodu kavramı kafa karıştırıcı olabilir, bu nedenle genel akışı aşağıya CLR'ye yapıştırıyorum:

 

 

Şekil 1. Ortak dil çalışması 


2. MQL5'ten yönetilen koda erişmenin olası uygulamaları

Aşağıdaki paragrafta, yönetilmeyen koddan yönetilen koda erişmeyi sağlayan yöntemleri anlatacağım.

Benim kullandığım yöntem yerine başka bir yöntemi kullanmayı tercih edecek birileri olabileceği için hepsinden bahsetmeye değer olduğunu düşünüyorum. Kullanımda olan yöntemler, COM Birlikte Çalışma, Ters P/Invoke, C++ IJW, C++/Cli sarmalayıcı sınıfı ve Yönetilmeyen Dışa Aktarma'dır.


2.1. COM Birlikte Çalışma 

Bileşen Nesne Modeli (COM), doksanların başlarında Microsoft tarafından tanıtılan bir ikili arabirim standardıdır. Bu teknolojinin temel fikri, farklı programlama dillerinde oluşturulan nesnenin, dahili uygulamasını bilmeden başka herhangi bir COM nesnesi tarafından kullanılmasını sağlamaktır. Bu tür gereksinim, COM'un uygulamadan tamamen ayrı katı, iyi tanımlanmış arabiriminin uygulanmasını zorunlu kılar.

Aslında COM'un yerini .NET teknolojisi aldı ve Microsoft, COM yerine .NET kullanmaya zorladı. .NET, eski kodla geriye dönük uyumluluk sağlamak için COM ile her iki yönde de işbirliği yapabilir, yani .NET COM yöntemlerini çağırabilir ve COM nesnesi .NET yönetilen kodu kullanabilir.

Bu işleve COM Birlikte Çalışabilirliği veya COM Birlikte Çalışabilirliği adı verilir. COM birlikte çalışma API'si, yönetilen System.Runtime.InteropServices ad alanındadır.

 

Şekil 2. COM Birlikte Çalışabilirlik modeli

Şekil 2. COM Birlikte Çalışabilirlik modeli 


Aşağıdaki COM birlikte çalışma kodu, tek bir raw_factorial işlevini çağırır.

Lütfen CoInitialize() CoCreateInstance() ve CoUninitialize() işlevlerine ve arabirim çağırma işlevine dikkat edin:

#include "windows.h"
#include <stdio.h>
#import "CSDll.tlb" named_guids

int main(int argc, char* argv[])
{
    HRESULT hRes = S_OK;
    CoInitialize(NULL);
    CSDll::IMyManagedInterface *pManagedInterface = NULL;

    hRes = CoCreateInstance(CSDll::CLSID_Class1, NULL, CLSCTX_INPROC_SERVER, 
     CSDll::IID_IMyManagedInterface, reinterpret_cast<void**> (&pManagedInterface));

    if (S_OK == hRes)
    {
        long retVal =0;
        hRes = pManagedInterface->raw_factorial(4, &retVal);
        printf("The value returned by the dll is %ld\n",retVal);
        pManagedInterface->Release();
    }

    CoUninitialize();
    return 0;
}

COM Birlikte Çalışma hakkında daha fazla bilgi için lütfen Interop'a Giriş ve msdn blogunda bulduğum kullanım örneğindeki ayrıntılı belgeleri okuyun: C++ kodu Yönetilen'den nasıl çağrılır ve tam tersi (Interop).


2.2. Ters P/Çağrı

P/Invoke olarak anılan Platform Invoke, .NET'in imzası yeniden bildirildiği sürece herhangi bir yönetilmeyen dilde herhangi bir işlevi çağırmasını sağlar. Bu, .NET'ten yerel bir işlev işaretçisi yürütülerek elde edilir. Kullanım Platform Invoke Tutorial'da iyi açıklanmıştır.

Temel kullanım, içe aktarılan işlevi işaretlemek için DllImport özniteliğini kullanmaktır:

// PInvokeTest.cs
using System;
using System.Runtime.InteropServices;

class PlatformInvokeTest
{
    [DllImport("msvcrt.dll")]
    public static extern int puts(string c);
    [DllImport("msvcrt.dll")]
    internal static extern int _flushall();

    public static void Main() 
    {
        puts("Test");
        _flushall();
    }
}

Tersine işlem, yönetilmeyen koda yönetilen bir temsilci geri araması sağlamak olarak tanımlanabilir.

Buna Ters P/Invoke denir ve yönetilen ortamda bir genel temsilci işlevi uygulanarak ve yerel DLL'de uygulanan arayan işlevini içe aktararak elde edilir: 

#include <stdio.h>
#include <string.h>
typedef void (__stdcall *callback)(wchar_t * str);
extern "C" __declspec(dllexport) void __stdcall caller(wchar_t * input, int count, callback call)
{
      for(int i = 0; i < count; i++)
      {
            call(input);
      }
}

Yönetilen kod örneği aşağıdaki gibidir:

using System.Runtime.InteropServices;
public class foo
{
    public delegate void callback(string str);
    public static void callee(string str)
    {
        System.Console.WriteLine("Managed: " +str);
    }
    public static int Main()
    {
        caller("Hello World!", 10, new callback(foo.callee));
        return 0;
    }
    [DllImport("nat.dll",CallingConvention=CallingConvention.StdCall)]
    public static extern void caller(string str, int count, callback call);
}

Bu çözümün ana noktası, bunun yönetilen tarafın etkileşimi başlatmasını gerektirmesidir.

Daha fazla başvuru için lütfen Ters Pinvoke ile Gotchas (yönetilen kod geri aramalarına yönetilmeyen) ve PInvoke-Reverse PInvoke ve stdcall - cdecl’yi okuyun.


2.3. C++ IJW

It Just Works (IJW) olarak anılan C++ birlikte çalışması, Managed Extensions for C++ tarafından sağlanan C++'a özgü bir özelliktir:

#using <mscorlib.dll>
using namespace System;
using namespace System::Runtime::InteropServices;

#include <stdio.h>

int main()
{
   String * pStr = S"Hello World!";
   char* pChars = (char*)Marshal::StringToHGlobalAnsi(pStr).ToPointer(); 
   
   puts(pChars);
   
   Marshal::FreeHGlobal(pChars);
} 

Bu çözüm, yönetilen C++'larını yönetilmeyen uygulamada kullanmak isteyen kişiler için yararlı olabilir. Tam başvuru için lütfen C++ için Yönetilen Uzantılarda Birlikte Çalışabilirlik ve Yönetilen C++'da IJW'yi kullanma’yı okuyun.


2.4. C++/Cli sarmalayıcı sınıfı

C++/Cli sarmalayıcı sınıfı uygulaması, adını, yönetilen sınıfın C++/Cli modunda yazılmış başka bir sınıf tarafından gömmesinden veya sarmalanmasından alır. Sarmalayıcı DLL'sini yazmanın ilk adımı, orijinal yönetilen sınıfın yöntemlerini saran C++ sınıfını yazmaktır.

Sarmalayıcı sınıfı, gcroot<> şablonunu kullanan bir .NET nesnesi tanıtıcısı içermeli ve orijinal sınıftan tüm çağrıları devretmelidir. Sarmalayıcı sınıfı, IL (orta düzey dil) biçiminde derlenir ve bu nedenle yönetilen bir sınıftır.

Sonraki adım, IL sınıfını saran ve tüm çağrıları __declspec(dllexport) ile delege eden #pragma yönetilmeyen yönergeyle yerel C++ sınıfı yazmaktır. direktif. Bu adımlar, yönetilmeyen herhangi bir uygulama tarafından kullanılabilecek yerel C++ DLL oluşturacaktır.

Lütfen örnek uygulamaya bakın. İlk adım, C# kodunu uygulamaktır.

Örnek hesap makinesi sınıfı iki genel yöntem içerir: 

public class Calculator
{
    public int Add(int first, int second)
    {
        return first + second;
    }
    public string FormatAsString(float i)
    {
        return i.ToString();
    }
}

Sonraki adım, hesap makinesi sınıfındaki tüm yöntemleri devredecek bir yönetilen sarmalayıcı yazmaktır:

#pragma once
#pragma managed

#include <vcclr.h>

class ILBridge_CppCliWrapper_Calculator {
private:
    //Aggregating the managed class
    gcroot<CppCliWrapper::Calculator^> __Impl;
public:
    ILBridge_CppCliWrapper_Calculator() {
        __Impl = gcnew CppCliWrapper::Calculator;
    }
    int Add(int first, int second) {
        System::Int32 __Param_first = first;
        System::Int32 __Param_second = second;
        System::Int32 __ReturnVal = __Impl->Add(__Param_first, __Param_second);
        return __ReturnVal;
    }
    wchar_t* FormatAsString(float i) {
        System::Single __Param_i = i;
        System::String __ReturnVal = __Impl->FormatAsString(__Param_i);
        wchar_t* __MarshaledReturnVal = marshal_to<wchar_t*>(__ReturnVal);
        return __MarshaledReturnVal;
    }
};

Lütfen orijinal Calculator sınıfına yapılan referansın gcnew talimatı kullanılarak saklandığını ve gcroot<> şablonu olarak saklandığını unutmayın. Tüm sarılmış yöntemler orijinal olanlarla aynı ada sahip olabilir ve parametreler ve dönüş değerlerinin önünde sırasıyla __Param ve __ReturnVal bulunur.

Şimdi C++/Cli'yi saran ve yerel C++ DLL yöntemlerini dışa aktaran yönetilmeyen C++ sınıfı uygulanmalıdır.

Başlık dosyası, __declspec(dllexport) yönergesine sahip sınıf tanımını içermeli ve işaretçiyi sarmalayıcı sınıfına kaydetmelidir.

#pragma once
#pragma unmanaged

#ifdef THISDLL_EXPORTS
#define THISDLL_API __declspec(dllexport)
#else
#define THISDLL_API __declspec(dllimport)
#endif

//Forward declaration for the bridge
class ILBridge_CppCliWrapper_Calculator;

class THISDLL_API NativeExport_CppCliWrapper_Calculator {
private:
    //Aggregating the bridge
    ILBridge_CppCliWrapper_Calculator* __Impl;
public:
    NativeExport_CppCliWrapper_Calculator();
    ~NativeExport_CppCliWrapper_Calculator();
    int Add(int first, int second);
    wchar_t* FormatAsString(float i);
};

Ve uygulanması:

#pragma managed
#include "ILBridge_CppCliWrapper_Calculator.h"
#pragma unmanaged
#include "NativeExport_CppCliWrapper_Calculator.h"

NativeExport_CppCliWrapper_Calculator::NativeExport_CppCliWrapper_Calculator() {
    __Impl = new ILBridge_CppCliWrapper_Calculator;
}
NativeExport_CppCliWrapper_Calculator::~NativeExport_CppCliWrapper_Calculator()
{
    delete __Impl;
}
int NativeExport_CppCliWrapper_Calculator::Add(int first, int second) {
    int __ReturnVal = __Impl->Add(first, second);
    return __ReturnVal;
}
wchar_t* NativeExport_CppCliWrapper_Calculator::FormatAsString(float i) {
    wchar_t* __ReturnVal = __Impl->FormatAsString(i);
    return __ReturnVal;
}

Bu sarmalayıcı sınıfını yapmak için adım adım bir kılavuz, .NET - C++ Köprüsü’nde açıklanmıştır.

Sarmalayıcılar oluşturmak için tam bir referans Mixing .NET ve yerel kod’da bulunabilir ve Yerel Türlerde tanıtıcıları bildirme hakkında genel bilgi için lütfen şunu okuyun: Nasıl yapılır: Yerel Türlerde Tutacakları açıklayın.


2.5. Yönetilmeyen dışa aktarmalar 

Bu teknik, Expert .NET 2.0 IL Assembler kitabında tam olarak açıklanmıştır ve .NET derleyicisinin ayrıntılarını okumak isteyen herkese tavsiye ederim. Ana fikir, önceden derlenmiş modülü ILDasm kullanarak IL koduna ayrıştırarak, modülün VTable ve VTableFixup tablolarını değiştirerek ve DLL'yi ILAsm kullanarak yeniden derleyerek yönetilen yöntemleri yönetilen bir DLL'nin yönetilmeyen dışa aktarmaları olarak ortaya çıkarmaktır.

Bu görev göz korkutucu görünebilir, ancak bu işlemin sonucu, herhangi bir yönetilmeyen uygulama içinden kullanılabilecek bir DLL üretmek olacaktır. Bunun hala yönetilen bir derleme olduğu unutulmamalıdır, bu nedenle .NET çerçevesi kurulmalıdır.  Bunu yapmak için adım adım bir eğitime, Export Managed Code as Unmanaged adresinden ulaşılabilir.

DLL'yi ILDasm kullanarak ayrıştırdıktan sonra IL dilinde kaynak kodu alıyoruz. Lütfen yönetilmeyen dışa aktarmanın aşağıya yapıştırıldığı basit bir IL kodu örneğini inceleyin:

assembly extern mscorlib {}
..assembly UnmExports {}
..module UnmExports.dll
..corflags 0x00000002
..vtfixup [1] int32 fromunmanaged at VT_01
..data VT_01 = int32(0)
..method public static void foo()
{
..vtentry 1:1
..export [1] as foo
ldstr "Hello from managed world"
call void [mscorlib]System.Console::WriteLine(string)
ret
}

Yönetilmeyen dışa aktarmaların uygulanmasından sorumlu IL kaynak kodu satırları şunlardır:

..vtfixup [1] int32 fromunmanaged at VT_01
..data VT_01 = int32(0)

ve

..vtentry 1:1
..export [1] as foo

Birinci kısım, VTableFixup tablosuna fonksiyon girişi eklemekten ve fonksiyona VT_01 sanal adresini ayarlamaktan sorumludur. İkinci kısım, bu işlev için hangi VTEntry'nin kullanılacağını ve dışa aktarılacak işlev için dışa aktarma takma adını belirtir. 

Bu çözümün artıları, DLL uygulama aşamasında olağan yönetilen C# DLL'den başka herhangi bir ek kod uygulamamız gerekmemesi ve kitapta belirtildiği gibi, bu yöntemin yönetilen dünyayı tüm güvenlik ve sınıf kitaplıklarıyla birlikte yönetilmeyenlere tamamen açmasıdır. müşteriler.

Dezavantajı, .NET derleme diline girmenin tüm insanlar için uygun olmamasıdır. Robert Giesecke'nin yönetilmeyen ihracat şablonunu bulana kadar bunun yerine c++ sarmalayıcı sınıfı yazacağıma ikna olmuştum: http://sites.google.com/site/robertgiesecke/ IL kodunun içine girmeye gerek kalmadan yönetilmeyen dışa aktarımların kullanılmasını sağlar.


3. Yönetilmeyen dışa aktarma C# şablonu

R.Giesecke tarafından yönetilmeyen dışa aktarma C# projeleri için şablon, derlemeden sonra uygun VT düzeltmelerini otomatik olarak ekleyen MSBuild görevi’ni kullanır, bu nedenle IL kodunu değiştirmeye hiç gerek yoktur. Şablon paketinin yalnızca bir zip dosyası olarak indirilmesi ve Visual Studio'nun ProjectTemplates klasörüne kopyalanması gerekir.

Projeyi derledikten sonra ortaya çıkan DLL dosyası MetaTrader tarafından kusursuz bir şekilde içe aktarılabilir, sonraki bölümlerde örnekleri vereceğim.


4. Örnekler

Doğru Marshalling yöntemini kullanarak MetaTrader ve C# arasında değişkenlerin, dizilerin ve yapıların nasıl geçileceğini bulmak oldukça zor bir işti ve burada verilen bilgilerin size çok zaman kazandıracağını düşünüyorum. Tüm örnekler .NET 4.0 ve Visual C# Express 2010 ile Windows Vista'da derlenmiştir. Ayrıca makaleye C# DLL'den işlevleri çağıran MQL5 kodlu örnek DLL ekliyorum.


4.1. Örnek 1. DLL işlevinde iki tamsayı, çift veya kayan değişken ekleme ve sonucu MetaTrader'a döndürme

using System;
using System.Text;
using RGiesecke.DllExport;
using System.Runtime.InteropServices;

namespace Testme
{
    class Test
    {

        [DllExport("Add", CallingConvention = CallingConvention.StdCall)]
        public static int Add(int left, int right)
        {
            return left + right;
        }

        [DllExport("Sub", CallingConvention = CallingConvention.StdCall)]
        public static int Sub(int left, int right)
        {
            return left - right;
        }

        [DllExport("AddDouble", CallingConvention = CallingConvention.StdCall)]
        public static double AddDouble(double left, double right)
        {
            return left + right;
        }

        [DllExport("AddFloat", CallingConvention = CallingConvention.StdCall)]
        public static float AddFloat(float left, float right)
        {
            return left + right;
        }

    }
}

Fark etmiş olabileceğiniz gibi, dışa aktarılan her işlevden önce DllExport yönergesi gelir. İlk parametre dışa aktarılan işlevin diğer adını ve ikinci parametre çağırma kuralını tanımlar, MetaTrader için CallingConvention.StdCall kullanmalıyız.

DLL'den dışa aktarılan işlevleri içe aktaran ve kullanan MQL5 kodu basittir ve yerel C++ ile yazılmış diğer DLL'lerden farklı değildir. İlk başta #import bloğu içinde içe aktarılan işlevlerin bildirilmesi ve DLL'den hangi işlevlerin daha sonra MQL5 kodundan kullanılabileceğinin belirtilmesi gerekir:

//+------------------------------------------------------------------+
//|                                  UnmanagedExportsDLLExample1.mq5 |
//|                                      Copyright 2010, Investeo.pl |
//|                                                http:/Investeo.pl |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010, Investeo.pl"
#property link      "http:/Investeo.pl"
#property version   "1.00"

#import "Testme.dll"
   int Add(int left,int right);
   int Sub(int left,int right);
   float AddFloat(float left,float right);
   double AddDouble(double left,double right);
#import

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   for(int i=0; i<3; i++)
     {
      Print(Add(i,666));
      Print(Sub(666,i));
      Print(AddDouble(666.5,i));
      Print(AddFloat(666.5,-i));
     }
  }
//+------------------------------------------------------------------+

Sonuç 

2011.01.30 21:28:18     UnmanagedExportsDLLExample1 (EURUSD,M1) 664.50000
2011.01.30 21:28:18     UnmanagedExportsDLLExample1 (EURUSD,M1) 668.5
2011.01.30 21:28:18     UnmanagedExportsDLLExample1 (EURUSD,M1) 664
2011.01.30 21:28:18     UnmanagedExportsDLLExample1 (EURUSD,M1) 668
2011.01.30 21:28:18     UnmanagedExportsDLLExample1 (EURUSD,M1) 665.50000
2011.01.30 21:28:18     UnmanagedExportsDLLExample1 (EURUSD,M1) 667.5
2011.01.30 21:28:18     UnmanagedExportsDLLExample1 (EURUSD,M1) 665
2011.01.30 21:28:18     UnmanagedExportsDLLExample1 (EURUSD,M1) 667
2011.01.30 21:28:18     UnmanagedExportsDLLExample1 (EURUSD,M1) 666.50000
2011.01.30 21:28:18     UnmanagedExportsDLLExample1 (EURUSD,M1) 666.5
2011.01.30 21:28:18     UnmanagedExportsDLLExample1 (EURUSD,M1) 666
2011.01.30 21:28:18     UnmanagedExportsDLLExample1 (EURUSD,M1) 666


4.2. Örnek 2. tek boyutlu dizi

        [DllExport("Get1DInt", CallingConvention = CallingConvention.StdCall)]
        public static int Get1DInt([MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)]  int[] tab, int i, int idx)
        {
            return tab[idx];
        }

        [DllExport("Get1DFloat", CallingConvention = CallingConvention.StdCall)]
        public static float Get1DFloat([MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)]  float[] tab, int i, int idx)
        {
            return tab[idx];
        }

        [DllExport("Get1DDouble", CallingConvention = CallingConvention.StdCall)]
        public static double Get1DDouble([MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)]  double[] tab, int i, int idx)
        {
            return tab[idx];
        }

Tek boyutlu bir diziyi sıralamak için, MarshalAs yönergesi ilk parametre olarak UnmanagedType.LPArray'i ve ikinci parametre olarak SizeParamIndex'i geçmelidir. SizeParamIndex, dizi boyutunu içeren parametrenin (0'dan sayarak) hangi parametre olduğunu gösterir.

Yukarıdaki örneklerde i dizi boyutudur ve idx döndürülecek öğenin dizinidir.

Dizi erişimini kullanan MQL5 örnek kodu aşağıdadır:

//+------------------------------------------------------------------+
//|                                  UnmanagedExportsDLLExample2.mq5 |
//|                                      Copyright 2010, Investeo.pl |
//|                                                http:/Investeo.pl |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010, Investeo.pl"
#property link      "http:/Investeo.pl"
#property version   "1.00"

#import "Testme.dll"  
   int Get1DInt(int &t[],int i,int idx);
   float Get1DFloat(float &t[],int i,int idx);
   double Get1DDouble(double &t[],int i,int idx);
#import
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   int tab[3];
   tab[0] = 11;
   tab[1] = 22;
   tab[2] = 33;

   float tfloat[3]={0.5,1.0,1.5};
   double tdouble[3]={0.5,1.0,1.5};

   for(int i=0; i<3; i++)
     {
      Print(tab[i]);
      Print(Get1DInt(tab,3,i));
      Print(Get1DFloat(tfloat,3,i));
      Print(Get1DDouble(tdouble,3,i));

     }
  }
//+------------------------------------------------------------------+

Sonuç

2011.01.30 21:46:25     UnmanagedExportsDLLExample2 (EURUSD,M1) 1.5
2011.01.30 21:46:25     UnmanagedExportsDLLExample2 (EURUSD,M1) 1.50000
2011.01.30 21:46:25     UnmanagedExportsDLLExample2 (EURUSD,M1) 33
2011.01.30 21:46:25     UnmanagedExportsDLLExample2 (EURUSD,M1) 33
2011.01.30 21:46:25     UnmanagedExportsDLLExample2 (EURUSD,M1) 1
2011.01.30 21:46:25     UnmanagedExportsDLLExample2 (EURUSD,M1) 1.00000
2011.01.30 21:46:25     UnmanagedExportsDLLExample2 (EURUSD,M1) 22
2011.01.30 21:46:25     UnmanagedExportsDLLExample2 (EURUSD,M1) 22
2011.01.30 21:46:25     UnmanagedExportsDLLExample2 (EURUSD,M1) 0.5
2011.01.30 21:46:25     UnmanagedExportsDLLExample2 (EURUSD,M1) 0.50000
2011.01.30 21:46:25     UnmanagedExportsDLLExample2 (EURUSD,M1) 11
2011.01.30 21:46:25     UnmanagedExportsDLLExample2 (EURUSD,M1) 11

 

4.3. Örnek 3. Bir boyutlu diziyi doldurma ve onu MetaTrader'a döndürme

        [DllExport("SetFiboArray", CallingConvention = CallingConvention.StdCall)]
        public static int SetFiboArray([MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)]
        int[] tab, int len, [In, Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] int[] res)
        {
            res[0] = 0;
            res[1] = 1;
            
            if (len < 3) return -1;
            for (int i=2; i<len; i++)
                res[i] = res[i-1] + res[i-2];
            return 0;
        }

Bu örnek, giriş parametresi kuralını karşılaştırmak için iki giriş dizisi kullanır. Değiştirilen öğeler Metatrader'a geri döndürülecekse (referansla geçerek) [In, Out,] niteliklerini MarshalAs niteliğinin önüne koymak yeterlidir.

//+------------------------------------------------------------------+
//|                                  UnmanagedExportsDLLExample3.mq5 |
//|                                      Copyright 2011, Investeo.pl |
//|                                                http:/Investeo.pl |
//+------------------------------------------------------------------+
#property copyright "Copyright 2011, Investeo.pl"
#property link      "http:/Investeo.pl"
#property version   "1.00"

#import "Testme.dll"  
    int SetFiboArray(int& t[], int i, int& o[]);
#import


//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
int fibo[10];
static int o[10];

   for (int i=0; i<4; i++)
   { fibo[i]=i; o[i] = i; }
   
   SetFiboArray(fibo, 6, o);
   
   for (int i=0; i<6; i++)
      Print(IntegerToString(fibo[i])+":"+IntegerToString(o[i]));
      
  }
//+------------------------------------------------------------------+

 Sonuç

2011.01.30 22:01:39     UnmanagedExportsDLLExample3 (EURUSD,M1) 0:5
2011.01.30 22:01:39     UnmanagedExportsDLLExample3 (EURUSD,M1) 0:3
2011.01.30 22:01:39     UnmanagedExportsDLLExample3 (EURUSD,M1) 3:2
2011.01.30 22:01:39     UnmanagedExportsDLLExample3 (EURUSD,M1) 2:1
2011.01.30 22:01:39     UnmanagedExportsDLLExample3 (EURUSD,M1) 1:1
2011.01.30 22:01:39     UnmanagedExportsDLLExample3 (EURUSD,M1) 0:0


4.4. Örnek 4. İki boyutlu diziye erişim

        public static int idx(int a, int b) {int cols = 2; return a * cols + b; }
 
        [DllExport("Set2DArray", CallingConvention = CallingConvention.StdCall)]
        public static int Set2DArray([In, Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] int[] tab, int len)
        {
            tab[idx(0, 0)] = 0;
            tab[idx(0, 1)] = 1;
            tab[idx(1, 0)] = 2;
            tab[idx(1, 1)] = 3;
            tab[idx(2, 0)] = 4;
            tab[idx(2, 1)] = 5;
            
            return 0;
        }

İki boyutlu diziyi sıralamak o kadar kolay değil, ancak bir hile kullandım - yani 2B diziyi tek boyutlu olarak geçirmek ve yardımcı idx işleviyle dizi öğelerine erişmek.

//+------------------------------------------------------------------+
//|                                  UnmanagedExportsDLLExample4.mq5 |
//|                                      Copyright 2011, Investeo.pl |
//|                                                http:/Investeo.pl |
//+------------------------------------------------------------------+
#property copyright "Copyright 2011, Investeo.pl"
#property link      "http:/Investeo.pl"
#property version   "1.00"

#import "Testme.dll"
   int Set2DArray(int &t[][2],int i);
#import
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   int t2[3][2];

   Set2DArray(t2,6);

   for(int row=0; row<3; row++)
      for(int col=0; col<2; col++)
         Print("t2["+IntegerToString(row)+"]["+IntegerToString(col)+"]="+IntegerToString(t2[row][col]));

  }
//+------------------------------------------------------------------+

Sonuç

2011.01.30 22:13:01     UnmanagedExportsDLLExample4 (EURUSD,M1) t2[2][1]=5
2011.01.30 22:13:01     UnmanagedExportsDLLExample4 (EURUSD,M1) t2[2][0]=4
2011.01.30 22:13:01     UnmanagedExportsDLLExample4 (EURUSD,M1) t2[1][1]=3
2011.01.30 22:13:01     UnmanagedExportsDLLExample4 (EURUSD,M1) t2[1][0]=2
2011.01.30 22:13:01     UnmanagedExportsDLLExample4 (EURUSD,M1) t2[0][1]=1
2011.01.30 22:13:01     UnmanagedExportsDLLExample4 (EURUSD,M1) t2[0][0]=0


4.5. Örnek 5. Dize içeriğini değiştirme

  	 [DllExport("ReplaceString", CallingConvention = CallingConvention.StdCall)]
        public static int ReplaceString([In, Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder str,
        [MarshalAs(UnmanagedType.LPWStr)]string a, [MarshalAs(UnmanagedType.LPWStr)]string b)
        {
            str.Replace(a, b);

            if (str.ToString().Contains(a)) return 1;
            else  return 0;
        }

Bu örnek kısa ama [In,Out] özniteliklerini kullanarak veya ref veya out anahtar sözcüklerini kullanarak string parametresini başarılı bir şekilde kullanmaya çalıştığım için uygulamam oldukça uzun zaman aldı.

Çözüm, string değişkeni yerine StringBuilder kullanmaktır.

//+------------------------------------------------------------------+
//|                                  UnmanagedExportsDLLExample5.mq5 |
//|                                      Copyright 2011, Investeo.pl |
//|                                                http:/Investeo.pl |
//+------------------------------------------------------------------+
#property copyright "Copyright 2011, Investeo.pl"
#property link      "http:/Investeo.pl"
#property version   "1.00"

#import "Testme.dll"   
   int ReplaceString(string &str,string a,string b);
#import
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   string str="A quick brown fox jumps over the lazy dog";
   string stra = "fox";
   string strb = "cat";


   Print(str);
   Print(ReplaceString(str,stra,strb));
   Print(str);

  }
//+------------------------------------------------------------------+

Sonuç

2011.01.30 22:18:36     UnmanagedExportsDLLExample5 (EURUSD,M1) A quick brown cat jumps over the lazy dog
2011.01.30 22:18:36     UnmanagedExportsDLLExample5 (EURUSD,M1) 0
2011.01.30 22:18:36     UnmanagedExportsDLLExample5 (EURUSD,M1) A quick brown fox jumps over the lazy dog


4.6. Örnek 6. MqlTick yapısını gönderme ve değiştirme

	 private static List<MqlTick> list;

	 [StructLayout(LayoutKind.Sequential, Pack = 1)]
        public struct MqlTick
        {
            public Int64 Time;
            public Double Bid;
            public Double Ask;
            public Double Last;
            public UInt64 Volume;
        }

        [DllExport("AddTick", CallingConvention = CallingConvention.StdCall)]
        public static int AddTick(ref MqlTick tick, ref double bidsum)
        {
            bidsum = 0.0;

            if (list == null) list = new List<MqlTick>();

            tick.Volume = 666;
            list.Add(tick);

            foreach (MqlTick t in list) bidsum += t.Ask;

            return list.Count;
        }

MqlTick yapısı referans olarak iletilir, ref anahtar sözcüğü ile işaretlenir. MqlTick yapısının kendisinden önce [StructLayout (LayoutKind.Sequential, Pack =1)] özniteliği gelmelidir.

Pack parametresi, yapıdaki veri hizalamasını açıklar, lütfen ayrıntılar için StructLayoutAttribute.Pack Field'ı okuyun.  

//+------------------------------------------------------------------+
//|                                  UnmanagedExportsDLLExample6.mq5 |
//|                                      Copyright 2011, Investeo.pl |
//|                                                http:/Investeo.pl |
//+------------------------------------------------------------------+
#property copyright "Copyright 2011, Investeo.pl"
#property link      "http:/Investeo.pl"
#property version   "1.00"

#import "Testme.dll"
   int AddTick(MqlTick &tick, double& bidsum);
#import
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator buffers mapping
   
//---
   return(0);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime& time[],
                const double& open[],
                const double& high[],
                const double& low[],
                const double& close[],
                const long& tick_volume[],
                const long& volume[],
                const int& spread[])
  {
//---
   MqlTick newTick;
   double bidsum;
   
   SymbolInfoTick(Symbol(), newTick);
   
   Print("before = " + IntegerToString(newTick.volume));
   
   Print(AddTick(newTick, bidsum));
   
   Print("after = " + IntegerToString(newTick.volume) + " : " + DoubleToString(bidsum));
   
   
//--- return value of prev_calculated for next call
   return(rates_total);
  }
//+------------------------------------------------------------------+

Sonuç

2011.01.30 23:59:05     TickDLLSend (EURUSD,M1) after = 666 : 8.167199999999999
2011.01.30 23:59:05     TickDLLSend (EURUSD,M1) 6
2011.01.30 23:59:05     TickDLLSend (EURUSD,M1) before = 0
2011.01.30 23:59:05     TickDLLSend (EURUSD,M1) after = 666 : 6.806
2011.01.30 23:59:05     TickDLLSend (EURUSD,M1) 5
2011.01.30 23:59:05     TickDLLSend (EURUSD,M1) before = 0
2011.01.30 23:59:05     TickDLLSend (EURUSD,M1) after = 666 : 5.4448
2011.01.30 23:59:05     TickDLLSend (EURUSD,M1) 4
2011.01.30 23:59:05     TickDLLSend (EURUSD,M1) before = 0
2011.01.30 23:59:05     TickDLLSend (EURUSD,M1) after = 666 : 4.0836
2011.01.30 23:59:05     TickDLLSend (EURUSD,M1) 3
2011.01.30 23:59:05     TickDLLSend (EURUSD,M1) before = 0
2011.01.30 23:59:05     TickDLLSend (EURUSD,M1) after = 666 : 2.7224
2011.01.30 23:59:05     TickDLLSend (EURUSD,M1) 2
2011.01.30 23:59:05     TickDLLSend (EURUSD,M1) before = 0
2011.01.30 23:59:05     TickDLLSend (EURUSD,M1) after = 666 : 1.3612
2011.01.30 23:59:05     TickDLLSend (EURUSD,M1) 1
2011.01.30 23:59:04     TickDLLSend (EURUSD,M1) before = 0

 

Sonuç

Bu yazıda MQL5 kodu ile yönetilen C# kodu arasındaki etkileşimin farklı yöntemlerini sundum.

Ayrıca MQL5 yapılarının C#'a karşı nasıl sıralanacağı ve MQL5 betiklerinde dışa aktarılan DLL işlevlerinin nasıl çağrılacağı konusunda da birkaç örnek sağladım. Sağlanan örneklerin, yönetilen kodda DLL yazma konusunda gelecekteki araştırmalar için bir temel oluşturabileceğine inanıyorum.

Bu makale aynı zamanda MetaTrader'ın C#'da halihazırda uygulanmış olan birçok kütüphaneyi kullanmasına da kapı aralamaktadır. Daha fazla referans için lütfen Referanslar bölümünde bağlantılı makaleleri okuyun.


Test etmek için lütfen aşağıdaki klasörlerdeki dosyaları bulun:

MQL5\Libraries\testme.dll
MQL5\Scripts\unmanagedexportsdllexample1.mq5
MQL5\Scripts\unmanagedexportsdllexample2.mq5
MQL5\Scripts\unmanagedexportsdllexample3.mq5
MQL5\Scripts\unmanagedexportsdllexample4.mq5
MQL5\Scripts\unmanagedexportsdllexample5.mq5
MQL5\Experts\unmanagedexportsdllexample6.mq5


Referanslar

  1. Yerel Uygulamalar Tarafından Tüketilecek Visual Studio 2005 ile .NET DLL'lerini Dışa Aktarma
  2. Unmadged Coded ile Birlikte Çalışma
  3. COM Interop'a Giriş
  4. Component Object Model (COM)
  5. Bir DLL'den __declspec(dllexport) Kullanarak Dışa Aktarma
  6. How to: Yerel Türlerde Tutamaçları Bildirin
  7. C++ kodu Managed'den nasıl çağrılır ve tam tersi (Interop)
  8. Reverse P/Invoke ve istisna 
  9. Visual Studio.NET veya Visual Studio 2005'te yerel Visual C++ kodundan yönetilen DLL nasıl çağrılır
  10. Platform Invoke Tutorial
  11. PInvoke-Reverse PInvoke ve __stdcall - __cdecl
  12. Ters Pinvoke ile Gotchas (yönetilen kod geri aramalarına yönetilmeyen)
  13. .NET ve yerel kodu karıştırma
  14. Yönetilen Kodu Yönetilmeyen Olarak Dışa Aktar
  15. .NET Uygulamalarıyla Klasik COM Birlikte Çalışabilirliğini Anlama
  16. C++ Programlama için Yönetilen Uzantılar
  17. Robert Giesecke'nin sitesi
  18. MSBuild Tasks
  19. Common Language Runtime

MetaQuotes Ltd tarafından İngilizceden çevrilmiştir.
Orijinal makale: https://www.mql5.com/en/articles/249

C++ Şablonlarına Alternatif Olarak Sahte Şablonları Kullanma C++ Şablonlarına Alternatif Olarak Sahte Şablonları Kullanma
Makale, şablonları kullanmadan, ancak şablonlarda iherenet programlama stilini koruyarak programlamanın bir yolunu açıklar. Özel yöntemler kullanarak şablonların uygulanmasını anlatır ve belirtilen şablonlar temelinde bir kod oluşturmak için eklenmiş hazır bir komut dosyasına sahiptir.
Piyasa Fiyat Tahmini için Evrensel Regresyon Modeli Piyasa Fiyat Tahmini için Evrensel Regresyon Modeli
Piyasa fiyatı, sırayla çeşitli ekonomik, politik ve psikolojik faktörlere bağlı olan talep ve arz arasındaki istikrarlı bir dengeden oluşur. Doğadaki farklılıklar ve bu faktörlerin etki nedenleri, tüm bileşenlerin doğrudan ele alınmasını zorlaştırmaktadır. Bu makale, ayrıntılı bir regresyon modeli temelinde piyasa fiyatını tahmin etme girişimini ortaya koymaktadır.
Zigzag ve ATR örneklerini Kullanarak Göstergelerin Sınıflara Uyarlanması Zigzag ve ATR örneklerini Kullanarak Göstergelerin Sınıflara Uyarlanması
Göstergeleri hesaplamanın en iyi yolunun ne olduğu hakkındaki tartışmalar asla bitmez. Gösterge değerlerini nerede hesaplamalıyız - göstergenin kendi içinde mi yoksa bütün mantığı kullanacak bir Expert Advisor’a (Uzman Danışman) mı yüklenmeli? Bu makale özel bir gösterge olan iCustom’ın kaynak kodunu bir Expert Advisor’ın koduna taşımak ile hesaplamaları optimize edilmiş bir komut dizisi oluşturmak ve prev_calculated (daha önce hesaplanmış) değeri örnek almak arasındaki değişkenlerden biri hakkındadır.
Zaman Serilerinin Tahmini için MetaTrader 5 Göstergelerini ENCOG Makine Öğrenimi Çerçevesi ile Kullanma Zaman Serilerinin Tahmini için MetaTrader 5 Göstergelerini ENCOG Makine Öğrenimi Çerçevesi ile Kullanma
Bu makale, MetaTrader 5'i ENCOG - Gelişmiş Sinir Ağı ve Makine Öğrenimi Çerçevesi’ne bağlamak üzerine yazılmıştır. Standart bir teknik göstergeye dayalı temel bir sinir ağı göstergesinin ve bir sinirsel göstergeye dayalı bir Expert Advisor’ın (Uzman Danışman) tanımını ve uygulanmasını içerir. Tüm kaynak kod, derlenmiş ikili dosyalar, DLL'ler ve örnek bir eğitimli ağ makalede ek olarak verilmiştir.