English Русский 中文 Español Deutsch 日本語 Português Français Italiano Türkçe
관리되지 않는 내보내기를 사용하여 MQL5에 C# 코드 노출

관리되지 않는 내보내기를 사용하여 MQL5에 C# 코드 노출

MetaTrader 5통합 | 4 8월 2021, 17:08
146 0
investeo
investeo

소개

MQL5에서 관리 모드 C# DLL을 사용할 수 있는 간단한 솔루션을 찾기 위해 오랫동안 찾고 있었습니다. 많은 글을 읽은 후 많은 시간을 절약해 주는 훌륭한 솔루션을 발견하게 된 저는 관리 DLL용 C++ 래퍼를 구현할 준비가 되어있었습니다.

이 솔루션은 관리되지 않는 응용 프로그램에서 사용할 관리되는 C# 코드를 내보내는 간단한 예를 제공했습니다. 이 글에서는 관리 모드 DLL에 대한 배경 지식을 제공하고 MetaTrader에서 직접 액세스할 수 없는 이유를 설명하고 MetaTrader에서 관리 코드를 사용할 수 있는 솔루션을 소개합니다.

관리되지 않는 내보내기 템플릿의 간단한 사용 예를 제공하고 내가 발견한 모든 것을 계속할 것입니다. 이것은 MetaTrader 5에서 C# DLL 코드를 사용하려는 모든 사람에게 건전한 배경을 제공해야 합니다.


1. 관리 코드와 비관리 코드

대부분의 독자는 관리 코드와 비관리 코드의 차이점을 인식하지 못할 수 있으므로 몇 문장으로 설명하겠습니다. 기본적으로 MetaTrader는 MQL 언어를 사용하여 거래 규칙, 지표, Expert Advisor 및 스크립트를 구현합니다. 그러나 이미 다른 언어로 구현된 라이브러리를 사용하고 런타임 중에 동적으로 연결할 수 있습니다. 이러한 라이브러리를 DLL 또는 동적 링크 라이브러리라고도 합니다.

라이브러리는 실제로 특정 작업을 수행하기 위해 여러 외부 프로그램에서 호출할 수 있는 컴파일된 소스 코드를 포함하는 이진 파일입니다. 예를 들어 신경망 라이브러리는 신경망 훈련 및 테스트를 위한 기능을 내보낼 수 있고, 파생 라이브러리는 다양한 파생 상품의 계산을 내보낼 수 있으며, 행렬 라이브러리는 행렬에 대한 연산을 내보낼 수 있습니다. MetaTrader용 DLL은 지표 또는 Expert Advisor 구현의 일부를 숨길 수 있게 되면서 점점 더 대중화되었습니다. 라이브러리를 사용하는 주된 이유는 반복해서 구현할 필요 없이 기존 코드를 재사용하기 위해서입니다.

.NET이 존재하기 전에는 Visual Basic, Delphi, VC++로 컴파일된 모든 DLL(COM, Win32 또는 일반 C++)을 운영 체제에서 직접 실행할 수 있었습니다. 이 코드를 비관리 코드 또는 네이티브 코드라고 합니다. 그런 다음 .NET이 등장하여 매우 다른 환경을 제공했습니다.

코드는 .NET Common Language Runtime - CLR에 의해 제어(또는 관리)됩니다. CLR 컴파일러는 여러 다른 언어, 메타데이터 및 공통 중간 언어(CIL)로 작성될 수 있는 소스 코드에서 생성해야 합니다.

CIL은 기계 독립적인 상위 수준 언어이며 메타데이터는 공통 유형 사양 - CTS에 따라 CIL에서 설명하는 개체 유형을 완전히 설명합니다. CLR은 유형에 대한 모든 것을 알고 있기 때문에 관리되는 실행 환경을 제공할 수 있습니다. 관리는 쓰레기 모으기(자동 메모리 관리 및 개체 삭제 및 보안 제공)로 생각할 수 있으며, 관리자 권한으로 에일리언 코드 실행을 유발할 수 있는 모국어의 일반적인 실수로부터 보호하거나 단순히 메모리를 재정의합니다.

CIL 코드는 절대 직접 실행되지 않습니다. 항상 JIT(Just-In-Time) 컴파일을 통해 또는 CIL을 어셈블리로 사전 컴파일하여 기본 기계어로 변환됩니다. 이것을 처음 읽는 사람에게는 관리 모드 코드의 개념이 혼동될 수 있으므로 아래 CLR에 일반 흐름을 붙여넣습니다.

 

 

그림 1. 공용 언어 런타임 


2. MQL5에서 관리 코드 액세스의 가능한 구현

다음 단락에서는 비관리 코드에서 관리 코드에 액세스할 수 있는 방법을 설명합니다.

내가 사용하는 방법보다 다른 방법을 사용하는 것을 선호하는 사람이 있을 수 있으므로 모두 언급할 가치가 있다고 생각합니다. 사용 중인 메소드는 COM Interop, Reverse P/Invoke, C++ IJW, C++/Cli 래퍼 클래스 및 관리되지 않는 내보내기입니다.


2.1. COM 상호 운용성 

구성 요소 개체 모델(COM)은 90년대 초 Microsoft에서 도입한 바이너리 인터페이스 표준입니다. 이 기술의 핵심 아이디어는 내부 구현을 모른 채 다른 COM 개체에서 다른 프로그래밍 언어로 만든 개체를 사용할 수 있도록 하는 것입니다. 이러한 요구 사항은 구현과 완전히 분리된 엄격하고 잘 정의된 COM 인터페이스를 구현하도록 강제합니다.

사실 COM은 .NET 기술로 대체되었으며 Microsoft는 COM 대신 .NET을 사용하도록 밀어붙였습니다. 이전 코드와의 역호환성을 제공하기 위해 .NET은 COM과 양방향으로 협력할 수 있습니다. 즉, .NET은 COM 메소드를 호출할 수 있고 COM 개체는 .NET 관리 코드를 사용할 수 있습니다.

이 기능을 COM 상호 운용성 또는 COM 상호 운용성이라고 합니다. COM interop API는 관리형 System.Runtime.InteropServices 네임스페이스에 있습니다.

 

그림 2. COM 상호 운용성 모델

그림 2. COM 상호 운용성 모델 


다음 COM interop 코드는 단일 함수 raw_factorial을 호출합니다.

CoInitialize() CoCreateInstance() 및 CoUninitialize() 함수와 인터페이스 호출 함수에 유의하십시오.

#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 Interop에 대한 자세한 내용은 COM Interop 소개에 있는 자세한 문서와 msdn 블로그에서 찾은 사용 예를 참조하세요: How to call C++ code from Managed, and vice versa (Interop).


2.2. 역 P/Invoke

P/Invoke라고 하는 Platform Invoke를 사용하면 서명이 다시 선언되는 한 .NET에서 관리되지 않는 모든 언어로 모든 함수를 호출할 수 있습니다. 이것은 .NET에서 기본 함수 포인터를 실행하여 달성됩니다. 사용법은 플랫폼 호출 튜토리얼에 잘 설명되어 있습니다.

기본 사용법은 DllImport 특성을 사용하여 가져온 함수를 표시하는 것입니다.

// 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();
    }
}

역 작업은 관리되지 않는 코드에 관리되는 대리자 콜백을 제공하는 것으로 설명할 수 있습니다.

이것을 Reverse P/Invoke라고 하며 관리되는 환경에서 공개 대리자 함수를 구현하고 네이티브 DLL에 구현된 호출자 함수를 가져옴으로써 달성됩니다. 

#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);
      }
}

관리 코드 예제는 다음과 같습니다.

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);
}

이 솔루션의 요점은 관리 측에서 상호 작용을 시작해야 한다는 것입니다.

추가 참조는 Reverse Pinvoke가 포함된 문제(관리 코드 콜백으로 관리되지 않음)PInvoke-Reverse PInvoke 및 stdcall - cdecl을 참조하세요.


2.3. C++ IJW

It Just Works(IJW)라고 하는 C++ 상호 운용성은 Managed Extensions for C++에서 제공하는 C++ 전용 기능입니다.

#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);
} 

이 솔루션은 관리되지 않는 애플리케이션에서 관리되는 C++를 사용하려는 사람들에게 유용할 수 있습니다. 전체 참조는 Managed Extensions for C++의 상호 운용성Managed C++에서 IJW 사용을 참조하세요.


2.4. C++/Cli 래퍼 클래스

C++/Cli 래퍼 클래스 구현은 C++/Cli 모드로 작성된 다른 클래스에 의해 관리 클래스를 포함하거나 래핑하여 이름을 사용합니다. 래퍼 DLL을 작성하는 첫 번째 단계는 원래 관리되는 클래스의 메소드를 래핑하는 C++ 클래스를 작성하는 것입니다.

래퍼 클래스는 gcroot<> 템플릿을 사용하는 .NET 개체에 대한 핸들을 포함해야 하며 원래 클래스의 모든 호출을 위임해야 합니다. 래퍼 클래스는 IL(중간 언어) 형식으로 컴파일되므로 관리되는 클래스입니다.

다음 단계는 IL 클래스를 래핑하고 모든 호출을 __declspec(dllexport) 지시어로 위임하는 #pragma unmanaged 지시문을 사용하여 네이티브 C++ 클래스를 작성하는 것입니다. 이러한 단계는 관리되지 않는 모든 응용 프로그램에서 사용할 수 있는 네이티브 C++ DLL을 만듭니다.

구현 예를 참조하십시오. 첫 번째 단계는 C# 코드를 구현하는 것입니다.

예시 계산기 클래스에는 다음 두 가지 공개 메소드가 포함되어 있습니다. 

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

다음 단계는 계산기 클래스의 모든 메소드를 위임하는 관리되는 래퍼를 작성하는 것입니다.

#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;
    }
};

원래 Calculator 클래스에 대한 참조는 gcnew 명령어를 사용하여 저장되고 gcroot<> 템플릿으로 저장됩니다. 모든 래핑된 메소드는 원래 메소드와 동일한 이름을 가질 수 있으며 매개 변수와 반환 값 앞에는 각각 __Param 및 __ReturnVal이 옵니다.

이제 C++/Cli를 래핑하고 네이티브 C++ DLL 메소드를 내보내는 관리되지 않는 C++ 클래스를 구현해야 합니다.

헤더 파일은 __declspec(dllexport) 지시문이 있는 클래스 정의를 포함하고 래퍼 클래스에 대한 포인터를 저장해야 합니다.

#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);
};

그리고 그 구현:

#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;
}

이 래퍼 클래스를 만드는 단계별 가이드는 .NET to C++ Bridge에 설명되어 있습니다.

래퍼 생성에 대한 전체 참조는 Mixing .NET 및 네이티브 코드에서 볼 수 있으며 네이티브 유형의 핸들 선언에 대한 일반적인 정보는 방법: 네이티브 유형의 핸들 선언을 참조하세요.


2.5. 관리되지 않는 내보내기 

이 기술은 Expert .NET 2.0 IL Assembler 책에 자세히 설명되어 있으며 .NET 컴파일러에 대한 세부 정보를 읽고 싶은 사람에게 추천합니다. 주요 아이디어는 ILDasm을 사용하여 이미 컴파일된 모듈을 IL 코드로 디컴파일하고 모듈의 VTable 및 VTableFixup 테이블을 변경하고 ILAsm을 사용하여 DLL을 다시 컴파일하여 관리되는 메소드를 관리되는 DLL의 관리되지 않는 내보내기로 노출하는 것입니다.

이 작업은 어려운 작업처럼 보일 수 있지만 이 작업의 결과는 관리되지 않는 애플리케이션 내에서 사용할 수 있는 DLL을 생성하는 것입니다. 여전히 관리되는 어셈블리이므로 .NET 프레임워크를 설치해야 한다는 점을 기억해야 합니다. 이를 위한 단계별 자습서는 관리 코드를 비관리로 내보내기에서 사용할 수 있습니다.

ILDasm을 사용하여 DLL을 디컴파일하면 IL 언어로 된 소스 코드를 얻습니다. 관리되지 않는 내보내기를 아래에 붙여넣은 IL 코드의 간단한 예를 확인하세요.

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
}

관리되지 않는 내보내기 구현을 담당하는 IL 소스 코드 라인은 다음과 같습니다.

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

그리고

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

첫 번째 부분은 VTableFixup 테이블에 함수 항목을 추가하고 함수에 VT_01 가상 주소를 설정하는 역할을 합니다. 두 번째 부분은 이 함수에 사용할 VTEntry와 내보낼 함수의 내보내기 별칭을 지정합니다. 

이 솔루션의 장점은 DLL 구현 단계에서 일반적인 관리되는 C# DLL과 별도로 추가 코드를 구현할 필요가 없으며 책에서 언급한 바와 같이 이 방법을 사용하면 관리되지 않는 클라이언트에 모든 보안 및 클래스 라이브러리가 포함되어 있는 잘 관리된 세계가 완전히 열리게 됩니다.

단점은 .NET 어셈블리 언어를 사용하는 것이 모든 사람에게 적합하지 않다는 것입니다. Robert Giesecke의 관리되지 않는 내보내기 템플릿을 찾을 때까지 대신 C++ 래퍼 클래스를 작성하겠다고 확신했습니다. http://sites.google.com/site/robertgiesecke/ IL 코드에 들어가야 합니다.


3. 관리되지 않는 내보내기 C# 템플릿

R.Giesecke의 비관리형 내보내기 C# 프로젝트용 템플릿은 빌드 후 적절한 VT 수정을 자동으로 추가하는 MSBuild 작업을 사용하므로 IL 코드를 전혀 변경할 필요가 없습니다. 템플릿 패키지는 zip 파일로 다운로드하고 Visual Studio의 ProjectTemplates 폴더에 복사하기만 하면 됩니다.

프로젝트를 컴파일한 후 결과 DLL 파일을 MetaTrader에서 완벽하게 가져올 수 있으며 다음 섹션에서 예제를 제공합니다.


4. 예

올바른 Marshalling 방법을 사용하여 MetaTrader와 C# 간에 변수, 배열 및 구조체를 전달하는 방법을 파악하는 것은 상당히 어려운 작업이었고 여기에 제공된 정보가 많은 시간을 절약할 수 있다고 생각합니다. 모든 예제는 .NET 4.0 및 Visual C# Express 2010이 포함된 Windows Vista에서 컴파일되었습니다. 또한 C# DLL에서 글로 함수를 호출하는 MQL5 코드가 있는 샘플 DLL을 첨부합니다.


4.1. 예 1. DLL 함수에 두 개의 정수, 이중 또는 부동 변수를 추가하고 그 결과를 MetaTrader에 반환

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;
        }

    }
}

알다시피 내보낸 모든 함수 앞에는 DllExport 지시문이 옵니다. 첫 번째 매개변수는 내보낸 함수의 별칭과 두 번째 매개변수 호출 규칙을 설명합니다. 즉, MetaTrader의 경우 CallingConvention.StdCall을 사용해야 합니다.

DLL에서 내보낸 함수를 가져오고 사용하는 MQL5 코드는 간단하며 네이티브 C++로 작성된 다른 DLL과 다르지 않습니다. 처음에는 #import 블록 내에서 가져온 함수를 선언하고 나중에 MQL5 코드에서 사용할 수 있는 DLL의 함수를 표시해야 합니다.

//+------------------------------------------------------------------+
//|                                  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));
     }
  }
//+------------------------------------------------------------------+

결과 

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. 예 2. 1차원 배열 액세스

        [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];
        }

1차원 배열을 마샬링하려면 MarshalAs 지시문이 UnmanagedType.LPArray를 첫 번째 매개변수로 전달하고 SizeParamIndex를 두 번째 매개변수로 전달해야 합니다. SizeParamIndex는 배열 크기를 포함하는 매개변수(0부터 계산)가 어떤 매개변수인지 나타냅니다.

위의 예에서 i는 배열 크기이고 idx는 반환할 요소의 인덱스입니다.

어레이 액세스를 사용하는 MQL5 예제 코드는 다음과 같습니다.

//+------------------------------------------------------------------+
//|                                  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));

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

결과

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. 예시 3. 1차원 배열을 채우고 MetaTrader로 반환

        [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;
        }

이 예시에서는 두 개의 입력 배열을 사용하여 입력 매개변수 규칙을 비교합니다. 변경된 요소가 MetaTrader에 다시 반환되어야 하는 경우(참조로 전달) MarshalAs 속성 앞에 [In, Out,] 속성을 넣으면 충분합니다.

//+------------------------------------------------------------------+
//|                                  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]));
      
  }
//+------------------------------------------------------------------+

 결과

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. 예 4. 2차원 배열에 대한 액세스

        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;
        }

2차원 배열은 마샬링하기가 그렇게 간단하지 않지만 트릭을 사용했습니다. 즉, 2차원 배열을 1차원으로 전달하고 보조 idx 함수로 배열 요소에 액세스하는 것입니다.

//+------------------------------------------------------------------+
//|                                  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]));

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

결과

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. 예 5. 문자열 내용 바꾸기

  	 [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;
        }

이 예제는 짧지만 [In,Out] 속성을 사용하거나 ref 또는 out 키워드를 사용하여 성공하지 못한 문자열 매개변수를 사용하려고 했기 때문에 구현하는 데 꽤 오랜 시간이 걸렸습니다.

해결책은 문자열 변수 대신 StringBuilder를 사용하는 것입니다.

//+------------------------------------------------------------------+
//|                                  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);

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

결과

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. 예 6. MqlTick 구조체 전송 및 변경

	 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 구조체는 ref 키워드로 표시된 참조로 전달됩니다. MqlTick 구조체 자체는 [StructLayout (LayoutKind.Sequential, Pack =1)] 속성이 앞에 와야 합니다.

Pack 매개변수는 구조체의 데이터 정렬을 설명합니다. 자세한 내용은 StructLayoutAttribute.Pack 필드를 참조하세요.  

//+------------------------------------------------------------------+
//|                                  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);
  }
//+------------------------------------------------------------------+

결과

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

 

결론

이 글에서는 MQL5 코드와 관리되는 C# 코드 간의 다양한 상호 작용 방법을 제시했습니다.

또한 C#에 대해 MQL5 구조를 마샬링하는 방법과 MQL5 스크립트에서 내보낸 DLL 함수를 호출하는 방법에 대한 몇 가지 예를 제공했습니다. 제공된 예제가 관리 코드에서 DLL을 작성하는 향후 연구의 기초가 될 수 있다고 생각합니다.

이 글은 또한 MetaTrader가 C#에서 이미 구현된 많은 라이브러리를 사용할 수 있는 기회를 제공합니다. 추가 참조를 위해 참조 섹션에 링크된 글을 읽으십시오.


테스트하려면 다음 폴더에서 파일을 찾으십시오.

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


참고문헌

  1. 네이티브 애플리케이션에서 사용할 Visual Studio 2005로 .NET DLL 내보내기
  2. 언매지드 코딩과의 상호 운용
  3. COM Interop 소개
  4. 구성 요소 개체 모델(COM)
  5. __declspec(dllexport)을 사용하여 DLL에서 내보내기
  6. 방법: 네이티브 형식에서 핸들 선언
  7. Managed에서 C++ 코드를 호출하는 방법과 그 반대로 호출하는 방법(Interop)
  8. 역 P/호출 및 예외 
  9. Visual Studio.NET 또는 Visual Studio 2005의 네이티브 Visual C++ 코드에서 관리되는 DLL을 호출하는 방법
  10. 플랫폼 호출 튜토리얼
  11. Reverse Pinvoke가 있는 문제(관리 코드 콜백에 ​​대한 관리되지 않음)
  12. Reverse Pinvoke가 있는 문제(관리 코드 콜백으로 관리되지 않음)
  13. .NET과 네이티브 코드 혼합
  14. .NET 응용 프로그램과의 클래식 COM 상호 운용성 이해
  15. .NET 응용 프로그램과의 클래식 COM 상호 운용성 이해
  16. C++ 프로그래밍을 위한 관리 확장
  17. Robert Giesecke의 사이트
  18. MSBuild 작업:
  19. 공용 언어 런타임

MetaQuotes 소프트웨어 사를 통해 영어가 번역됨
원본 기고글: https://www.mql5.com/en/articles/249

시장 가격 예측을 위한 범용 회귀 모델 시장 가격 예측을 위한 범용 회귀 모델
시장 가격은 다양한 경제적, 정치적, 심리적 요인에 따라 달라지는 수요와 공급 간의 안정적인 균형에서 형성됩니다. 이러한 요인들의 영향 요인과 성격의 차이로 인해 모든 구성 요소를 직접적으로 고려하기가 어렵습니다. 이 글은 정교한 회귀 모델을 기반으로 시장 가격을 예측하려는 시도를 설명합니다.
랜덤 워크와 추세 표시기 랜덤 워크와 추세 표시기
랜덤 워크는 실제 시장 데이터와 매우 유사해 보이지만 몇 가지 중요한 기능을 갖고 있습니다. 이 글에서는 동전 던지기 게임을 사용하여 시뮬레이션한 랜덤 워크의 속성을 고려할 것입니다. 데이터의 속성을 연구하기 위해 경향성 지표가 개발되었습니다.
시계열 예측을 위한 ENCOG 머신 러닝 프레임워크와 함께 MetaTrader 5 지표 사용 시계열 예측을 위한 ENCOG 머신 러닝 프레임워크와 함께 MetaTrader 5 지표 사용
이 글에서는 MetaTrader 5를 ENCOG(Advanced Neural Network and Machine Learning Framework)에 연결하는 방법을 설명합니다. 여기에는 표준 기술 지표를 기반으로 하는 간단한 신경망 지표와 신경 지표를 기반으로 하는 Expert Advisor에 대한 설명과 구현이 포함되어 있습니다. 모든 소스 코드, 컴파일된 바이너리, DLL 및 훈련된 예시적인 네트워크가 글에 첨부되어 있습니다.
지그재그 및 ATR의 예에 의한 지표의 클래스 구현 지그재그 및 ATR의 예에 의한 지표의 클래스 구현
지표를 계산하는 최적의 방법에 대한 논쟁은 끝이 없습니다. 지표 자체에서 지표 값을 계산하거나 이를 사용하는 Expert Advisor에 전체 논리를 포함시켜야 하는 곳은 어디입니까? 이 글에서는 계산을 최적화하고 prev_calculated 값을 모델링하여 Expert Advisor 또는 스크립트의 코드에서 사용자 지정 지표 iCustom의 소스 코드를 바로 이동하는 변형 중 하나를 설명합니다.