Tuesday, May 06, 2008

Writing Far Manager plugin in C

Far Manager is a file manager for Windows, however the most functionality in Far is contained in plugins. Here is a short introduction how to create one.

You'll need Far Manager with Development Pack, GCC compiler and Developer's Encyclopedia at hand. Encyclopedia is also available in Development Pack in .chm format.

Plugin is a .dll file compiled from .c source. For the minimal example you'll need to create .c file with at least one function GetPluginInfo() that tells Far about plugin capabilities.



This Sequence Diagram illustrates communication of Far with a plugin that does absolutely nothing. The source of the plugin is below:

#include "plugin.hpp"

void WINAPI GetPluginInfo(struct PluginInfo *Info) {
Info->StructSize = sizeof(struct PluginInfo);
}

Encyclopedia states that StructSize should be filled with the size of PluginInfo structure to maintain backwards compatibility in case of future API changes. Because plugin really does nothing, it is impossible to tell if it works or not. To prove it really works let's add logging to file.

#include <stdio.h>
#include "plugin.hpp"

void WINAPI GetPluginInfo(struct PluginInfo *Info) {
FILE *file;
file = fopen("minilog.txt", "a+");
fprintf(file, "%s\n", "Fired.");
fclose(file);

Info->StructSize = sizeof(struct PluginInfo);
}

Copy "plugin.hpp" near to mini.c and execute GCC to compile the plugin:

gcc -shared -o mini.dll mini.c -Wl,--kill-at

Place mini.dll into plugin path or execute Far with /p parameter pointing to directory with mini.dll Press Ctrl-R and look for "minilog.txt" file in current dir containing burning proof that plugin works. This is enough to get started and follow Developer's Encyclopedia on your own, there are still some technical details that may answer some questions about GCC options and .dll writing not covered in Encyclopedia.


DLL, exports and GCC options


GCC parameters shown above instruct it to compile mini.c into shared library mini.dll To be recognized as a plugin the .dll should make GetPluginInfo() function visible to Far (i.e. exported from the library) the same way as any other function that Far calls in plugins. By default all functions in .dll are exported and this is visible in .dll as export table that can be checked for example with BIEW. The list of exported functions can (and usually should) be narrowed to increase performance and clean API either with .DEF file or with __declspec(dllexport) addition to function prototype. Before going with example there is one more thing left to explain - -Wl,--kill-at parameter.

By default function names are exported with "at" suffix like GetPluginInfo@4 where 4 denotes the number of bytes the argument takes. When looking for plugins Far looks for clean names without @4. The last option to gcc -Wl,--kill-at is required to strip "at" suffix from exported function name. Read this link for more details about this calling convention.

To illustrate how __declspec(dllexport) works we move logging code into separate function and compile it with:

gcc -shared -o mini.dll mini.c

#include <stdio.h>
#include "plugin.hpp"

void logfile(const char* msg) {
FILE *file;
file = fopen("minilog.txt", "a+");
fprintf(file, "%s\n", msg);
fclose(file);
}

void WINAPI GetPluginInfo(struct PluginInfo *Info) {
Info->StructSize = sizeof(struct PluginInfo);
logfile("GetPluginInfo called.");
}

If you now launch BIEW on mini.dll and press Alt-F3 to get to export table, you'll see two exported functions - logfile and GetPluginInfo. Latter with @ suffix. @ is added by WINAPI calling convention.



To remove suffix recompile plugin with:
gcc -shared -o mini.dll mini.c -Wl,--kill-at

To leave only GetPluginInfo() function in export table - add __declspec(dllexport) to its definition. If __declspec(dllexport) is present in at least one functions definition, all other functions that doesn't have this tag will be excluded from export.

#include <stdio.h>
#include "plugin.hpp"

void logfile(const char* msg) {
FILE *file;
file = fopen("minilog.txt", "a+");
fprintf(file, "%s\n", msg);
fclose(file);
}

void WINAPI __declspec(dllexport) GetPluginInfo(struct PluginInfo *Info) {
Info->StructSize = sizeof(struct PluginInfo);
logfile("GetPluginInfo called.");
}

Inspecting .dll to see if the "at" suffix is gone (thanks to GCC options) and only one function is present in export table (the one marked with __declspec).



Further Steps


There are four basic export functions in Far Plugin API that come handy at start. Plugins export these function to let Far call them to supply information about itself and gather data about plugin. Functions (if present) are invoked in particular order which is illustrated in the following table.


NameRequiredOrderComment
GetMinFarVersion()no1called first
SetStartupInfo()no2always called if present - good for extra initialization code
GetPluginInfo()yes3cached
OpenPlugin()no4not required, but without it plugin is pretty useless



With this yet another lame Sequence Diagram there should be enough useful information to get started. Among improvement that could be done to mini.c code is to replace stdio.h library calls with native Windows API and Far API calls, adding help, menus and language files. However this falls out of scope of this post, so the best way to move further is to checkout Encyclopedia. Good luck!