Tema 1. Como reconoce Far Manager una DLL como plugin?

1.1. Primero, entendamos la diferencia entre una DLL normal y un plugin

Imagina que desarrollamos esta funcion en C++:

int suma(int a, int b) {
 return a + b;
}

Podemos compilarlo como parte de una DLL (calculadora.dll).

Pero aqui aparece una pregunta importante:

Como sabe otro programa que nuestra DLL contiene una funcion llamada sumar y como puede utilizarla?

Para eso existen las funciones exportadas.

En Windows, una DLL puede publicar ciertas funciones mediante una tabla denominada Export Table, que forma parte del formato PE (Portable Executable).

Por ejemplo:

extern "C" __declspec(dllexport)
int sumar(int a, int b) {
 return a + b;
}

Veamos que significa cada parte:

ElementoSignificado
extern "C"Evita que C++ modifique el nombre exportado mediante name mangling.
__declspec(dllexport)Indica al compilador que exporte la funcion desde la DLL.
intEspecifica que la funcion devuelve un entero.
sumar(int a, int b)Define su nombre y los parametros que recibe.
Ahora otro programa puede localizar esa funcion mediante mecanismos de Windows como LoadLibraryW y GetProcAddress.

Pero hay algo importante: poder llamar a una funcion de la DLL no significa que Windows sepa automaticamente que es lo que hace esa funcion.

El programa que carga la DLL necesita conocer de antemano su nombre, sus parametros y como debe utilizarla.

Aqui entra en juego la API de plugins de Far Manager.

1.2. El contrato entre Far Manager y nuestro plugin

Far Manager establece una interfaz que los plugins deben respetar.

Por ejemplo, puede buscar una funcion con este nombre exacto:

GetGlobalInfoW

Esa funcion proporciona informacion global del plugin.

O puede invocar:

SetStartupInfoW

Para entregar al plugin informacion y servicios que necesitara durante su funcionamiento.

Estas no son funciones inventadas por el desarrollador de TrueTemplate. Son puntos de entrada definidos por la API de Far Manager 3.

Esto explica por que una DLL ordinaria no se convierte automaticamente en un plugin: necesita implementar la interfaz esperada.

1.3. Veamos el codigo real de TrueTemplate

Esta funcion esta en: source/true-tpl.cpp:

void WINAPI GetGlobalInfoW(struct GlobalInfo *Info)
{
 Info->StructSize = sizeof(struct PluginInfo);
 Info->MinFarVersion=MAKEFARVERSION(3,0,0,4040,VS_RELEASE);
 Info->Version=MAKEFARVERSION(3,0,1,11,VS_RC);
 Info->Guid=MainGuid;
 Info->Title=L"True Template";
 Info->Description=L"True Template Editor Plugin";
 Info->Author=L"Ivan Shynkarenka";
}

void WINAPI indica que la funcion no devuelve ningun valor y utiliza una convencion de llamada definida por Windows.

GlobalInfo *Info es un puntero a una estructura. Far Manager proporciona esa estructura y el plugin escribe en ellas sus datos.

Info->Title, Info->Description y Info-Author son campos que permiten identificar el plugin.

MainGuid es un identificador global del plugin. Ayuda a distinguirlo de otros plugins.

1.4 La parte mas importante: los callbacks

Consideremos esta funcion de TrueTemplate:

void WINAPI SetStartupInfoW(
    const struct PluginStartupInfo *Info)
{
    ::Info = *Info;
    // Inicializacion adicional...
}

Far Manager entrega al plugin una estructura con informacion y punteros a funciones que este puede utilizar para interactuar con el programa.

Por ejemplo, TrueTemplate utiliza:

Info->EditorControl(...)

Esta interfaz permite consultar o manipular determinados aspectos del editor de Far Manager.

Por lo tanto, la comunicacion funciona en ambas direcciones:

  • Far Manager -> Plugin: llama a las funciones exportadas.
  • Plugin -> Far Manager: utiliza los servicios y callbacks proporcionados por la API.

Este mecanismo es fundamentala. Lo encontraremos tambien en sistemas extensibles y en muchas APIs nativas.

Comprobemos que quedo claro

SI creamos miPlugin.dll con una funcion sumar(), por que Far Manager no lo reconocera automaticamente como un Plugin Funcional?

  • Porque debemos implementar la interfaz que espera Far Manager, con sus funciones exportadas y estructuras.

Recordemos que para que Far Manager pueda reconocerla y utilizarla, debe implementar la interfaz que espera su API: funciones con nombres especificos, estructuras compatibles y convenciones de llamada correctas.

Por ejemplo:

extern "C" __declspec(dllexport)
int sumar(int a, int b)
{
    return a + b;
}

Esta DLL exporta sumar, pero Far Manager no sabe que hacer con ella.

En cambio, cuando implementamos una funcion como GetGlobalInfoW siguiendo el SDK, Far Manager puede obtener la informacion de nuestro plugin.

Tema 2: Entendiendo GlobalInfo, estructuras y punteros

Antes de crear HelloFar, debemos comprender tres conceptos que aparecen constantemente en los plugins nativos:

  1. Estructuras (struct).
  2. Punteros (*) y acceso a miembros (->).
  3. Funciones que reciben estructuras para devolver informacion.

2.1 Que es una estructura en C++?

Imaginemos que queremos representar la informacion de un plugin.

Podriamos tener tres variables independientes:

int version = 1;
string nombre = "HelloFar";
string autor = "Developer";

Pero es mas practico agrupar esos datos en una sola estructura.

struct Plugin {
 int version;
 const wchar_t* nombre;
 const wchar_t* autor;
};

Ahora podemos crear una instancia:

Plugin miPlugin;
 
miPlugin.version = 1;
miPlugin.nombre = L"HelloFar";
miPlugin.autor = L"Developer";

FIjemonos en el operador .. Lo utilizamos para acceder a los campos cuando tenemos directamente el objeto.

El prefijo L indica que estamos usando literales de caracteres anchos, adecuados para las interfaces Unicode de Windows que emplea Far Manager.

2.2 Que cambia cuando utilizamos un puntero?

Observemos esta funcion:

void configurar(Plugin* info) {
 info->version = 2;
}

Aqui no estamos recibiendo un objeto Plugin por valor.

Estamos recibiendo un puntero a un objeto Plugin.

Un puntero almacena una direccion de memoria.

En C++:

miPlugin.version = 2; // objeto directamente
 
info->version = 2; // puntero al objeto

Estas expresiones acceden al mismo tipo de campo, pero mediante mecanismos distintos.

De hecho:

info->version

es equivalente a:

(*info).version

Esto es importante porque la API de Far Manager utilliza punteros a estructuras constantemente.

2.3 Ahora relacionemoslo con el codigo real de TrueTemplate

Recordemos esta funcion que encontramos en true-tpl.cpp:

void WINAPI GetGlobalInfoW(struct GlobalInfo *Info)
{
 Info->StructSize = sizeof(struct PluginInfo);
 Info->MinFarVersion=MAKEFARVERSION(3,0,0,4040,VS_RELEASE);
 Info->Version=MAKEFARVERSION(3,0,1,11,VS_RC);
 Info->Guid=MainGuid;
 Info->Title=L"True Template";
 Info->Description=L"True Template Editor Plugin";
 Info->Author=L"Ivan Shynkarenka";
}

Esta funcion es un ejemplo del mecanismo que acabamos de estudiar.

Que ocurre paso a paso?

  1. Far Manager prepara una estructura GlobalInfo.
  2. Entrega un puntero a esa estructura cuando invoca GetGlobalIndoW.
  3. TrueTemplate rellena los campos mediante Info->.
  4. Cuando la funcion termina, Far Manager puede consultar la informacion que recibio.

El plugin no necesita devolver una estructura completa mediante return, porque esta modificando la estructura proporcionada a traves del puntero.

Que significa sizeof?

sizeof(GlobalInfo)

Devuelve el size (tamanio) en bytes de esa estructura, segun como la representa el compilador.

El campo StructSize ayuda a la API a comprobar que version o extension de una estructura puede utilizarse.

Con todo esto podemos…

Conectar cuatro conceptos:

DLL -> Funcione exportada -> Punteroa a estructura -> Intercambio de informacion con Far Manager.

Ahora estamos preparados para empezar a interpretar las declaraciones del SDK, en lugar de simplemente copiarlas.

2.4 Una ultima comprobacion

Considerando este codigo:

struct Datos {
 int valor;
};
 
void modificar(Datos* info) {
 info->valor = 100;
}
 
int main() {
 Datos d{10};
 modificar(&d);
}

Que valor tendra d.valor despues de llamar a modificar(&d)?

Si tu respuesta es 10, es incorrecto, porque:

La clave esta en la instruccion:

modificar(&d);

El operador & obtiene la direccion de memoria de d. No estamos enviando una copia del objeto, sino un puntero a el.

Si hubieramos escrito:

void modificar(Datos info) {
 indo.valor = 100;
}

Entonces si modificariamos una copia y d.valor seguiria siendo 10.

Esta diferencia es exactamente la que necesitamos comprender para trabajar con las estructuras de Far Manager. El programa proporciona estructuras al plugin y este puede rellenarlas mediante punteros.

Tema 3: GetPluginInfoW y SetStartupInfoW

Ya sabemos que GetGlobalInfoW proporciona los datos de identidad del plugin.

Ahora conoceremos otras dos funciones:

  • GetPluginInfoW: Permite que el plugin informe a Far Manager como debe integrarse en sus menus y que caracteristicas expone.
  • SetStartupInfoW: Recibe acceso a funciones y servicios que proporciona Far Manager.

La diferencia esencial es que una describe la integracion del plugin y la otra le proporciona herramientas para interactuar con Far Manager.

3.1 Analizando GetPluginInfoW en TrueTemplate

Esta es la funcion completa:

void WINAPI GetPluginInfoW(struct PluginInfo *Info)
{
 if (!IsOldFar)
 {
  static const wchar_t *PluginMenuStrings[1];
  Info->StructSize = sizeof(struct PluginInfo);
  Info->Flags = PF_EDITOR | PF_VIEWER | PF_FULLCMDLINE;
  Info->DiskMenu.Count = 0;
  PluginMenuStrings[0] = GetMsg (MTitle);
  Info->PluginMenu.Guids=&MainGuid;
  Info->PluginMenu.Count = 1;
  Info->PluginMenu.Strings = PluginMenuStrings;
 
  static const wchar_t *PluginCfgStrings[1];
  PluginCfgStrings[0] = GetMsg (MTitle);
  Info->PluginConfig.Guids=&MainGuid;
  Info->PluginConfig.Count = 1;
  Info->PluginConfig.Strings = PluginCfgStrings;
 
  static const wchar_t *PluginPrefix = GetMsg (MPrefix);
  Info->CommandPrefix = PluginPrefix;
 }
}
 

Este fragmento proviene directamente del archivo source/true-tpl.cpp del repositorio.

void WINAPI GetPluginInfoW(struct PluginInfo *Info)
{
    if (!IsOldFar)
    {
        static const wchar_t *PluginMenuStrings[1];
 
        Info->StructSize = sizeof(struct PluginInfo);
        Info->Flags = PF_EDITOR | PF_VIEWER | PF_FULLCMDLINE;
 
        Info->DiskMenu.Count = 0;
 
        PluginMenuStrings[0] = GetMsg(MTitle);
 
        Info->PluginMenu.Guids = &MainGuid;
        Info->PluginMenu.Count = 1;
        Info->PluginMenu.Strings = PluginMenuStrings;
    }
}

Se omitieron partes de la funcion para concentrarnos en lo mas importante.

A. La estructura PluginInfo

void WINAPI GetPluginInfo(struct PluginInfo *Info)

Oservamos que seguismo utilizando el mismo patron:

  1. Far Manager llama a la funcion.
  2. Proporciona un puntero Info.
  3. El plugin escribe informacion dentro de la estructura.

Pero ahora no estamos trabajando con GlobalInfo, sino con PluginInfo.

GlobalInfo describe la identidad del plugin, PluginInfo describe aspectos de su integracion con Far Manager.

B. Las banderas (Flags)

Info->Flags = PF_EDITOR | PF_VIEWER | PF_FULLCMDLINE;

El operador | realiza un OR a nivel de bits.

Aqui permite combinar diferentes banderas de configuracion.

No estamos asignando tres valores diferentes sucesivamente, estamos construyendo un unico valor que representa una combinacion de opciones.

C. El menu del plugin

static const wchar_t *PluginMenuStrings[1];

Esta linea crea un arreglo con capacidad para un puntero a una cadena de caracteres anchos.

Luego:

PluginMenuStrings[0] = GetMsg(MTitle);

Obtiene el texto que representa la entrada del menu.

Y finalmente:

Info->PluginMenu.Guids = &MainGuid;
Info->PluginMenu.Count = 1;
Info->PluginMenu.Strings = PluginMenuStrings;

Esto indica que TrueTemplate proporciona una entrada de menu asociada a su GUID y al texto configurado.

El uso de static es importante: el arreglo sigue existiendo despues de que termine la funcion. Far Manager necesita poder acceder a la informacion proporcionada.

3.2 Analizando SetStartupInfoW

Este es el codigo completo:

void WINAPI SetStartupInfoW(const struct PluginStartupInfo *Info)
{
 ::Info = *Info;
 IsOldFar = true;
 if (Info->StructSize >= sizeof(PluginStartupInfo))
 {
  ::Info=*Info;
  FSF=*Info->FSF;
  ::Info.FSF=&FSF;
  IsOldFar = false;
  ::Info.RegExpControl(nullptr, RECTL_CREATE, 0, &RegExpHandle);
 
  PluginSettings settings(MainGuid, ::Info.SettingsControl);
  autocompile = settings.Get(0, L"AutoCompile", true);
  autoformat = settings.Get(0, L"AutoFormat", true);
  pluginStop = settings.Get(0, L"Disable", false);
  scrollStop = settings.Get(0, L"UseScrollLock", true);
  ignoreposn = settings.Get(0, L"IgnorePosition", true);
  outputmenu = settings.Get(0, L"OutputMenu", true);
  filterring = settings.Get(0, L"OutputFilter", false);
  confFilename = String(settings.Get(0, L"ConfigFilename", DefaultConfFilename));
  if (confFilename.empty())
   confFilename = DefaultConfFilename;
  defExpandFKey = String(settings.Get(0, L"Key", L"Space"));
 
  InitMacro();
 }
}

Esta es una parte simplificada del codigo de TrueTemplate:

void WINAPI SetStartupInfoW(
    const struct PluginStartupInfo *Info)
{
    ::Info = *Info;
 
    FSF = *Info->FSF;
    ::Info.FSF = &FSF;
}

Aqui conocemos una operacion nueva:

::Info = *Info;

El simbolo *, aplicado a un puntero, permite acceder al objeto apuntado.

Por tanto, esta instruccion copia la estructura recibidad a una variable llamada Info ubicada en el ambito global.

El operado :: especifica ese ambito global, evitando confundir ambas variables que tienen el mismo nombre.

Por que necesita TrueTemplate conservar esa informacion?

Funcion completa:

intptr_t WINAPI ProcessEditorEventW(const struct ProcessEditorEventInfo *Info)
{
 wchar_t    filename[NM];
 EditorInfoEx ei;
 TEInfo    *te;
 
 initEList ();
 switch (Info->Event)
 {
 case EE_READ:
  ::Info.EditorControl (-1, ECTL_GETINFO, 0, &ei);
  ::Info.EditorControl (ei.EditorID, ECTL_GETFILENAME, NM, filename);
  te = (*eList) [eListInsert (ei.EditorID, filename)];
  if (te->newFile)
  {
   te->newFile = false;
   intptr_t lngid = te->lang;
   if (lngid != -1)
   {
    const TLang *lng = langColl[lngid];
    if (lng)
     InsertTemplate (ei.EditorID, lng);
   }
  }
  return (0);
 
 case EE_CLOSE:
  eList->removeID (Info->EditorID);
  return (0);
 
 case EE_REDRAW:
  if (reloadNeeded)
  {
   reloadInProcess = true;
   DoneMacro ();
   InitMacro ();
  }
  return (0);
 
 default:
  return (0);
 }
}

Porque posteriormente utiliza funciones proporcionadas por Far Manager, por ejemplo:

::Info.EditorControl(
    -1,
    ECTL_GETINFO,
    0,
    &ei
);

Esta llamada permite consultar informacion del editor activo. TrueTemplate utiliza este tipo de operaciones para identificar el archivo abierto y procesar sus plantillas.

Es importante distinguir: SetStartupInfoW no implementa el autocompletado. Proporciona el acceso a servicios que otros componentes del plugin utilizaran despues.

3.3 Relacionemos las tres funciones

FuncionPapel
GetGlobalInfoWIdentificacion del plugin
SetStartupInfoWInicializacion y acceso a servicios
GetPluginInfoWIntegracion en menus y opciones
Todavia nos falta OpenW, que sera particularmente importante: es uno de los puntos por lo que Far Manager puede activar una funcionalidad del plugin.

Comprobacion antes de continuar…

Imaginemos que estamos creando HelloFar y queremos que aparezca como una opcion en el menu de plugins de Far Manager.

En que funcion debemos definir esa entrada?

Si tu respuesta es GetGlobalInfoW esta incorrecto porque:

A veces puede ser confuso porque ambas funciones GetGlobalInfoW y GetPluginInfoW trabajan con informacion del plugin, pero tienen responsabilidades diferentes.

FuncionResponsabilidad
GetGlobalInfoWIdentidad: nombre, autor, descripcion, version y GUID
GetPluginInfoWIntegracion: entradas del menu, configuracion y prefijos de comandos
OpenWEjecutar la funcionalidad cuando se activa el plugin
Por ejemplo, en TrueTemplate:
Info->Title = L"True Template";

Se establece dentro de GetGlobalInfoW.

Pero:

Info->PluginMenu.Count = 1;
Info->PluginMenu.Strings = PluginMenuStrings;

Pertenece a GetPluginInfoW y configura su entrada en el menu.

Recuerda: una cosa es como se identifica el plugin y otra doonde aparece para que el usuario pueda ejecutarlo.

Tema 4: OpenW: hacer que nuestro plugin ejecute algo

Ya conocemos las funciones que identifican y configuran el plugin. Nos faltaba una pieza: que sucede cuando seleccionamos nuestro plugin desde F11?

Ahi entra OpenW.

4.1 Que es OpenW?

Su firma en el SDK de Far Manager 3 es:

HANDLE WINAPI OpenW(const struct OpenInfo *Info);

Descompongamosla:

  • HANDLE: es el tipo de valor que devuelve. Puede representar el identificador de un panel de plugin o un valor especial.
  • WINAPI: establece la convencion de llamada.
  • OpenW: nombre que Far Manager reconoce.
  • const struct OpenInfo *Info: recibe informacion sobre la forma en que se activo el plugin.

En el SDK existe la estructura OpenInfo:

struct OpenInfo {
 size_t StructSize;
 enum OPENFROM OpenFrom;
 const UUID* Guid;
 intptr_t Data;
 void* Instance;
} 

Aqui aparece un concepto nuevo: OpenFrom.

Este campo indica el contexto desde el que se solicito abrir el plugin.

Algunos valores son:

ValorSignificado
OPEN_PLUGINSMENUActivado desde el menu de plugins
OPEN_COMMANDLINEActivado desde la linea de comandos
OPEN_EDITORActivado desde el editor
OPEN_VIEWERActivado desde el visor
OPEN_FILEPANELActivado desde un panel de archivos

Por lo tanto, un plugin puede comportarse de distintas maneras dependiendo de donde lo ejecutemos.

Por ejemplo:

if (Info->OpenFrom == OPEN_PLUGINSMENU) {
 // Ejecutar una funcionalidad desde F11
}

Esto es particularmente util para desarrollar plugins que funcionen tanto en los paneles como en el editor.

4.2 Que devuelve OpenW?

Para nuestro primer plugin no necesitamos crear paneles virtuales.

Unicamente queremos mostrar un mensaje cuando el usuario seleccione HelloFar.

Por eso, despues de ejecutar nuestra funcion, devolveremos:

return nullptr;

En este caso significa que no estamos devolviendo un identificador de panel de plugin para mantenerlo abierto.

Tema 5: Construyamos HelloFar

Nuestro primer plugin tendra un objetivo muy concreto:

Para ello necesitaremos cuatro funciones:

GetGlobalInfoW()
SetStartupInfoW()
GetPluginInfoW()
OpenW()

El SDK permite integrar mas callbacks, pero para este ejemplo no necesitamos implementar todos los que tiene TrueTemplate.

5.1 Codigo de nuestro primer plugin

Guardamos el siguiente contenido en HelloFar.cpp. Esta basado en las estructuras del SDK de Far Manager 3 que revisamos y en el patron utilizado por TrueTemplate.

Algunos datos antes:

GUID: Un GUID (Globally Unique Identifier) es un identificador de 128 bits (16 bytes) utilizado para identificar elementos de forma unica.

Como se genera?

En WIndows puedes abrir PowerShell y ejecutar:

[guid]::NewGuid()

La estructura GUID de Windows:

typedef struct _GUID {
 unsigned long Data1; // 32 bits (4 bytes)
 unsigned short Data2; // 16 bits (2 bytes)
 unsigned short Data3; // 16 bites (2 bytes)
 unsigned char Data4[8]; // 64 bits (8 bytes)
} GUID;

Como se corresponde con el textro?

Si [guid]::NewGuid() te da:

8d4c0fb4-a505-4099-bbb0-9fc4f9962e82

los grupos del texto se reparten asi:

TextoCampoValor
8d4c0fb4Data10x8d4c0fb4
a505Data20xA505
4099Data30x4099
bbb0Data4[0..1]0xBB, 0xB0
9fc4f9962e82Data4[2..7]0x9F, 0xC4, 0xF9, 0x96, 0x2E, 0x82
En C quedaria asi:
GUID miGuid = {
 0x8d4c0fb4, 0xA505, 0x4099, { 0xBB, 0xB0, 0x9F, 0xC4, 0xF9, 0x96, 0x2E, 0x82 }
};

El detalle importante: el orden de los bytes en memoria.

Data1, Data2 y Data3 son enteros, asi que en x86/x64 se guardan en littlle-endian (al reves). Data4 es un arreglo de bytes, asi que se guarda tal cual. Por eso ToByteArray() no devuelve los bytes en el orden del texto:

Texto:       6B29FC40 - CA47 - 4067 - B31D - 00DD010662DA
Memoria:     40 FC 29 6B   47 CA   67 40   B3 1D 00 DD 01 06 62 DA
             └─ Data1 ─┘   └D2─┘   └D3─┘   └────── Data4 ──────┘
             (invertido)  (invert.) (invert.)    (igual)

Sacar los campos desde PoweShell

$g = [guid]::NewGuid()
$b = $g.ToByteArray()
 
$Data1 = [BitConverter]::ToUInt32($b, 0)
$Data2 = [BitConverter]::ToUInt16($b, 4)
$Data3 = [BitConverter]::ToUInt16($b, 6)
$Data4 = $b[8..15]
 
"GUID: $g"
"{{ 0x{0:X8}, 0x{1:X4}, 0x{2:X4}, {{ {3} }} }}" -f $Data1, $Data2, $Data3, (($Data4 | ForEach-Object { '0x{0:X2}' -f $_ }) -join ', ')

{ 0x191906AE, 0x1292, 0x47BF, { 0xBD, 0x00, 0x11, 0x0C, 0x16, 0xA5, 0xA8, 0x74 } }

Esto te imprime el GUID listo para pegarlo en C. Funciona porque BitConverter tambien usa little-endian en Windows, asi que deshace la inversion.

Extra: lo que te dice GUID de NewGuid()

NewGuid() genera un GUID de version 4 (aleatorio):

  • El primer digito hexadecimal de Data3 siempre es 4 (el 4067 del ejemplo).
  • Los 2 bits altos de Data4[0] siempre son 10, asi que ese byte empieza por 8, 9, A o B.

De los 128 bits, 122 son aleatorios y 6 son fijos.

Que es guiddef.h?

Es un header del Windows SDK, y ya lo tenemos instalado. En el equipo esta en:

C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\shared\guiddef.h

La definicion esta en las lineas 22-27 de ese archivo, tal como lo mostramos al inicio.

abrirlo desde el bloc de notas: notepad "C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\shared\guiddef.h"

Primera funcion: PluginGuid

static const GUID PluginGuid = {
 0x191906AE, 0x1292, 0x47BF, { 0xBD, 0x00, 0x11, 0x0C, 0x16, 0xA5, 0xA8, 0x74 } 
};

PluginGuid identifica nuestro plugin como unico para Far Manager.

Ahora tambien lo mismo para MessageGUid, porque por ejemplo, imaginemos que nuestro plugin HelloFar tiene tres ventanas.

  • Una que muestra un saludo
  • Otra que muestra un error
  • Otra que muestra informacion del plugin

Todas pertenecen al mismo plugin, pero cada ventana puede tener su propio GUID.

static const GUID MessageGuid = {
 0xA73B82C1, 0xD41F, 0x4D6A, {0xA5, 0x27, 0x1E, 0x6F, 0x83, 0xB2, 0xD9, 0x44} 
};

Como se relacionan?

FarInfo.Message(&PluginGuid, &MessageGuid, FMSG_MB_OK, nullptr, messages, 2, 0);

Far Manager utiliza PluginGuid para identificar al propietario y MessageGuid como identificador de esa instancia logica del mensaje, util para funciones como su identificacion o seguimiento interno.

Es obligatorio crear un MessageGuid?

No. El segundo parametro de FarInfo.Message() adminte nullptr.

Para nuestro primer plugin podemos simplificarlo:

FarInfo.Message(&PluginGuid, nullptr, FMSG_MB_OK, nullptr, messages, 2, 0);

Esto funciona sin necesidad de declarar MessageGuid.

**En resumen: PluginGuid es importante para identificar nuestro plugin, mientras que MessageGuid es opcional para este cuadro de mensaje sencillo.

Para HelloFar, podemos eliminar MessageGuid y mantener nuestro codigo mas simple o no…

Segunda funcion MessageGuid

static const GUID MessageGuid = {
 0xA73B82C1, 0xD41F, 0x4D6A, {0xA5, 0x27, 0x1E, 0x6F, 0x83, 0xB2, 0xD9, 0x44} 
};

Tercera funcion FarInfo()

static PluginStartupInfo FarInfo{};

Que hace static PluginStartupInfo FarInfo{};?

Declara una variable llamada FarInfo del tipo PluginStartupInfo, que es una estructura definida en el SDK de Far Manager.

  • static: al declararla fuera de las funciones, limita su visibilidad a ese archivo .cpp y mantiene su existencia durante la ejecucion del modulo.
  • PluginStartupInfo: es la estructura que contiene los servicios que Far Manager proporciona aa nuestro plugin.
  • FarInfo: es el nombre que le asignamos a nuestra variable.
  • {}: inicializa sus campos a sus valores predeterrminados, normalmente cero o punteros nulos.

Cuando recibe esa informacion?

En estra funcion:

void WINAPI SetStartupInfoW(const PluginStartupInfo* info) {
 FarInfo = *info; 
}

Aqui Far Manager nos proporciona una estructura con sus funciones y servicios.

Nosotros copiamos esa informacion a FarInfo para poder utilizarla posteriormente.

Por ejemplo:

FarInfo.Message(...);

Asi podemos mostrar mensajes mediante la API de Far Manager.

Algo que recalcar, no nos confundamos:

DeclaracionSignificado
static PluginStartupInfo{};Declara e inicializa una variable.
static PluginStartupInfo;Declara una variable (tambien queda inicializada a cero al ser estatica)
static PluginStartupInfo();Declara una fucion
En nuestro plugin debemos utilizar:
static PluginStartupInfo{};

Porque necesitamos almacenar los servicios que Far Manager nos entrega y reutilzarlos desde funciones como OpenW.

Hasta ahora como va HelloFar

#include <windows.h>
#include "plugin.hpp"
 
// Identificador unico de nuestro plugin
 
static const GUID PluginGuid = {
 0x191906AE, 0x1292, 0x47BF, { 0xBD, 0x00, 0x11, 0x0C, 0x16, 0xA5, 0xA8, 0x74 } 
};
 
// Identificador de la ventana de mensaje.
 
static const GUID MessageGuid = {
 0xA73B82C1, 0xD41F, 0x4D6A, {0xA5, 0x27, 0x1E, 0x6F, 0x83, 0xB2, 0xD9, 0x44} 
};
 
// Servicios que proporciona Far Manager.
 
static PluginStartupInfo FarInfo();
 
// 1. Identidad de plugin.
 
extern "C" __declspec(dllexport)
void WINAPI GetGlobalInfoW(GlobalInfo* info) {
 info->StructSize = sizeof(GlobalInfo);
 info->MinFarVersion = MAKEFARVERSION(3, 0, 0, 4040, VS_RELEASE);
 info->Version = MAKEFARVERSION(1, 0, 0, 0, VS_RELEASE);
 info->Guid = PluginGuid;
 info->Title = L"HelloFar"; 
 info->Description = L"Mi primer plugin de Far Manager";
 info->Author = L"Cesar";
}
 
// 2. Obtener los servicios de Far Manager
 
extern "C" __declspec(dllexport)
void WINAPI SetStartupInfoW(const PluginStartupInfo* info) {
 FarInfo = *info; 
}

Tercera funcion GetPluginInfoW

Esta funcion proporciona a Far Manager informacion sobre como integrar nuestro plugin en su interfaz.

Por ejemplo, determina que opcion aparecera cuando presionemos F11.

Primera parte: exportar la funncion

extern "C" __declspec(dllexport)
void WINAPI GetPluginInfoW(PluginInfo* info)

Ya conocemos algunos elementos, pero relacionemoslo:

ElementoFuncion
extern "C"Evita la modificacion del nombre exportado por C++.
__declspec(dllexport)Exporta la funcion desde la DLL.
voidNo devuelve ningun valor.
WINAPIEspecifica la convencion de llamada.
GetPluginInfoWNombre que Far Manager busca en el plugin.
PluginInfo* infoPuntero a la estructura que debemos completar.
Algo interesante es que: no devolvemos el nombre del plugin mediante return

En lugar de eso, modificamos la estructura apuntada por info.

Es el mismo concepto de punteros que estudiamos anteriormente.

static const wchar_t* menuNames[] = {
 L"HelloFar";
};

Esta linea contiene varios conceptos.

wchar_t es un tipo de caracter ancho, utilizado en interfaces Unicode como la de Far Manager.

Por ejemplo:

const wchar_t* nombre = L"HelloFar";

Aqui nombre apunta a una cadena de caracteres anchos.

Pero en nuestro codigo tenemos:

const wchar_t* menuNames[] = {
 L"HelloFar";
};

Eso es un arreglo de punteros a cadenas.

Por que un arreglo?

Porque Far Manager permite que un plugin proporcione varias entradas de menu.

Por ejemplo:

static const wchar_t* menuNames[] = {
 L"HelloFar";
 L"Analizar archivo PE";
 L"Mostrar informacion";
};

En ese caso tendriamos tres entradas en ves de una.

Y para que sirve static?

Cuando declaramos una variable local normal dentro de una funcion, su tiempo de vida termina cuando la funcion finaliza.

Pero:

static const wchart_t* menuNames[] = {
 L"HelloFar";
};

hace que el arreglo conserve su existencia durante toda la ejecucion del modulo.

Esto es importante porque entregaremos un puntero a ese arreglo a Far Manager.

Sin static, ese puntero podria apuntar a un arreglo local que ya dejo de existir cuando Far Manager intente consultarlo.

static const GUID menuGuids[] = {
 PluginGuid
};

Ya sabemos que es un GUID.
Aqui estamos creando un arreglo con los identificadores correspondientes a las entradas de menu.

Como solamente tenemos una entrada, nuestro arreglo contiene un GUID.

Observemos esta relacion:

PosicionmenuNamesmenuGuids
0HelloFarPluginGuid
Cada entrada tiene su texto y su identificados asociado.

En un plugin mas complejo, podriamos utilizar GUID diferentes para identificar distintas acciones.

PluginMenu

Aqui estamos completando tres campos de una estructura interna.

Es decir:

  • Strings apunta al arreglo de nombres.
  • Guids apunta al arreglo de identificadores.
  • Count indica cuantas entradas existen.

Y eso es todo lo necesario para comprender la parte fundamental del regisotr de nuestra entrada en F11.

Comprobacion rapida…

Supongamos que modificamos el codigo de la siguiente manera:

static const wchar_t* menuNames[] = {
    L"Calcular SHA256",
    L"Analizar PE",
    L"Información"
};
 
info->PluginMenu.Strings = menuNames;
info->PluginMenu.Count = 1;

Cuantas entradas de nuestro plugin le estamos indicando a Far Manager que muestre?

Respuesta: Piensa chibolo.

Codigo completo:

// 3. Registrar la entrada en el menu F11.
 
extern "C" __declspec(dllexport)
void WINAPI GetPluginInfoW(PluginInfo* info) {
 static const wchar_t* menuNames[] = { L"HelloFar"; };
 
 static const GUID menuGuids[] = { PluginGuid };
 
 info->StructSize = sizeof(PluginInfo);
 info->Flags = PF_NONE;
 
 // `PluginMenu` es una estructura metida dentro de `PluginInfo`, por eso despues de `->` se usa `.` para entrar en sus campos. 
 info->PluginMenu.Strings = menuNames;
 info->PluginMenu.Guids = menuGuids;
 info->PluginMenu.Count = 1;
}

Tema 6: OpenW: ejecutar acciones desde nuestro plugin

1. Entendamos su estructura

En nuestro plugin HelloFar escribimos:

extern "C" __declspec(dllexport)
HANDLE WINAPI OpenW(const OpenInfo* info) {
 if (info->OpenFrom == OPEN_PLUGINSMENU) {
  // Ejecutar nuestra funcionalidad
 }
 return nullptr;
}

Analicemos las partes nuevas:

ElementoSignificado
HANDLETipo de valor que devuelve la funcion.
OpenWFuncion que Far Manager puede invocar para abrir el plugin.
const OpenInfo* infoPuntero a la informacin de apertura.
info->OpenFromIndica des que contexto se activo.
return nullptrNo devuelve un identificador de panel del plugin.

Que es OpenFrom?

No todos los plugins se ejecutan necesariamente desde F11.

Por ejemplo, podemos activarlos desde el editor, el visor o mediante un prefijo en la linea de comandos.

Far Manager utiliza el campo OpenFrom para indicarnos desde donde se inicio la operacion.

if (info->OpenFrom == OPEN_PLUGINSMENU)

Esta condicion pregunta:

El usuario abrio nuestro plugin desde el menu de plugins?

Si la respuesta es verdadera, ejecutamos el codigo contenido en el bloque.

Lo importante es comprender que OpenFrom no identifica una funcion concreta del plugin. Identifica el contexto de apertua.

3. Relacionemoslo con el codigo real de TrueTemplate

Codigo completo:

HANDLE WINAPI OpenW(const struct OpenInfo *Info)
{
 if (!IsOldFar)
 {
  ptrdiff_t   n;
  EditorInfoEx ei;
  wchar_t filepath[NM];
  wchar_t filename[NM];
  switch (Info->OpenFrom)
  {
  case OPEN_VIEWER:
   {
    ViewerInfoEx vi;
    ::Info.ViewerControl(-1, VCTL_GETINFO, 0, &vi);
    ::Info.ViewerControl(-1, VCTL_GETFILENAME, NM, filename);
    wcscpy(filepath, filename);
    *Point2FileName (filepath) = 0;
    n = findLngID (filename);
    if (n != -1) CompilerMenu (nullptr, filename, filepath, n);
    break;
   }
  case OPEN_COMMANDLINE:
   {
    OpenCommandLineInfo* ocli = (OpenCommandLineInfo*)Info->Data;
    wchar_t tmp[NM];
    wcsncpy (tmp, (wchar_t *) ocli->CommandLine, NM);
    FSF.Unquote (tmp);
    wchar_t* filename = tmp + 4;
    wchar_t file[NM];
    wchar_t path[NM];
    wcscpy(path, filename);
    wchar_t* ch = wcsrchr(path, L'\\');
    if (ch == nullptr)
    {
     FarPanelDirectory* dirInfo = (FarPanelDirectory*)malloc(sizeof(FarPanelDirectory) + NM);
     if (dirInfo)
     {
      ZeroMemory(dirInfo, sizeof(FarPanelDirectory));
      dirInfo->StructSize = sizeof(FarPanelDirectory);
      ::Info.PanelControl (PANEL_ACTIVE, FCTL_GETPANELDIRECTORY, sizeof(FarPanelDirectory) + NM, dirInfo);
 
      wcscpy (path, dirInfo->Name);
      wcscpy (file, dirInfo->Name);
      wcscat (file, L"\\");
      wcscat (file, filename);
 
      free(dirInfo);
     }
    }
    else
    {
     wcscpy (file, filename);
     *ch = L'\0';
    }
    if (tmp[2] == L'l')
    {
     n = findLngID (file);
     if (n != -1) CompilerMenu (nullptr, file, path, n);
    }
 
    if (tmp[2] == L'f')
    {
     n = findLngID (file);
     if (n != -1) FormatMenu (file, n);
    }
    break;
   }
  case OPEN_EDITOR:
   ::Info.EditorControl (-1, ECTL_GETINFO, 0, &ei);
   ::Info.EditorControl (ei.EditorID, ECTL_GETFILENAME, NM, filename);
   wcscpy(filepath, filename);
   *Point2FileName (filepath) = 0;
   initEList ();
   n = eList->findID (ei.EditorID);
   if (n == -1) n = eListInsert (ei.EditorID, filename);
   if (n != -1)
   {
    TEInfo *te = (*eList)[n];
    if (te)
    {
     FarMenuItemEx mMenu[9];
     int      count = _countof (mMenu);
     for (int i = 0; i < count; i++) mMenu[i].Flags = 0;
     mMenu[0].Text = GetMsg (MMenuTemplates);
     mMenu[1].Text = GetMsg (MFormatting);
     mMenu[2].Text = GetMsg (MNavigation);
     mMenu[3].Text = GetMsg (MNavigationList);
     mMenu[4].Text = GetMsg (MComments);
     mMenu[5].Text = GetMsg (MMenuSelectLang);
     mMenu[6].Text = GetMsg (MMenuCompile);
     mMenu[8].Text = GetMsg (MShowOutput);
     mMenu[7].Flags |= MIF_SEPARATOR;
     if (!compilerOut) mMenu[8].Flags |= MIF_DISABLE;
 
     intptr_t mode = ::Info.Menu
      (
       &MainGuid,
       &OpenEditorGuid,
       -1,
       -1,
       0,
       FMENU_WRAPMODE,
       GetMsg (MTitle),
       nullptr,
       nullptr,
       nullptr,
       nullptr,
       (const FarMenuItemEx *) mMenu,
       count
      );
     switch (mode)
     {
     case 0:
      SelectTemplate (te);
      break;
     case 1:
      SelectFormatting (te);
      break;
     case 2:
      SelectNavigation (te, filepath);
      break;
     case 3:
      SelectNavigationList (te, filepath);
      break;
     case 4:
      BlockComments (te);
      break;
     case 5:
      SelectTemplateSet (te);
      break;
     case 6:
      SelectCompiler (te);
      break;
     case 8:
      ShowOutput (filepath);
      break;
     }
    }
   }
   break;
  case OPEN_PLUGINSMENU:
   FarMenuItemEx mMenu[3];
 
   int count = _countof (mMenu);
   mMenu[0].Text = GetMsg (MMenuCompile);
   mMenu[2].Text = GetMsg (MShowOutput);
   mMenu[1].Flags |= MIF_SEPARATOR;
   if (!compilerOut) mMenu[2].Flags |= MIF_DISABLE;
 
   intptr_t mode = ::Info.Menu
    (
     &MainGuid,
     &OpenMenuGuid,
     -1,
     -1,
     0,
     FMENU_WRAPMODE,
     GetMsg (MTitle),
     nullptr,
     nullptr,
     nullptr,
     nullptr,
     (const FarMenuItemEx *) mMenu,
     count
    );
   switch (mode)
   {
   case 0:
   case 2:
    PanelInfo PInfo;
    if (::Info.PanelControl (PANEL_ACTIVE, FCTL_CHECKPANELSEXIST, 0, &PInfo) == TRUE)
    {
     ::Info.PanelControl (PANEL_ACTIVE, FCTL_GETPANELINFO, 0, &PInfo);
     if (PInfo.ItemsNumber > 0)
     {
      FarPanelDirectory* dirInfo = (FarPanelDirectory*)malloc(sizeof(FarPanelDirectory) + NM);
      if (dirInfo)
      {
       ZeroMemory(dirInfo, sizeof(FarPanelDirectory));
       dirInfo->StructSize = sizeof(FarPanelDirectory);
       ::Info.PanelControl (PANEL_ACTIVE, FCTL_GETPANELDIRECTORY, sizeof(FarPanelDirectory) + NM, dirInfo);
 
       wchar_t path[NM];
       wcscpy (path, dirInfo->Name);
 
       wchar_t buf[NM];
       wcscpy (buf, dirInfo->Name);
       wcscat (buf, L"\\");
 
       free(dirInfo);
 
       if (mode == 0)
       {
        size_t Size=::Info.PanelControl(PANEL_ACTIVE, FCTL_GETPANELITEM, PInfo.CurrentItem, 0);
        PluginPanelItem *PPI=(PluginPanelItem*)malloc(Size);
        if (PPI)
        {
         FarGetPluginPanelItem FGPPI={sizeof(FarGetPluginPanelItem),Size,PPI};
         ::Info.PanelControl(PANEL_ACTIVE, FCTL_GETPANELITEM, PInfo.CurrentItem, &FGPPI);
         wcscat (buf, PPI->FileName);
         free(PPI);
        }
 
        n = findLngID (buf);
        if (n != -1) CompilerMenu (nullptr, buf, path, n);
       }
       else if (mode == 2)
       {
        ShowOutput (path);
       }
      }
     }
    }
    break;
   }
   break;
  }
 }
 
 return (nullptr);
}

Demasiado wtf.

Vayamos por un fragmento simplificado que conserva la organizacion del codigo original.

HANDLE WINAPI OpenW(const struct OpenInfo *Info)
{
    if (!IsOldFar)
    {
        switch (Info->OpenFrom)
        {
            case OPEN_VIEWER:
                // Operaciones del visor
                break;
 
            case OPEN_COMMANDLINE:
                // Operaciones desde la línea de comandos
                break;
 
            case OPEN_EDITOR:
                // Operaciones del editor
                break;
 
            case OPEN_PLUGINSMENU:
                // Operaciones desde F11
                break;
        }
    }
 
    return nullptr;
}

Aqui vemos una diferencia interesante respecto a HelloFar: TrueTemplate no utiliza solamente un if, sino un switch.

Por que utiliza switch?

Porque necesita distinguir varios valores posibles de OpenFrom.

Por ejemplo:

switch (Info->OpenFrom)
{
    case OPEN_EDITOR:
        // Trabajar con el editor
        break;
 
    case OPEN_PLUGINSMENU:
        // Trabajar con los paneles
        break;
}

En el codigo real de TrueTemplate ocurre algo mas: cuando recibe OPEN_EDITOR, crea un menu con acciones como insertar plantillas, formatear, navegar por codigo y compilar.

Es decir, OpenW puede presentar un segundo menu con funcionalidades propias del plugin.

Esto sera muy util cuando construyamos un plugin con varias herramientas.

Codigo de TrueTemplate:

FarMenuItemEx mMenu[9];
     int      count = _countof (mMenu);
     for (int i = 0; i < count; i++) mMenu[i].Flags = 0;
     mMenu[0].Text = GetMsg (MMenuTemplates);
     mMenu[1].Text = GetMsg (MFormatting);
     mMenu[2].Text = GetMsg (MNavigation);
     mMenu[3].Text = GetMsg (MNavigationList);
     mMenu[4].Text = GetMsg (MComments);
     mMenu[5].Text = GetMsg (MMenuSelectLang);
     mMenu[6].Text = GetMsg (MMenuCompile);
     mMenu[8].Text = GetMsg (MShowOutput);
     mMenu[7].Flags |= MIF_SEPARATOR;

4. Y como sabe que opcion especifica seleccionamos?

Aqui debemos distinguir dos cosas:

OpenFrom responde a la pregunta: desde donde se abrio el plugin?

Guid dentro de OpenInfo puede identificar que entrada registrada del plugin se selecciono.

Por ejemplo, si registramos tres entradas en GetPluginInfoW, podemos asignarles GUID distintos y compararlos dentro de OpenW.

Sin embargo, TrueTemplate utiliza tambien otra estrategia: registra una entrada principal y posteriormente muestra un menu propio mediante Info.Menu. Luego utiliza el numero de la opcion seleccionada para decidir que operacion ejecutar.

Esto demuestra que podemos diseniar un mismo plugin de diferentes maneras.

Tema 7: FarInfo.Message y los punteros a funciones

Hasta ahora sabemos como Far Manager reconoce nuestro plugin, como lo incorpora al menu F11 y como ejecuta OpenW.

Pero, como hacemos para que nuestro plugin utilice las funcionalidades internas de Far Manager?

Por ejemplo, mostrar una ventana de mensaje.

1. Recordemos que es FarInfo

En nuetro codigo declaramos:

static PluginStartupInfo FarInfo{};

Esta variable almacena una estrucutra definida por el SDK de Far Manager.

Dentro de PluginStartupInfo encontramos campos como:

struct PluginStartupInfo {
 size_t StructSize;
 
 // Otros campos....
 
 FARAPIMENU Menu;
 FARAPIMESSAGE Message;
 FARAPIGETMSG GetMsg;
 FARAPIEDITORCONTROL EditorContro;
}

Este es un fragmento simplificado de la estructura real.

Observa que Message no es una funcion que nosotros hayamos desarrollado. Es un puntero a una funcion que Far Manager pone a disposicion del plugin.

2. Diferencia entre un callback y un puntero a funcion

Un puntero a funcion es un mecanismo del lenguaje C++ que permite almacenar e invocar una funcion mediante su direccion.

Un calback es una funcion que proporcinamos a otro componente para que este la invoque cuando corresponda.

Entonces…

  • OpenW actua como una funcion de entrada que Far Manager invoca.
  • FarInfo.Message es un puntero a una funcion proporcionada por Far Manager que nuestro plugin puede invocar.

Tema 8: Compilar nuestro primer Plugin HelloFar

Nuestro objetivo ahora es pasar del codigo C++ a un archivo HelloFar.dll que Far Manager pueda cargar.

Lo haremos con Visual Studio Community y el SDK de Far Manager 3.

Ejecutamos desde Far Manager o podemos hacerlo directamente desde Visual Studio Community

C:\TDM-GCC-64\bin\c++.exe -shared -o HelloFar.dll HelloFar.cpp

Tema 9: Instalar y probar HelloFar

Paso 1. Instalar la DLL

Localiza la carpeta donde tienes instalado Far Manager (donde se encuentra Far.exe).

Dentro de Plugins, crea una carpeta llamada HelloFar y copia la DLL:

Ahora…

  1. Cerramos Far Manager si esta abierto.
  2. Volvemos a iniciarlo
  3. Presionamos F11 desde uno de los paneles de archivos.
  4. Buscamos HelloFar
  5. Seleccionamos y presionamos Enter

En caso de que no aparezca HelloFar, primero debemos comprobar las funciones exportadas de nuestra DLL.

C:\TDM-GCC-64\bin\objdump.exe -p HelloFar.dll

Deberian figurar esas:

SI las funciones estan exportadas correctamente, revisaremos despues la compatibilidad de arquitectura, la carga de dependiencias y la version del SDK.

Codigo final:

#include <windows.h>
#include "plugin.hpp"
 
// Identificador unico de nuestro plugin
 
static const GUID PluginGuid = { 0x191906AE, 0x1292, 0x47BF, { 0xBD, 0x00, 0x11, 0x0C, 0x16, 0xA5, 0xA8, 0x74 } };
 
// Identificador de la ventana de mensaje.
 
static const GUID MessageGuid = { 0xA73B82C1, 0xD41F, 0x4D6A, { 0xA5, 0x27, 0x1E, 0x6F, 0x83, 0xB2, 0xD9, 0x44 } };
 
// Servicios que proporciona Far Manager.
 
static PluginStartupInfo FarInfo{};
 
// 1. Identidad de plugin.
// Recordemos que en este caso yo estoy utilizando un puntero.
// En este caso estamos recibiendo un puntero a un objeto `GlobalInfo`
 
extern "C" __declspec(dllexport)
void WINAPI GetGlobalInfoW(GlobalInfo* info) {
 info->StructSize = sizeof(GlobalInfo);
 info->MinFarVersion = MAKEFARVERSION(3, 0, 0, 4040, VS_RELEASE);
 info->Version = MAKEFARVERSION(1, 0, 0, 0, VS_RELEASE);
 info->Guid = PluginGuid;
 info->Title = L"HelloFar"; 
 info->Description = L"Mi primer plugin de Far Manager";
 info->Author = L"Cesar";
}
 
// 2. Obtener los servicios de Far Manager
 
extern "C" __declspec(dllexport)
void WINAPI SetStartupInfoW(const PluginStartupInfo* info) {
 FarInfo = *info; 
}
 
// 3. Registrar la entrada en el menu F11.
 
extern "C" __declspec(dllexport)
void WINAPI GetPluginInfoW(PluginInfo* info) {
 static const wchar_t* menuNames[] = { L"HelloFar" };
 
 static const GUID menuGuids[] = { PluginGuid };
 
 info->StructSize = sizeof(PluginInfo);
 info->Flags = PF_NONE;
 
 // `PluginMenu` es una estructura metida dentro de `PluginInfo`, por eso despues de `->` se usa `.` para entrar en sus campos. 
 info->PluginMenu.Strings = menuNames;
 info->PluginMenu.Guids = menuGuids;
 info->PluginMenu.Count = 1;
}
 
// 4. Ejecutar la funcionalidad.
// Recordemos que: extern "C" __declspec(dllexport)
// Es la forma de decirle al compilador: "esta funcion debe salir de la DLL con este nombre exacto, para que otro programa la encuentre."
 
extern "C" __declspec(dllexport)
HANDLE WINAPI OpenW(const OpenInfo* info) {
 if (info->OpenFrom == OPEN_PLUGINSMENU) {
  static const wchar_t* messages[] = { L"HelloFar", L"Hola desdes el primer pluginnnnn", L"Autor: Cesar Apolaya" };
  FarInfo.Message(&PluginGuid, &MessageGuid, FMSG_MB_OK, nullptr, messages, 3, 0);
 }
 return nullptr;
}