이런 기사를 오랫동안 기다렸습니다. 작성자에게 감사드립니다.
고맙습니다. 저에게 감사하는 사람은 당신이 처음이 아닙니다. 기사의 내용에 대한 모든 소망과 비판적 의견을 기꺼이 듣겠습니다.
앞으로는 MT5 용 Delphi 에서 프로그래밍 주제를 개발하여 사이트에 새로운 정보를 추가하고 싶습니다.
많은 분들에게 유용한 글이라고 생각합니다. 몇 가지 의견이 있습니다:
1. SysUtils와 클래스 단위는 프로젝트에 남겨 두었어야 합니다. 그들의 존재가 프로젝트를 다소 "팽창"시킨다는 사실에도 불구하고 작지만 중요한 기능이 많이 있습니다. 예를 들어, SysUtils가 있으면 예외 처리가 프로젝트에 자동으로 추가됩니다. 아시다시피, dll에서 예외가 처리되지 않으면 mt5로 전달되어 mql5 프로그램 실행이 중지됩니다.
2. DllEntryPoint(일명 DllMain) 내에서 모든 종류의 프로시저를 사용해서는 안 됩니다. Microsoft가 문서에서 언급했듯이 이것은 여러 가지 불쾌한 효과로 가득 차 있습니다. 다음은 이 주제에 대한 작은 기사 목록입니다:
Microsoft의 DLL 생성 모범 사례 - http://www.microsoft.com/whdc/driver/kernel/DLL_bestprac.mspx.
DllMain과 출산 전 생활-http://transl-gunsmoker.blogspot.com/2009/01/dllmain.html
DllMain - 취침 시간 이야기 - http://transl-gunsmoker.blogspot.com/2009/01/dllmain_04.html
DllMain에서 무서운 일을 하지 말아야 하는 몇 가지 이유 - http://transl-gunsmoker.blogspot.com/2009/01/dllmain_05.html
DllMain에서 무서운 일을해서는 안되는 더 많은 이유 : 우발적 인 잠금 - -.
http://transl-gunsmoker.blogspot.com/2009/01/dllmain_7983.html나는 이미 어딘가에서 미완성 된 기사를 발췌하여 쿼드 포럼에서 발췌 한 것 같습니다. 여기서 반복하겠습니다.
시작...끝
DLL 컴파일을 위한 델파이 프로젝트를 만들 때 시작...끝 섹션이 .DPR 프로젝트 파일에 나타납니다. 이 섹션은 DLL이 프로세스 주소 공간에 처음 투영될 때 항상 실행됩니다. 즉, 모든 유닛에 있는 일종의 초기화 섹션으로 간주할 수 있습니다. 이곳에서 현재 프로세스에 대해 처음에 한 번만 수행해야 하는 몇 가지 작업을 수행할 수 있습니다. 다른 프로세스의 주소 공간에 DLL을 로드할 때 이 섹션이 다시 실행됩니다. 그러나 프로세스의 주소 공간은 서로 분리되어 있으므로 한 프로세스에서 초기화해도 다른 프로세스에는 어떤 영향도 미치지 않습니다.
이 섹션에는 몇 가지 제한 사항이 있으므로 주의하고 고려해야 합니다. 이러한 제한 사항은 Windows DLL 로딩 메커니즘의 미묘한 부분과 관련이 있습니다. 이에 대해서는 나중에 자세히 설명하겠습니다.
초기화/최종화
각 유닛 델파이에는 초기화 및 최종화 섹션이라고 하는 특수 섹션이 있습니다. 어떤 유닛이 프로젝트에 연결되는 즉시 이 섹션은 메인 모듈의 특수 로드 및 언로드 메커니즘에 연결됩니다. 그리고 이러한 섹션은 메인 시작...종료 섹션이 작업을 시작하기 전과 작업이 완료된 후에 실행됩니다 . 이는 프로그램 자체에서 초기화 및 최종화를 작성할 필요가 없으므로 매우 편리합니다. 동시에 연결 및 연결 해제가 자동으로 수행되므로 프로젝트에 장치를 연결하거나 연결 해제하기만 하면 됩니다. 이는 기존 EXE 파일뿐만 아니라 DLL에서도 마찬가지입니다. DLL을 메모리에 '로드'할 때 초기화 순서는 다음과 같습니다. 먼저 프로젝트의 용도에 표시된 순서대로 유닛의 모든 초기화 섹션이 실행된 다음 시작...끝 섹션이 실행됩니다. 종료는 역순으로 이루어지지만, DLL 프로젝트 파일에 특별히 설계된 종료 기능이 없다는 점을 제외하면 그 반대입니다. 이것이 일반적으로 DLL 프로젝트를 프로젝트 파일과 사용 단위로 분리하는 것이 권장되는 또 다른 이유입니다.
DllMain
이것은 소위 DLL 진입점입니다. 요점은 Windows가 때때로 프로세스 내에서 발생하는 모든 이벤트를 DLL 자체에 보고해야 한다는 것입니다. 이를 위해 진입점이 있습니다. 즉, 각 DLL에 있고 메시지를 처리할 수 있는 특별히 미리 정의된 함수입니다. 델파이로 작성된 DLL에서는 아직 이 함수를 보지 못했지만, 그럼에도 불구하고 이러한 점이 있습니다. 그 기능의 메커니즘 만 가려져 있지만 언제든지 얻을 수 있습니다. 질문에 대한 답은 - 전혀 필요합니까? - 라는 질문에 대한 답은 생각만큼 명확하지 않습니다.
먼저 Windows가 DLL에 전달하려는 것이 무엇인지 이해하려고 노력해 봅시다. 운영 체제가 DLL에 오는 총 4 개의 메시지가 있습니다. 첫 번째 메시지인 DLL_PROCESS_ATTACH 알림은 시스템이 호출 프로세스의 주소 공간에 DLL을 첨부할 때마다 전송됩니다. MQL4의경우 이는 암시적 로딩입니다. 이 DLL이 이미 다른 프로세스의 주소 공간에 로드되어 있어도 메시지는 계속 전송됩니다. 그리고 실제로 Windows가 특정 DLL을 메모리에 한 번만 로드하는 것은 중요하지 않으며, 이 DLL을 주소 공간에 로드하려는 모든 프로세스는 이 DLL의 반영만 받습니다. 이것은 동일한 코드이지만 DLL이 가질 수 있는 데이터는 각 프로세스마다 고유합니다(공통 데이터가 존재할 수 있음). 두 번째인 DLL_PROCESS_DETACH 알림은 호출 프로세스의 주소 공간에서 DLL을 분리하라는 알림입니다. 실제로 이 메시지는 Windows가 DLL 언로드를 시작하기 전에 수신됩니다. 실제로 다른 프로세스에서 DLL을 사용 중일 경우 언로드가 이루어지지 않으며, Windows는 해당 프로세스의 주소 공간에 DLL이 존재했다는 사실을 "잊어버립니다". DLL을 로드한 프로세스가 프로세스 내에서 스레드를 생성하거나 삭제할 때 DLL_THREAD_ATTACH 및DLL_THREAD_DETACH라는두 가지 알림이 추가로 수신됩니다. 스레드 알림을 수신하는 순서와 관련된 몇 가지 미묘한 문제가 있지만 여기서는 고려하지 않겠습니다.
이제 델파이로 작성된 DLL이 배열되는 방식과 일반적으로 프로그래머에게 숨겨져 있는 내용에 대해 알아보겠습니다. Windows가 "DLL을 호출 프로세스의 주소 공간에 투영"하거나 간단히 말해서 DLL을 메모리에로드 한 후이 시점에서 진입 점에있는 함수에 대한 호출이 있고이 함수에 알림 DLL_PROCESS_ATTACH를 전달합니다. 델파이로 작성된 DLL에서 이 진입점에는 단위 초기화를 시작하는 등 다양한 작업을 수행하는 특수 코드가 포함되어 있습니다. 이 코드는 초기화 및 DLL의 첫 실행이 완료되었음을 기억하고 메인 프로젝트 파일의 시작...끝을 실행합니다. 따라서 이 초기 로딩 코드는 한 번만 실행되고, 진입점에 대한 다른 모든 Windows 호출은 후속 알림을 처리하는 다른 함수로 이루어지며, 실제로는 유닛을 마무리하는 DLL_PROCESS_DETACH 메시지를 제외하고는 이를 무시합니다. 이것이 델파이로 작성된 DLL을 로드하는 메커니즘의 일반적인 모습입니다. 대부분의 경우 MQL4에서 DLL을 작성하고 사용하는 것으로 충분합니다.
그래도 C에서와 똑같은 DllMain이 필요한 경우 이를 구성하는 것은 어렵지 않습니다. 다음과 같이 하면 됩니다. DLL을 처음 로드할 때, 무엇보다도 시스템 모듈(프로그램이나 DLL에 항상 존재)은 전역 절차 변수 DllProc을 자동으로 생성하며, 이 변수는 nil로 초기화됩니다. 즉, 존재하는 DllMain 알림 외에 추가 처리가 필요하지 않습니다. 함수의 주소가 이 변수에 할당되는 즉시 Windows의 모든 DLL에 대한 알림이 이 함수로 전달됩니다. 이것이 바로 엔트리 포인트에 필요한 것입니다. 그러나 DLL_PROCESS_DETACH 알림은 이전과 마찬가지로 DLL 종료 함수에 의해 계속 추적되어 마무리할 수 있습니다.
프로시저DllEntryPoint(Reason: DWORD);
begin
케이스 이유
DLL_PROCESS_ATTACH : ;//'연결 프로세스'
DLL_THREAD_ATTACH : ;//' 스레드 연결 중' thread'
DLL_THREAD_DETACH : ;//' 스레드연결 해제'. stream'
DLL_PROCESS_DETACH : ;//' 스레드연결을 끊는 중입니다. 프로세스'
end;
end;
begin
if not Assigned(DllProc) then start
DllProc :=@DllEntryPoint;
DllEntryPoint (DLL_PROCESS_ATTACH);
end;
end.
스레드 알림에 관심이 없는 경우에는 이 모든 것이 불필요합니다. 프로세스 연결 및 연결 해제 이벤트가 자동으로 추적되므로 초기화/종료 섹션을 단위로 구성하기만 하면 됩니다.
DllMain의 기만과 배신
이제 프로그래밍 문헌에서 의외로 거의 다루지 않는 문제를 다룰 차례입니다. 이 주제는 델파이나 C뿐만 아니라 DLL을 생성할 수 있는 모든 프로그래밍 언어와 관련이 있습니다. 이것은 Windows DLL 로더의 속성입니다. Windows 환경에서 프로그래밍에 대한 진지하고 널리 퍼진 번역된 문헌에서 단 한 명의 저자만이 이에 대한 언급을 찾아냈고, 그것도 가장 모호한 용어로 언급했습니다. 이 저자는 J. Richter이며, 그의 멋진 책은 일반적으로 32 비트 Windows가 널리 보급되지 않았던 2001 년에 출판 되었기 때문에 용서받을 수 있습니다.
MS 자체가 DllMain 문제의 존재를 숨기지 않았고 심지어 "DllMain을 사용하는 가장 좋은 방법"과 같은 특별 문서를 게시 한 것도 흥미 롭습니다. 여기에서 그는 DllMain에서 수행 할 수있는 작업과 권장되지 않는 작업을 설명했습니다. 그리고 권장되지 않는 것은 보기 어렵고 일관성 없는 오류로 이어진다는 점을 지적했습니다. 이 문서를 읽고 싶으신 분은 여기에서 보실 수 있습니다. 이 주제에 대한 몇 가지 경고성 보고서의 번역본 중 더 많이 알려진 요약본이 여기에 요약되어 있습니다.
문제의 본질은 매우 간단합니다. 요점은 특히 DLL을로드 할 때 DllMain이 특별한 장소라는 것입니다. 복잡하고 눈에 띄는 일을해서는 안되는 곳입니다. 예를 들어 CreateProcess 또는 LoadLibrary 다른 DLL은 권장하지 않습니다 . CreateThread 또는 CoInitialise COM도 권장하지 않습니다 . 등등.
가장 간단한 작업만 수행하면 됩니다. 그렇지 않으면 아무것도 보장되지 않습니다. 따라서 DllMain에 불필요한 것을 넣지 마십시오. 그렇지 않으면 DLL을 사용하는 응용 프로그램이 충돌하는 것을보고 놀라게 될 것입니다. 안전을 위해 적절한 순간에 기본 애플리케이션에서 호출되는 초기화 및 최종화의 특수 내보내기 함수를 만드는 것이 좋습니다. 이렇게 하면 적어도 DllMain의 문제를 피하는 데 도움이 됩니다.
ExitProc, ExitCode,MainInstance,HInstance....
컴파일 시 항상 DLL에 연결되는 System 모듈에는 사용할 수 있는 몇 가지 유용한 전역 변수가 있습니다.
ExitCode- 로딩 시 0이 아닌 다른 숫자를 넣을 수 있는 변수로, 이 변수를 넣으면 DLL 로딩이 중지됩니다.
ExitProc, - 종료 시 실행될 함수의 주소를 저장할 수 있는 절차적 변수입니다. 이 변수는 먼 과거의 유물이며 DLL에서는 작동하지 않으며, 또한 델파이 개발자는 가능한 문제 때문에 DLL에서 이 변수를 사용하지 않는 것이 좋습니다.
HInstance, - 로드 후 DLL 자체의 설명자가 저장되는 변수입니다. 매우 유용할 수 있습니다.
MainInstance, - DLL을 주소 공간에 로드한 애플리케이션의 설명자.
IsMultiThread, - DLL 컴파일에서 스레드 작업이 감지되면 자동으로 True로 설정되는 변수입니다. 이 변수 값에 따라 DLL메모리 관리자 가 멀티스레드 모드로 전환됩니다. 원칙적으로 DLL에서 스레드가 명시적으로 사용되지 않더라도 메모리 관리자가 멀티스레드 모드로 전환하도록 강제할 수 있습니다. IsMultiThread:=True; 당연히 멀티스레드 모드는 스레드가 서로 동기화되기 때문에 싱글스레드 모드보다 느립니다.
MainThreadID, - 애플리케이션의 메인 스레드에 대한 설명자.
등등. 일반적으로 시스템 모듈은 C에서 CRT와 거의 동일한 기능을 수행합니다. 메모리 관리 기능을 포함합니다. 프로젝트 설정에서 링커 옵션인 맵 파일 - 상세를 켜면 컴파일된 DLL에 있는 모든 함수 및 변수 목록(내보낸 함수뿐만 아니라 모든 변수 포함)을 확인할 수 있습니다.
메모리 관리
다음으로 종종 문제를 일으키는 다소 심각한 문제는 DLL의 메모리 관리입니다. 보다 정확하게는 메모리 관리 자체는 문제를 일으키지 않지만 DLL이 응용 프로그램 자체의 메모리 관리자가 할당 한 메모리로 적극적으로 작업하려고하면 일반적으로 문제가 시작되는 곳입니다.
문제는 일반적으로 애플리케이션은 MemoryManager가 내장된 상태로 컴파일된다는 것입니다. 컴파일된 DLL에는 자체 MemoryManager도 포함되어 있습니다. 이는 특히 다른 프로그래밍 환경에서 생성된 애플리케이션과 DLL에 해당됩니다. 우리의 경우처럼 터미널은 MSVC에 있고 DLL은 Delphi에 있습니다. 이들은 구조상 서로 다른 관리자이지만, 동시에 프로세스의 공통 주소 공간 내에서 각각 자체 메모리를 관리하는 물리적으로 다른 관리자임이 분명합니다. 원칙적으로 그들은 서로 간섭하지 않고 서로의 메모리를 빼앗지 않으며 서로 병렬로 존재하며 일반적으로 경쟁자의 존재에 대해 아무것도 "알지 못"합니다. 이는 두 관리자가 동일한 소스인 Windows 메모리 관리자에서 메모리에 액세스하기 때문에 가능합니다.
문제는 DLL 함수와 애플리케이션이 서로 다른 메모리 관리자가 배포한 메모리 섹션을 관리하려고 할 때 시작됩니다. 따라서 프로그래머들 사이에는 "메모리는 코드 모듈의 경계를 넘지 않아야 한다"는 경험 법칙이 있습니다.
좋은 규칙이지만 정확하지는 않습니다. 애플리케이션이 사용하는 DLL에서 동일한 MemoryManager를 사용하는 것이 더 정확할 것입니다. 사실 저는 MT4메모리 관리자를 Delphi메모리 관리자 FastMM에연결하는 아이디어가 더 마음에 들지만, 이는 전혀 실현 가능한 아이디어가 아닙니다. 어쨌든 메모리 관리는 한 가지여야 합니다.
Delphi 에서는 기본 메모리 관리자를 일부 요구 사항을 충족하는 다른 메모리 관리자로 대체할 수 있습니다. 따라서 DLL과 애플리케이션에 단일 메모리 관리자를 만들 수 있으며, 이 메모리 관리자는 MT4 메모리 관리자가 될 것입니다.
새로운 기고글 Delphi로 MQL5용 DLL 짜는 법 가 게재되었습니다:
이 문서는 Delphi 환경에서 인기좋은 언어 ObjectPascal을 이용해 DLL 모듈을 짜는 법에 대해서 알아볼 것입니다. 본 문서에서 제공하는 자료는 주로 외부 DLL 모듈을 연결하여 MQL5의 내장 프로그래밍 언어의 경계를 허문 초보자 프로그래머를 대상으로 합니다.
6번 그림과 같이 파란색 회귀 채널을 생성하는 것이 인디케이터 작업의 결과입니다. 채널 구성의 정확성을 확인하기 위해 차트에는 MetaTrader 5 기술 분석 도구 중 "회귀 캐널"이 빨갛게 표시됩니다.
위 그림에서 볼 수 있듯이, 채널의 중앙 라인이 합쳐집니다. 그와중에 채널의 두께엔 서로 다른 계산 접근으로 인한 미세한 차이가 생깁니다 (몇포인트).
6번 그림. 회귀채널 비교
작성자: Andrey Voytenko