English Русский 中文 Español Deutsch 日本語 Português Français Italiano Türkçe
DLL 생성이 불필요한 이유

DLL 생성이 불필요한 이유

MetaTrader 5 | 11 10월 2021, 16:04
88 0
---
---


아직도 직접 DLL을 작성하시나요?
그럼 저희가 도와드릴게요!


개요

MQL5 언어로만은 기능이 부족할 때가 있습니다. 그럴때면 추가 도구를 사용해야 하죠. 데이터베이스, 네트워크 소켓 또는 OS 함수를 이용하는 것이 그 예입니다. MQL5 프로그램의 활용을 최대화하기 위해서는 다양한 API를 다룰 줄도 알아야 합니다. 개발자가 필요한 함수를 MQL5에서 실행하지 못하는 경우가 있죠. 다음 사항을 모르기 때문입니다.

  • 복합 데이터 유형(예: 구조)을 API 함수로 전송
  • API 함수가 반환하는 포인터 활용

그렇기 때문에 다른 프로그램 언어를 이용해 중간 DLL을 만들게 되는 겁니다. MQL5로 다양한 형식의 데이터를 만들어 API로 전송할 수는 있지만 수락된 포인터에 대한 데이터 추출 과정에서 발생하는 문제는 해결할 수 없습니다.

이 글에서는 복합 데이터 유형을 다루고 교환하는 간단한 메커니즘에 대해 자세히 알아보겠습니다.


목차

1. 메모리의 중요성

  • 인덱스 찾기
  • 메모리 영역 복사하기

2. API 함수로 구조 전송하기

  • MQL5로 구조 변환하기
  • 소켓 구조 전송 예시

3. API 함수 포인터 활용하기

  • 메모리 맵 파일 예시
  • MySQL 예시

4. API 함수에서 0값으로 끝나는 문자열 읽기



1. 메모리의 중요성

여러분도 아시다시피 복합 데이터 형식을 포함해 모든 변수는 메모리의 특정 주소에 저장됩니다. 주소는 4 바이트 정수형으로 해당 변수의 첫 번째 바이트 주소와 일치합니다.

각 변수를 잘 지정하기만 하면 해당 메모리 영역에 대한 작업을 실행할 수 있습니다. C 언어 라이브러리(msvcrt.dll)에 memcpy 함수가 포함되어 있는데요. 해당 함수는 MQL5와 다양한 API 라이브러리를 연결하여 개발자가 필요로 하는 기능을 찾는 데에 도움을 줍니다.


선조들의 지식 활용하기

Memcpy 함수는 한 버퍼에서 다른 버퍼로 특정 개의 바이트를 복사하고 리시버 버퍼에 포인터를 반환합니다.

void *memcpy(void *dst, const void *src, int cnt);
dst - pointer to the receiver buffer
src - pointer to the source buffer
cnt - number of bytes for copying

다시 말해 크기가 cnt 바이트이며 src에서 시작하는 주소를 갖는 메모리 영역이 dst 주소로 시작하는 메모리 영역으로 복사되는 것입니다.

src에 위치하는 데이터 형은 여러가지입니다. char 형식의 1바이트 변수, double 형식의 8바이트 숫자, 배열, 구조 등이 포함됩니다. 주소와 크기만 알면 데이터를 전송할 수 있는 거죠.


어떻게 작동하나?

다이어그램 1은 여러 데이터 의 크기를 나타냅니다.

MQL5 데이터 형 사이즈


Memcpy 함수는 한 메모리 영역에서 다른 메모리 영역으로 데이터를 복사할 때 필요하죠.
그림 2는 4 바이트 복사를 보여 줍니다.

memcpy 4 바이트 복사 예시

MQL5에서는 다음과 같이 보일 겁니다.

Example 1. Using memcpy
#import "msvcrt.dll"
  int memcpy(int &dst, int &src, int cnt);
#import
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
{
  int dst, src=4, cnt=sizeof(int);
  int adr=memcpy(dst, src, cnt);
  Print("dst value="+string(dst)+"   Address dst="+string(adr));
}

동일한 cnt 규모를 갖는 다양한 데이터 형이 dstsrc가 지정하는 메모리 영역으로 사용될 수 있습니다. 예를 들어, src 포인터가 double형 변수를 참조할 수 있고(cnt=8 바이트) dst 포인터는 동일한 크기의 배열 char[8] 또는 int[2]를 참조할 수 있다는 것입니다.

프로그래머가 원하는 데이터 형을 이용할 수 있습니다. char[8] 배열이든 형식 변수이든 { int a1; int a2; } 구조이든 아무 상관이 없죠.

메모리 데이터는 다양한 형태로 나타나는 일종의 데이터인 것이죠. 예를 들어, 5 바이트 배열을 {int i; char c;} 구조로 전송하거나 혹은 그 반대를 실행할 수 있습니다. 바로 이런 관계 덕분에 API 함수를 직접 활용할 수 있죠.

memcpy 함수를 어떻게 사용할 수 있는지 보겠습니다.


인덱스 찾기

앞서 memcpy 함수가 dst 변수 주소를 반환한다는 걸 배웠습니다.

이런 특성을 이용해 모든 변수의 주소를 얻을 수 있습니다. 복잡한 데이터 형으로 이루어진 배열을 포함해서 말이죠. 동일한 변수를 소수 변수 및 리시버 변수로 지정하면 됩니다. cnt를 이용하면 0 값을 전송할 수 있습니다.

예를 들어, double형 변수와 short 배열의 주소를 획득할 수 있죠.

Example 2. Getting pointers to the variable
#import "msvcrt.dll"
  int memcpy(short &dst[], short &src[], int cnt);
  int memcpy(double &dst,  double &src, int cnt);
#import

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
{
  short src[5];
  //--- getting src array address (i.е., the address of the first element)
  int adr=memcpy(src, src, 0);
  double var;
  //--- getting var variable address
  adr=memcpy(var, var, 0); 
}

수신된 주소는 API 함수로 전송되거나 memcpy 함수의 구조 변수 또는 일반 변수로 이용될 수 있습니다.


배열 복사하기

아시다시피 배열은 꽤 큰 메모리 청크입니다. 해당 청크의 크기는 엘리먼트 형식과 양에 따라 달라지죠. 예를 들어, short 배열의 엘리먼트 개수가 10개라면 메모리의 20 바이트를 차지하게 되죠. short 배열의 크기가 2 바이트니까요.

이 20 바이트는 20개의 char 또는 5개의 int 데이터 형을 가진 배열로 나타날 수도 있습니다. 모두 동일하게 메모리의 20 바이트를 차지하죠.

배열 복사 시 다음을 따르세요.

  • dst 메모리에 필요한 엘리먼트 개수 할당(결과인 cnt 바이트보다 클 것)
  • src에서 복사할 바이트 수를 cnt로 설정
Example 3. Copying the arrays
#import "msvcrt.dll"
  int memcpy(double &dst[],  double &src[], int cnt);
#import

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
{
  double src[5];
  //--- calculating the number of bytes!!!
  int cnt=sizeof(double)*ArraySize(src);
  double dst[]; 
  ArrayResize(dst, 5);
  //--- the array has been copied from src to dst
   memcpy(dst, src, cnt); 
}



2. API 함수로 구조 전송하기

구조 포인터를 API로 전송해야 한다고 가정해 봅시다. MQL5 언어는 구조 전송에 제한을 둡니다. 본문 도입부에서 메모리가 여러 방법으로 나타날 수 있다고 말씀드렸는데요. 필요한 구조를 MQL5가 지원하는 데이터 형으로 복사할 수 있다는 뜻이기도 합니다. 일반적으로 구조에는 배열이 이용됩니다. 따라서 구조에서 배열을 얻은 후 API 함수로 전송해야 하죠.

구조를 이용한 메모리 복사 방법은 문서화 섹션에서 찾아볼 수 있습니다. 구조를 매개 변수로 전송할 수 없기 때문에 memcpy 함수는 사용할 수 없습니다.

그림 3은 서로 다른 유형의 5가지 변수로 구성된 구조를 나타냅니다. char 배열로도 나타나죠.

서로 다른 유형의 5가지 변수로 구성된 구조와 char 배열

Example 4. Copying the structures by means of MQL5
struct str1
{
  double d; // 8 bytes
  long l;   // 8 bytes
  int i[3]; // 3*4=12 bytes
};
struct str2
{
  uchar c[8+8+12]; // str1 structure size
};
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
{
  str1 src; 
  src.d=-1;
  src.l=20;
  //--- filling the structure parameters
  ArrayInitialize(src.i, 0); 
  str2 dst;
  //--- turning the structure into the byte array
  dst=src; 
}

아주 간단한 방법으로 구조를 바이트 배열로 복사하는 법을 배웠습니다.

소켓 생성 함수로 본 예제를 좀 더 유용하게 만들어 볼게요.

int connect(SOCKET s, const struct sockaddr *name, int namelen);

해당 함수의 두 번째 매개 변수가 포인터를 구조로 받아들이기 때문에 문제가 좀 있습니다. 하지만 우리는 해결 방법을 알고 있죠? 한번 해봅시다.

1. MQL5에서 사용할 수 있도록 연결 함수를 작성합니다.

int connect(int s, uchar &name[], int namelen);

2. 문서화된 코드의 구조를 찾습니다.

struct sockaddr_in
{
  short   sin_family;
  u_short sin_port;
  in_addr sin_addr; // additional 8 byte structure
  char sin_zero[8];
};

3. 비슷한 규모의 배열로 구조를 생성합니다.

struct ref_sockaddr_in
{
  uchar c[2+2+8+8];
};

4. sockaddr_in 구조를 채운 후 바이트 배열로 전송하고 connect 변수로 제출합니다.

다음은 위의 단계에 해당하는 코드입니다.

Example 5. Referring of the client socket to the server
#import "Ws2_32.dll"
  ushort htons(ushort hostshort);
  ulong inet_addr(char &cp[]);
  int connect(int s, char &name[], int namelen);
#import
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
{
  //--- connecting the host after the socket initialization

  char ch[];
  StringToCharArray("127.0.0.1", ch);
  //--- preparing the structure
  sockaddr_in addrin;
  addrin.sin_family=AF_INET;
  addrin.sin_addr=inet_addr(ch);
  addrin.sin_port=htons(1000);
  //--- copying the structure to the array
  ref_sockaddr_in ref=addrin; 
  //--- connecting the host
  res=connect(asock, ref.c, sizeof(addrin)); 

  //--- further work with the socket
}

DLL을 따로 만들 필요가 전혀 없죠. 구조를 곧바로 API로 전송할 수 있습니다.


3. API 함수 포인터 활용하기

대부분의 경우 API 함수는 데이터에 대한 포인터를 반환합니다. 구조와 배열이죠. MQL5는 데이터 추출에 적합하지 않으므로 memcpy 함수를 사용하겠습니다.

메모리 맵 파일(MMF) 메모리 배열 활용 예시



MMF로 작업을 할 때는 메모리 배열로 포인터를 반환하는 함수를 이용합니다.

int MapViewOfFile(int hFile, int DesiredAccess, int OffsetHigh, int OffsetLow, int NumOfBytesToMap);

해당 배열의 데이터 읽기memcpy 함수가 요구하는 양의 바이트의 복사를 통해 실행됩니다.
데이터 쓰기memcpy. 함수를 동일한 방법으로 이용해 실행됩니다.

Example 6. Recording and reading data from MMF memory
#import "kernel32.dll"
  int OpenFileMappingW(int dwDesiredAccess, int bInheritHandle,  string lpName);
  int MapViewOfFile(int hFileMappingObject, int dwDesiredAccess, 
                      int dwFileOffsetHigh, int dwFileOffsetLow, int dwNumberOfBytesToMap);
  int UnmapViewOfFile(int lpBaseAddress);
  int CloseHandle(int hObject);
#import "msvcrt.dll"
  int memcpy(uchar &Destination[], int Source, int Length);
  int memcpy(int Destination, int &Source, int Length);
  int memcpy(int Destination, uchar &Source[], int Length);
#import

#define FILE_MAP_ALL_ACCESS   0x000F001F

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
{
  //--- opening the memory object
  int hmem=OpenFileMappingW(FILE_MAP_ALL_ACCESS, 0, "Local\\file");
  //--- getting pointer to the memory
  int view=MapViewOfFile(hmem, FILE_MAP_ALL_ACCESS, 0, 0, 0); 
  //--- reading the first 10 bytes from the memory
  uchar src[10];
  memcpy(src, view, 10);
  int num=10;
  //--- recording the 4 byte int number to the memory beginning
  memcpy(view, num, 4);
  //--- unmapping the view
  UnmapViewOfFile(view); 
  //--- closing the object
  CloseHandle(hmem); 
}

메모리 배열 포인터를 활용하기가 어렵지 않죠? 특히 추가 DLL을 생성하지 않아도 되는 것이 장점입니다.




MySQL 반환 구조 활용 예시

MySQL을 이용할 때 가장 큰 문제 중 하나는 데이터를 가져오는 것이었죠. mysql_fetch_row 함수를 이용하면 문자 배열이 반환됩니다. 각 문자열은 배열 필드가 되죠. 다시 말해 포인터를 포인터로 반환하는 것입니다. 우리가 할 일은 반환된 포인터에서 데이터를 추출하는 것인데요.

필드가 바이너리 데이터를 포함한 다양한 데이터 형식을 갖기 때문에 조금 복잡해집니다. 문자 배열로 나타낼 수 없게 되기 때문이죠. mysql_num_rows, mysql_num_fields 및 mysql_fetch_lengths 함수를 이용해 배열과 필드의 크기를 구합니다.

그림 4는 메모리에 결과를 나타내는 구조입니다.
세 문자열의 시작 주소가 배열로 합쳐지죠. mysql_fetch_row 함수는 배열의 시작 주소(이 경우 94)를 반환합니다.

메모리에 결과를 나타내는 구조

아래는 데이터베이스에서 데이터를 가져올 때 쓰이는 코드의 예시입니다.

Example 7. Getting data from MySQL
#import "libmysql.dll"
  int mysql_real_query(int mysql, uchar &query[], int length);
  int mysql_store_result(int mysql);
  int mysql_field_count(int mysql);
  uint mysql_num_rows(int result);
  int mysql_num_fields(int result);
  int mysql_fetch_lengths(int result);
  int mysql_fetch_row(int result);
#import 
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
{
  //--- ... preliminarily initialized mysql data base
  //--- request for getting all the strings from the table
  string query="SELECT * FROM table"; 
  uchar aquery[]; 
  StringToCharArray(query, aquery);

  //--- sending the request
  err=mysql_real_query(mysql, aquery, StringLen(query)); 
  int result=mysql_store_result(mysql);

  //--- in case it contains the strings
  if (result>0) 
  {
    ulong num_rows=mysql_num_rows(result);
    int num_fields=mysql_num_fields(result);    

    //--- getting the first string pointer
    int r=0, row_ptr=mysql_fetch_row(result);
    while(row_ptr>0)
    {

       //--- getting the pointer to the current string columns lengths
      int len_ptr=mysql_fetch_lengths(result); 
      int lens[]; 
       ArrayResize(lens, num_fields);
      //--- getting the sizes of the string fields
      memcpy(lens, len_ptr, num_fields*sizeof(int));
      //--- getting the data fields   
      int field_ptr[];
      ArrayResize(field_ptr, num_fields);
      ArrayInitialize(field_ptr, 0);

      //--- getting the pointers to the fields
      memcpy(field_ptr, row_ptr, num_fields*sizeof(int)); 
      for (int f=0; f<num_fields; f++)
      {
        ArrayResize(byte, lens[f]);
        ArrayInitialize(byte, 0);
         //--- copy the field to the byte array
        if (field_ptr[f]>0 && lens[f]>0) memcpy(byte, field_ptr[f], lens[f]);
      }
      r++;
      //--- getting the pointer to the pointer to the next string
      row_ptr=mysql_fetch_row(result); 
    }
  }
}



4. API 함수에서 0값으로 끝나는 문자열 읽기

몇몇 API 함수는 문자열에 포인터를 반환하기는 하지만 문자열의 길이를 알려주지 않습니다. 이 경우 0으로 끝나는 문자열을 다루게 되죠. 0값은 문자열의 끝을 판단하는 데에 도움이 됩니다. 크기를 특정할 수 있다는 뜻이니까요.

메모리에 나타난 0값으로 끝나는 문자열

C 라이브러리(msvcrt.dll)에 포인터에서 0값으로 끝나는 문자열을 다른 문자열로 옮기는 함수가 포함되어 있습니다. 문자열의 크기는 해당 함수로 정의됩니다. API 함수가 유니코드 대신 멀티바이트 문자열을 반환하는 경우가 있으므로 바이트 배열을 리시버로 사용하는 것이 좋습니다.

strcpy 함수가 0값으로 끝나는 문자열을 복사합니다.

char *strcpy(char *dst, const char *src);
dst - the pointer to the destination string
src - the pointer to the Null-terminated source string

일종의 특수한 memcpy 함수라고 볼 수 있는데요. 문자열에서 0값이 발견되는 경우 시스템이 복사를 중단합니다. 이같은 포인터를 사용할 때는 항상 위의 함수가 사용됩니다.

MySQL의 API 함수 중에는 포인터를 문자열로 보내는 함수가 여러 개 포함되어 있는데요. strcpy 함수로 데이터를 가져오는 건 쉬운 편이죠.

Example 8. Getting the strings from the pointers
#import "libmysql.dll"
  int mysql_init(int mysql);
  int mysql_real_connect(int mysql, uchar &host[], uchar &user[], uchar &password[], 
                            uchar &DB[], uint port, uchar &socket[], int clientflag);
  int mysql_get_client_info();
  int mysql_get_host_info(int mysql);
  int mysql_get_server_info(int mysql);
  int mysql_character_set_name(int mysql);
  int mysql_stat(int mysql);
#import "msvcrt.dll"
  int strcpy(uchar &dst[], int src);
#import
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
{
  uchar byte[];
  ArrayResize(byte, 300);

  int ptr;
  string st;
  //--- pointer to the string
  ptr=mysql_get_client_info();

  if (ptr>0) strcpy(byte, ptr);
  Print("client_info="+CharArrayToString(byte));
  //--- initializing the base
  int mysql=mysql_init(mysql);

  //--- transferring the strings to the byte arrays
  uchar ahost[]; 
  StringToCharArray("localhost", ahost);
  uchar auser[];
  StringToCharArray("root", auser);
  uchar apwd[];
  StringToCharArray("", apwd);
  uchar adb[];
  StringToCharArray("some_db", adb);
  uchar asocket[];
  StringToCharArray("", asocket);
  //--- connecting the base
  int rez=mysql_real_connect(mysql, ahost, auser, apwd, adb, port, asocket, 0);
  //--- determining the connection and the base status
  ptr=mysql_get_host_info(mysql);
  if (ptr>0) strcpy(byte, ptr);
  Print("mysql_host_info="+CharArrayToString(byte));
  ptr=mysql_get_server_info(mysql);
  if (ptr>0) strcpy(byte, ptr);
  Print("mysql_server_info="+CharArrayToString(byte));
  ptr=mysql_character_set_name(mysql);
  if (ptr>0) strcpy(byte, ptr);
  Print("mysql_character_set_name="+CharArrayToString(byte));
  ptr=mysql_stat(mysql);
  if (ptr>0) strcpy(byte, ptr);
  Print("mysql_stat="+CharArrayToString(byte));
}


결론

다양한 API 함수와 작업할 때 메모리를 활용하는 세 가지 기본 메커니즘(구조 복사, memcpy 함수로 데이터 및 포인터 찾기, strcpy로 문자열 찾기)만 알면 됩니다.

경고 리시버 버퍼에 충분한 데이터가 할당되지 않은 경우 memcpy와 strcpy 함수를 이용하는 것이 위험할 수도 있습니다. 반드시 데이터 수신에 할당된 크기를 확인하세요.


MetaQuotes 소프트웨어 사를 통해 러시아어가 번역됨.
원본 기고글: https://www.mql5.com/ru/articles/364

MQL5.community 회원 활동 기록 MQL5.community 회원 활동 기록
MQL5.com은 여러분 한 분 한 분을 기억하고 있답니다. 어떤 글을 썼는지, 게시글의 조회수는 얼마인지, 코드 베이스의 프로그램 다운로드 수는 몇 회인지까지도 모두 알고 있죠. 게다가 이건 일부일 뿐이랍니다. 개인 활동 기록은 프로필에서 확인 가능하지만 전체 회원의 활동 기록은 어떻게 확인할 수 있을까요? 이번에는 MQL5.community 회원 활동에 대해 알아보겠습니다.
박스-칵스(Box-Cox) 변환 박스-칵스(Box-Cox) 변환
이 글은 박스-칵스 변환에 대한 설명입니다. 박스-칵스 변환 사용 관련 문제를 다루며 임의의 시퀀스와 실제 시세를 적용한 변환 효율성 평가 예제가 포함되어 있습니다.
통계의 기초 통계의 기초
기본적 분석을 이용하는 투자자도 어느 정도의 통계적 연산은 필요로 합니다. 본문에서는 통계의 기초와 기본 구성 요소, 그리고 의사결정 과정에 있어 통계의 중요성을 알아보겠습니다.
최초 구매 고객을 위한 팁 최초 구매 고객을 위한 팁
유명한 말이 있죠? '실패는 성공의 어머니다'라고. 반박하기 힘든 말입니다. 여러분 또는 타인의 과거의 실패를 분석해서 미래의 실패를 최소화할 수 있죠. 구인 서비스를 이용할 때 일어날 수 있는 여러 상황에 대해 알아보겠습니다.