
Guía para escribir un DLL para MQL en Delphi
Introducción
El mecanismo de escritura de un DLL se tratará con un ejemplo de entorno de desarrollo de Delphi 2009. Esta versión se eligió por el hecho de que, en MQL5, todas las líneas se guardan en formato Unicode. En versiones anteriores de Delphi, el módulo SysUtils no tenía la función para trabajar con líneas en formato Unicode.
Si por cualquier motivo está usando una versión anterior (Delphi 2007 o más antigua), deberá trabajar con líneas en formato ANSI, y para intercambiar datos con MetaTrader 5 deberá producir conversiones directas e inversas a Unicode. Para evitar tantas complicaciones, yo recomiendo desarrollar el módulo DLL para MQL5 en un entorno que no sea más antiguo que Delphi 2009. Puede descargarse una versión de prueba de 30 días de Delphi de la página web oficial http://embarcadero.com.
1. Crear el Proyecto
Para crear el proyecto, deberemos ejecutar el DLL Wizard seleccionándolo en el menú: 'File (Archivo)-> New (Nuevo)-> Other ... (Otro ...)-> DLL Wizard ' Tal y como se muestra en la Figura 1.
Figura 1. Crear un proyecto usando el DLL Wizard
Como resultado, crearemos un proyecto DLL vacío, como se muestra en la Figura 2.
Figura 2. Un proyecto DLL vacío
El objetivo de un comentario largo en el título del proyecto es recordarle que hay una conexión correcta y del uso de un gestor de memoria al trabajar con memoria distribuida dinámicamente. Trataremos esto con más detalle en la sección que trata sobre las cadenas de caracteres.
Antes de empezar a llenar el nuevo DLL con funciones, es importante configurar el proyecto.
Abra la ventana de propiedades del proyecto en el menú: 'Project (Proyectos)-> Options ... (Opciones ...)' o apretando la combinación de teclas 'Shift + Ctrl + F11' .
Para simplificar el proceso de depuración es necesario que el archivo DLL se cree directamente en la carpeta '.. \\MQL5\\Libraries' Trade Terminal MetaTrtader5. Para ello, en la pestaña DelphiCompiler (Compilador Delphi) configure el valor de propiedad correspondiente Output directory (Directorio de salida), tal y como se muestra en la Figura 3. Eliminará la necesidad de copiar constantemente el archivo generado por el DLL de la carpeta del proyecto a la carpeta del terminal.
Figura 3. Especifique la carpeta para almacenar el archivo DLL resultante
Durante la asamblea, para evitar la unión de módulos BPL sin cuya presencia en la capeta de sistema de Windows el DLL creado no podría funcionar en el futuro, es importante asegurarse de que la pestaña Packages (Paquetes), la flag Build with runtime packages (construir con paquetes de ejecución) se deja sin activar, tal y como se muestra en la Figura 4.
Figura 4. Exclusión de los módulos BPL de la asamblea
Tras completar la configuración del proyecto, guárdelo en su carpeta de trabajo. El nombre especificado para el proyecto será el nombre futuro del archivo DLL compilado.
2. Añadir los procedimientos y funciones
Consideremos una situación en la que escribiremos los procedimientos y funciones importados en el módulo DLL en un ejemplo de procedimiento sin parámetros. El anuncio y la transferencia de parámetros se tratarán en la siguiente sección.
Un pequeño comentario. Al escribir procedimientos y funciones en el lenguaje Object Pascal language, el programador tiene la posibilidad de usar las funciones incorporadas de la biblioteca Delphi, y también, por supuesto, los innumerables componentes para este entorno. Por ejemplo, para la ejecución de la misma acción, como por ejemplo provocar la muestra de una ventana modal con un mensaje de texto, puede usar una función API - MessageBox , así como un procedimiento de la biblioteca VCL - ShowMessage.
La segunda opción nos lleva a incluir el módulo Dialogs (Diálogos), y nos da la ventaja de trabajar fácilmente con diálogos estándar de Windows. No obstante, el tamaño del archivo DLL resultante aumentará aproximadamente unos 500 KB. Por tanto, si prefiere crear archivos DLL pequeños que no ocupen mucho espacio en el disco duro, no le recomendaría usar los componentes VCL.
Abajo tiene un proyecto de prueba modelo con explicaciones:
library dll_mql5; uses Windows, // necessary for the work of the MessageBox function Dialogs; // necessary for the work of the ShowMessage procedure from the Dialogs module var Buffer: PWideChar; //------------------------------------------------------+ procedure MsgBox(); stdcall; // //to avoid errors, use the stdcall (or cdecl) for the exported functions //------------------------------------------------------+ begin {1} MessageBox(0,'Hello World!','terminal', MB_OK); {2} ShowMessage('Hello World!');// alternative to the MessageBox function end; //----------------------------------------------------------+ exports //----------------------------------------------------------+ {A} MsgBox, {B} MsgBox name 'MessageBox';// renaming of the exported function //----------------------------------------------------------+ procedure DLLEntryPoint(dwReason: DWord); // event handler //----------------------------------------------------------+ begin case dwReason of DLL_PROCESS_ATTACH: // DLL attached to the process; // allocate memory Buffer:=AllocMem(BUFFER_SIZE); DLL_PROCESS_DETACH: // DLL detached from the process; // release memory FreeMem(Buffer); end; end; //----------------------------------------------------------+ begin DllProc := @DLLEntryPoint; //Assign event handler DLLEntryPoint(DLL_PROCESS_ATTACH); end. //----------------------------------------------------------+
Todas las funciones exportadas deben anunciarse con el modificador stdcall o cdecl. Si no se especifica ninguno de estos modificadores, Delphi usará el acuerdo fastcall por defecto, que usa principalmente registros de CPU para pasar parámetros. Sin duda, llevará a un error al trabajar con los parámetros pasados en el momento de llamar a las funciones externas DLL.
La sección "begin end" ("comienzo final") contiene un código de inicialización estándar de un controlador de eventos DLL. Se llamará al procedimiento de rellamada DLLEntryPoint al conectarse y desconectarse del proceso que lo llamó. Estos eventos se pueden usar para una gestión de memoria dinámica correcta, distribuida según nuestras necesidades, tal y como se muestra en el ejemplo.
Llamada para MQL5:
#import "dll_mql5.dll" void MsgBox(void); void MessageBox(void); #import // Call of procedure MsgBox(); // If the names of the function coincide with the names of MQL5 standard library function // use the DLL name when calling the function dll_mql5::MessageBox();
3. Pasar Parámetros a la Función y Valores Devueltos
Antes de tratar el paso de parámetros, analicemos la tabla de correspondencia de datos para MQL5 y Object Pascal.
Tipo de datos para MQL5 |
Tipo de datos para Object Pascal (Delphi) |
Nota |
---|---|---|
char | ShortInt | |
uchar |
Byte | |
short |
SmallInt | |
ushort |
Word |
|
int |
Integer |
|
uint | Cardinal | |
long | Int64 | |
ulong |
UInt64 |
|
float | Single | |
doble | Double |
|
ushort (символ) | WideChar | |
cadena de caracteres | PWideChar | |
bool | Boolean | |
datetime | TDateTime | conversion is required (vea más abajo en esta sección) |
color | TColor |
Tabla 1. La tabla de correspondencia de datos para MQL5 y Object Pascal
Como puede ver en la tabla, Delphi tiene un análogo completo para todos los tipos de datos a excepción de datetime.
Ahora consideremos dos maneras de pasar parámetros: por valor y por referencia. El formato de la declaración de parámetros para ambas versiones se da en la Tabla 2.
Método de transferencia de parámetros |
Anuncio para MQL5 |
Anuncio para Delphi |
Nota |
---|---|---|---|
by value |
int func (int a); | func (a:Integer): Integer; | correct |
int func (int a); |
func (var a: Integer): Integer; |
Error: access violation write to <memory address> | |
by link |
int func (int &a); |
func (var a: Integer): Integer; |
correct, however lines are transmitted without a modifier var! |
int func (int &a); | func (a: Integer): Integer; | error: instead of the value of the variable, contains the address of the memory cell |
Tabla 2. Métodos de transferencia de parámetros
Consideremos los ejemplos de trabajo con transferencia de parámetros y valores devueltos.
3.1 Convertir fecha y hora
Primero, ocupémonos del tipo de fecha y hora que desea convertir, porque el tipo de datetime se corresponde con TDate Time solo en su tamaño, pero no en formato. Para facilitar la transformación, use Int64 como el tipo de datos recibido, en lugar de TDate Time. Debajo están las funciones para la transformación directa e inversa:
uses SysUtils, // used for the constant UnixDateDelta DateUtils; // used for the function IncSecon, DateTimeToUnix //----------------------------------------------------------+ Function MQL5_Time_To_TDateTime(dt: Int64): TDateTime; //----------------------------------------------------------+ begin Result:= IncSecond(UnixDateDelta, dt); end; //----------------------------------------------------------+ Function TDateTime_To_MQL5_Time(dt: TDateTime):Int64; //----------------------------------------------------------+ begin Result:= DateTimeToUnix(dt); end;
3.2 Trabajar con tipos de datos simples
Examinemos cómo transferir tipos de datos simples poniendo como ejemplos los más comúnmente usados: int, double, bool, and datetime.
Llamada para Object Pascal:
//----------------------------------------------------------+ function SetParam(var i: Integer; d: Double; const b: Boolean; var dt: Int64): PWideChar; stdcall; //----------------------------------------------------------+ begin if (b) then d:=0; // the value of the variable d is not changed in the calling program i:= 10; // assign a new value for i dt:= TDateTime_To_MQL5_Time(Now()); // assign the current time for dt Result:= 'value of variables i and dt are changed'; end;
Llamada para MQL5:
#import "dll_mql5.dll" string SetParam(int &i, double d, bool b, datetime &dt); #import // initialization of variables int i = 5; double d = 2.8; bool b = true; datetime dt= D'05.05.2010 08:31:27'; // calling the function s=SetParam(i,d,b,dt); // output of results printf("%s i=%s d=%s b=%s dt=%s",s,IntegerToString(i),DoubleToString(d),b?"true":"false",TimeToString(dt));Resultado:
Los valores de las variables i y dt han cambiado - i = 10 d = 2.80000000 b = true dt = 2009.05 . 05 08 : 42
El valor de d no ha cambiado, puesto que se transfirió por valor. Para evitar que se den cambios en el valor de una variable dentro de una función DLL, se usó un modificador const. en la variable b.
3.3 Trabajar con estructuras y arrays
En varias ocasiones es práctico agrupar los parámetros de diferentes tipos en estructuras, y parámetros de un tipo en arrays. Considere trabajar con todos los parámetros transferidos de la función SetParam del ejemplo anterior, integrándolos en una estructura.
Llamada para Object Pascal:
type StructData = packed record i: Integer; d: Double; b: Boolean; dt: Int64; end; //----------------------------------------------------------+ function SetStruct(var data: StructData): PWideChar; stdcall; //----------------------------------------------------------+ begin if (data.b) then data.d:=0; data.i:= 10; // assign a new value for i data.dt:= TDateTime_To_MQL5_Time(Now()); // assign the current time for dt Result:= 'The values of variables i, d and dt are changed'; end;
Llamada para MQL5:
struct STRUCT_DATA { int i; double d; bool b; datetime dt; }; #import "dll_mql5.dll" string SetStruct(STRUCT_DATA &data); #import STRUCT_DATA data; data.i = 5; data.d = 2.8; data.b = true; data.dt = D'05.05.2010 08:31:27'; s = SetStruct(data); printf("%s i=%s d=%s b=%s dt=%s", s, IntegerToString(data.i),DoubleToString(data.d), data.b?"true":"false",TimeToString(data.dt));Resultado:
Los valores de las variables i, d y dt han cambiado i = 10 d = 0.00000000 b = true dt = 2009.05 . 05 12 : 19
Es necesario señalar una diferencia significativa del resultado del ejemplo anterior. Puesto que la estructura se transfiere a través de una referencia, es imposible proteger los campos seleccionados de ser editados en la función llamada. La tarea de monitorizar la integridad de los datos, en este caso, recae completamente en el programador.
Considere cómo sería trabajar con arrays en un ejemplo de array llenado con una secuencia de números Fibonacci:
Llamada para Object Pascal:
//----------------------------------------------------------+ function SetArray(var arr: IntegerArray; const len: Cardinal): PWideChar; stdcall; //----------------------------------------------------------+ var i:Integer; begin Result:='Fibonacci numbers:'; if (len < 3) then exit; arr[0]:= 0; arr[1]:= 1; for i := 2 to len-1 do arr[i]:= arr[i-1] + arr[i-2]; end;
Llamada para MQL5:
#import "dll_mql5.dll" string SetArray(int &arr[],int len); #import int arr[12]; int len = ArraySize(arr); // passing the array by reference to be filled by data in DLL s = SetArray(arr,len); //output of result for(int i=0; i<len; i++) s = s + " " + IntegerToString(arr[i]); printf(s);Resultado:
Números Fibonacci 0 1 1 2 3 5 8 13 21 34 55 89
3.4 Trabajar con cadenas de caracteres
Volvamos a la gestión de memoria. En DLL , es posible operar su propio gestor de memoria. No obstante, tanto DLL como los programas que lo llaman a menudo están escritos en diferentes lenguajes de programación y sus propios gestores de memoria, en lugar de un sistema de memoria general, se usan en el trabajo. Por ello, la carga de responsabilidad entera sobre la corrección de la operación de memoria en el cruce entre DLL y la aplicación recae en el programador.
Para trabajar con memoria, es importante cumplir con la regla de oro, que sería algo así como: "Aquellos que distribuyen memoria deben ser los que la liberen". En otras palabras, no debería tratar de liberar la memoria en el código del programa mql5, situado en DLL, y viceversa.
Veamos un ejemplo de gestión de memoria en estilo de llamadas de funciones API de Windows. En nuestro caso, el programa mql5 distribuye memoria para el buffer, un puntero para el buffer transferido a DLL como PWideChar , y el DLL solo llena este buffer con el valor deseado, tal y como se muestra en el siguiente ejemplo:
Llamada para Object Pascal:
//----------------------------------------------------------+ procedure SetString(const str:PWideChar) stdcall; //----------------------------------------------------------+ begin StrCat(str,'Current time:'); strCat(str, PWideChar(TimeToStr(Now))); end;
Llamada para MQL5:
#import "dll_mql5.dll" void SetString(string &a); #import // the string must be initialized before the use // the size of the buffer must be initially larger or equal to the string length StringInit(s,255,0); //passing the buffer reference to DLL SetString(s); // output of result printf(s);
Resultado:
Tiempo actual: 11: 48:51
La memoria para el buffer de línea se puede seleccionar en el DLL de varias maneras, tal y como se puede ver en el siguiente ejemplo:
Llamada para Object Pascal:
//----------------------------------------------------------+ function GetStringBuffer():PWideChar; stdcall; //----------------------------------------------------------+ var StrLocal: WideString; begin // working through the dynamically allocated memory buffer StrPCopy(Buffer, WideFormat('Current date and time: %s', [DateTimeToStr(Now)])); // working through the global varialble of WideString type StrGlobal:=WideFormat('Current time: %s', [TimeToStr(Time)]); // working through the local varialble of WideString type StrLocal:= WideFormat('Current data: %s', [DateToStr(Date)]); {A} Result := Buffer; {B} Result := PWideChar(StrGlobal); // it's equal to the following Result := @StrGlobal[1]; {С} Result := 'Return of the line stored in the code section'; // pointer to the memory, that can be released when exit from the function {D} Result := @StrLocal[1]; end;Llamada para MQL5:
#import "dll_mql5.dll" string GetStringBuffer(void); #import printf(GetStringBuffer());
Resultado:
Tiempo actual: 19.05.2010
Lo más significativo es que las cuatro opciones funcionan. En las dos primeras opciones, el trabajo con la línea se hace a través de una memoria distribuida globalmente.
En la opción A, la memoria se distribuye independientemente, y en la opción B, el trabajo con la gestión de memoria se asume por el gestor de memoria.
En la opción C, la línea constante no se guarda en la memoria, sino en el segmento de código, de modo que el gestor de memoria no distribuye memoria dinámica para su almacenamiento. La opción C es un error atrevido en programación, ya que la memoria distribuida para la variable local se puede liberar inmediatamente tras salir de la función.
Y aunque el gestor de memoria no libera esta memoria instantáneamente y no hay tiempo para rellenarla con datos innecesarios, recomiendo excluir la última opción.
3.5 Usar los parámetros por defecto
Hablemos ahora sobre el uso de parámetros opcionales. Son muy interesantes, porque sus valores no deben especificarse al llamar procedimientos y funciones. Mientras tanto, se deben describir estrictamente tras todos los parámetros obligatorios en la declaración de procedimientos y funciones, tal y como se muestra en el siguiente ejemplo:
Llamada para Object Pascal:
//----------------------------------------------------------+ function SetOptional(var a:Integer; b:Integer=0):PWideChar; stdcall; //----------------------------------------------------------+ begin if (b=0) then Result:='Call with default parameters' else Result:='Call without default parameters'; end;Llamada para MQL5:
#import "dll_mql5.dll" string SetOptional(int &a, int b=0); #import i = 1; s = SetOptional(i); // second parameter is optional printf(s);
Resultado:
Llamada con parámetros por defecto
Para facilitar la depuración, el código de los ejemplos anteriores se ha organizado como script, y se puede encontrar en el archivo Testing_DLL.mq5.
4. Posibles errores en la fase de diseño
Error: DLL Loading is not allowed - No se permite la carga de DLL.
Solución: Vaya a la configuración de MetaTrader 5 a través del menú ' Tools-Options' (Herramientas-Opciones) y permita la importación de funciones DLL, tal y como se muestra en la Figura 5.
Figura 5. Permiso para importar funciones DLL
Error: Cannot find 'function name' in 'DLL name' - No se puede encontrar el 'nombre de función' en el 'nombre DLL'.
Solución: Compruebe si la función de rellamada está especificada en la sección de Exportaciones del proyecto DLL. Si lo está, compruebe que coincide exactamente con el nombre de la función en DLL y en el programa MQL5. ¡Incluyendo mayúsculas y minúsculas!
Error: Access violation write to [memory address] - Violación de acceso escrita a [dirección de memoria]
Solución: Debe comprobar la corrección de la descripción de los parámetros transmitidos (vea la tabla 2). Puesto que generalmente este error se asocia con el procesamiento de líneas, es importante seguir las recomendaciones para trabajar con líneas, explicadas en el capítulo 3.4 de este artículo.
5. Ejemplo de código DLL
Como un ejemplo visual del uso de DLL, consideremos los cálculos de parámetros del canal de regresión, consistente en tres líneas. Para verificar la corrección de la construcción del canal, usaremos el objeto incorporado "Canal regression" ("Regresión de Canal"). El cálculo de la línea aproximada para LS (método de menos cuadrados) se toma de la página web http://alglib.sources.ru/, en la que hay una colección de algoritmos de procesamiento de datos. El código de algoritmos se presenta en varios lenguajes de programación, incluyendo Delphi.
Para calcular los coeficientes de a y b por la línea aproximada y = a + b * x, use el procedimiento descrito en el archivo LRLine linreg.pas.
procedure LRLine ( const XY: TReal2DArray; / / Two-dimensional array of real numbers for X and Y coordinates N : AlglibInteger; // number of points var Info : AlglibInteger; // conversion status var A: Double; / / Coefficients of the approximating line var B: Double);
Para calcular los parámetros del canal, use la función CalcLRChannel.
Llamada para Object Pascal:
//----------------------------------------------------------+ function CalcLRChannel(var rates: DoubleArray; const len: Integer; var A, B, max: Double):Integer; stdcall; //----------------------------------------------------------+ var arr: TReal2DArray; info: Integer; value: Double; begin SetLength(arr,len,2); // copy the data to a two-dimensional array for info:= 0 to len - 1 do begin arr[info,0]:= rates[info,0]; arr[info,1]:= rates[info,1]; end; // calculation of linear regression coefficients LRLine(arr, len, info, A, B); // find the maximal deviation from the approximation line found // and determine the width of the channel max:= rates[0,1] - A; for info := 1 to len - 1 do begin value:= Abs(rates[info,1]- (A + B*info)); if (value > max) then max := value; end; Result:=0; end;
Llamada para MQL5:
#import "dll_mql5.dll" int CalcLRChannel(double &rates[][2],int len,double &A,double &B,double &max); #import double arr[][2], //data array for processing in the ALGLIB format a, b, // Coefficients of the approximating line max; // maximum deviation from the approximating line is equal to half the width of the channel int len = period; //number of points for calculation ArrayResize(arr,len); // copying the history to a two-dimensional array int j=0; for(int i=rates_total-1; i>=rates_total-len; i--) { arr[j][0] = j; arr[j][1] = close[i]; j++; } // calculation of channel parameters CalcLRChannel(arr,len,a,b,max);
El código de indicador que usa la función CalcLRChannel para sus cálculos se encuentra en el archivo LR_Channel.mq5 y debajo:
//+------------------------------------------------------------------+ //| LR_Channel.mq5 | //| Copyright 2009, MetaQuotes Software Corp. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "2009, MetaQuotes Software Corp." #property link "https://www.mql5.com" #property version "1.00" #property indicator_chart_window #include <Charts\Chart.mqh> #include <ChartObjects\ChartObjectsChannels.mqh> #import "dll_mql5.dll" int CalcLRChannel(double &rates[][2],int len,double &A,double &B,double &max); #import input int period=75; CChart *chart; CChartObjectChannel *line_up,*line_dn,*line_md; double arr[][2]; //+------------------------------------------------------------------+ int OnInit() //+------------------------------------------------------------------+ { if((chart=new CChart)==NULL) {printf("Chart not created"); return(false);} chart.Attach(); if(chart.ChartId()==0) {printf("Chart not opened");return(false);} if((line_up=new CChartObjectChannel)==NULL) {printf("Channel not created"); return(false);} if((line_dn=new CChartObjectChannel)==NULL) {printf("Channel not created"); return(false);} if((line_md=new CChartObjectChannel)==NULL) {printf("Channel not created"); return(false);} return(0); } //+------------------------------------------------------------------+ 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[]) //+------------------------------------------------------------------+ { double a,b,max; static double save_max; int len=period; ArrayResize(arr,len); // copying of history to a two-dimensional array int j=0; for(int i=rates_total-1; i>=rates_total-len; i--) { arr[j][0] = j; arr[j][1] = close[i]; j++; } // procedure of calculating the channel parameters CalcLRChannel(arr,len,a,b,max); // if the width of the channel has changed if(max!=save_max) { save_max=max; // Delete the channel line_md.Delete(); line_up.Delete(); line_dn.Delete(); // Creating a channel with new coordinates line_md.Create(chart.ChartId(),"LR_Md_Line",0, time[rates_total-1], a, time[rates_total-len], a+b*(len-1) ); line_up.Create(chart.ChartId(),"LR_Up_Line",0, time[rates_total-1], a+max, time[rates_total-len], a+b*(len-1)+max); line_dn.Create(chart.ChartId(),"LR_Dn_Line",0, time[rates_total-1], a-max, time[rates_total-len], a+b*(len-1)-max); // assigning the color of channel lines line_up.Color(RoyalBlue); line_dn.Color(RoyalBlue); line_md.Color(RoyalBlue); // assigning the line width line_up.Width(2); line_dn.Width(2); line_md.Width(2); } return(len); } //+------------------------------------------------------------------+ void OnDeinit(const int reason) //+------------------------------------------------------------------+ { // Deleting the created objects chart.Detach(); delete line_dn; delete line_up; delete line_md; delete chart; }
El resultado del trabajo del indicador es la creación de un canal de regresión azul, tal y como se muestra en la Figura 6. Para verificar la corrección de la construcción del canal, el gráfico muestra un "Regression Canal" del MetaTrader 5 con un arsenal de instrumentos de análisis técnico marcados en rojo.
Como puede ver en la figura, las líneas centrales del canal se juntan. Entretanto, hay una ligera diferencia en la anchura del canal (unos pocos puntos) a causa de los diferentes enfoques en sus cálculos.
Figura 6. Comparación de canales de regresión
Conclusión
Este artículo describe las cualidades de la escritura DLL usando una plataforma de desarrollo de aplicaciones Delphi.
Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/96





- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso
Hello Andrey, his article is excellent, congratulations.
I have a doubt. I have RAD Studio 10 Seattle. When I entry to "Project Options for..." (Shift + Ctrl + F11) I find the next:
I do not find the checkButton "Build with runtime packages". I want to request a recomendation about "What must I do about this?". Thank you for your attention.
I had to change a part of the code source.
however, when I run the source code
I had to change a part of the code source.
however, when I run the source code
Hello Andrey, his article is excellent, congratulations.
I have a doubt. I have RAD Studio 10 Seattle. When I entry to "Project Options for..." (Shift + Ctrl + F11) I find the next:
I do not find the checkButton "Build with runtime packages". I want to request a recomendation about "What must I do about this?". Thank you for your attention.
however: