Иван Синтюрин
spinoza@mail.ru
http://www.moris.ru/~spinoza
Причиной написания данной статьи послужил, как мне кажется, наиболее эффективный на текущий день способ решения проблемы добавления собственным плагином "Поиск и Замена (S&R)" файлов на стандартную "временную панель" FAR, которым я и хочу поделиться с вами.
Немного истории
(можно абзац пропустить и не читать, тут выстраданное мною, поэтому не могу не написать :-).
С временной панелью S&R научился работать пять лет назад (в 2001 г.), функционально решение было оформлено достаточно "топорно" - в виде написания своей собственной "временной панели". Почему я называю это "топорным" - фактически все сведено к copy-and-paste исходного кода стандартной "временной панели", т.е. один и тот же одинаково работающий код загружается в память дважды. Да что там "одинаково работающий"... Со временем приходится периодически тратить время на синхронизацию своего кода с кодом стандартного плагина, чтобы не путать пользователя фактом существования двух "временных панелей" (если код синхронизировать, то пользователь как бы не замечает, что панелей две). В конце концов мне это надоело я с решил научить плагин помещать файлы на стандартную "временную панель", дабы не плодить сущности. Первое, что пришло мне в голову - делать LoadLibrary(стандартный плагин), а потом дергать его функции. Казалось бы, простое решение моментально обнаружило ряд трудностей: мне в своем плагине пришлось бы эмулировать ряд сервисных функций FAR (GetMsg, Message, Dialog, Menu), потому что они требуют в качестве параметра ModuleNumber, который в общем случае мне не известен. Таким образом, избавившись от одного дублирования кода, я автоматически получал другое, еще более сложное в реализации. Кроме того, из-за того, что стандартный плагин "временная панель" загружается FAR'ом в память по мере необходимости, а не тогда, когда нужно моему плагину, мы получали потенциальный глюкодром - в какой-то момент времени функции инициализации/деинициализации стандартной "временной панели" (SetStartupInfo, ExitFAR) могли быть вызваны дважды - моим плагином и FAR'ом, причем в заранее неизвестном порядке. В подобной ситуации в лучшем случае происходило бы "падение" FAR'а или того или иного плагина (проверять я не стал :-). В связи с изложенным, мне пришло в голову решение, излагаемое в следующем абзаце. Причем оно такое простое, что даже обидно, что я не догадался об этом пять лет назад.
Итак, не нужно загружать при помощи LoadLibrary стандартный плагин tmppanel.dll в память самому из своего плагина, нужно попросить сделать это FAR, тогда точно будет известно, что к моменту необходимости работы с "временной панелью" из своего плагина, она будет уже загружена в память и нужным образом проинициализирована. Для этого нужно, чтобы в tmppanel.dll выставлялся флаг PF_PRELOAD. В tmppanel.dll версии 1.70 build 27 (входит в релиз FAR 1.70) этот флаг не выставляется, поэтому я произвел все необходимые изменения в стандартном плагине и его версия 1.71 build 28 (ищите на официальном сайте) делает все, что нам нужно.
Вместо LoadLibrary мы получаем указатель на загруженную "временную панель" при помощи GetModuleHandle:
HMODULE hPluginDll=GetModuleHandle("TmpPanel.dll");
Соответственно, выгружать нам потом тоже ничего вручную не нужно.
Модуль tmppanel.dll экспортирует следующие функции: ClosePlugin, Configure, ExitFAR, GetFindData, GetMinFarVersion, GetOpenPluginInfo, GetPluginInfo, OpenFilePlugin, OpenPlugin, ProcessEvent, ProcessKey, PutFiles, SetDirectory, SetFindList, SetStartupInfo .
Нам потребуется задействовать все из них, за исключением SetStartupInfo, ExitFAR, GetPluginInfo, GetMinFarVersion, Configure, OpenFilePlugin, т.к. они представляют интерес только для самого FAR'а. Для получения адресов указанных функций используется GetProcAddress . У меня это выглядит приблизительно вот так:
typedef HANDLE (WINAPI *PLUGINOPENPLUGIN)(int OpenFrom,int Item);
typedef void (WINAPI *PLUGINCLOSEPLUGIN)(HANDLE hPlugin);
typedef void (WINAPI *PLUGINGETOPENPLUGININFO)(HANDLE hPlugin,
struct OpenPluginInfo *Info);
typedef int (WINAPI *PLUGINPUTFILES)(HANDLE hPlugin,struct PluginPanelItem *PanelItem,
int ItemsNumber,int Move,int OpMode);
typedef int (WINAPI *PLUGINGETFINDDATA)(HANDLE hPlugin,
struct PluginPanelItem **pPanelItem,
int *pItemsNumber,int OpMode);
typedef int (WINAPI *PLUGINSETDIRECTORY)(HANDLE hPlugin,const char *Dir,int OpMode);
typedef int (WINAPI *PLUGINSETFINDLIST)(HANDLE hPlugin,
const struct PluginPanelItem *PanelItem,
int ItemsNumber);
typedef int (WINAPI *PLUGINPROCESSKEY)(HANDLE hPlugin,int Key,
unsigned int ControlState);
typedef int (WINAPI *PLUGINPROCESSEVENT)(HANDLE hPlugin,int Event,void *Param);
struct TEMPPANEL
{
PLUGINOPENPLUGIN OpenPlugin;
PLUGINCLOSEPLUGIN ClosePlugin;
PLUGINSETFINDLIST SetFindList;
PLUGINPROCESSKEY ProcessKey;
PLUGINPROCESSEVENT ProcessEvent;
PLUGINGETOPENPLUGININFO GetOpenPluginInfo;
PLUGINGETFINDDATA GetFindData;
PLUGINSETDIRECTORY SetDirectory;
PLUGINPUTFILES PutFiles;
BOOL Init(HMODULE hDll)
{
OpenPlugin=NULL;
ClosePlugin=NULL;
SetFindList=NULL;
ProcessKey=NULL;
ProcessEvent=NULL;
GetOpenPluginInfo=NULL;
GetFindData=NULL;
SetDirectory=NULL;
PutFiles=NULL;
if(hDll)
{
OpenPlugin=reinterpret_cast(GetProcAddress(hDll,"OpenPlugin"));
ClosePlugin=reinterpret_cast(GetProcAddress(hDll,"ClosePlugin"));
SetFindList=reinterpret_cast(GetProcAddress(hDll,"SetFindList"));
ProcessKey=reinterpret_cast(GetProcAddress(hDll,"ProcessKey"));
ProcessEvent=reinterpret_cast(GetProcAddress(hDll, "ProcessEvent"));
GetOpenPluginInfo=reinterpret_cast(GetProcAddress(hDll, "GetOpenPluginInfo"));
GetFindData=reinterpret_cast(GetProcAddress(hDll, "GetFindData"));
SetDirectory=reinterpret_cast(GetProcAddress(hDll, "SetDirectory"));
PutFiles=reinterpret_cast(GetProcAddress(hDll, "PutFiles"));
}
return OpenPlugin && ClosePlugin && SetFindList && ProcessKey && ProcessEvent &&
GetOpenPluginInfo && GetFindData && SetDirectory && PutFiles;
}
};
...
HMODULE hPluginDll=GetModuleHandle("TmpPanel.dll");
// тут код проверки hPluginDll, на случай, если вызов GetModuleHandle
// окончился неудачей, вывод соответствующих сообщений и т.п.
TEMPPANEL TmpPanel;
if(TmpPanel.Init(hPluginDll))
{
// если инициализация указателей на функции успешная
}
else
{
// ...если облом
}
Чтобы добавить наши файлы на "временную панель" нужно получить указатель на эту самую панель при помощи TmpPanel.OpenPlugin, затем вызвать TmpPanel.SetFindList и, наконец, вернуть FAR'у указатель на созданную временную панель. Схематично выглядеть это будет вот так:
HANDLE WINAPI _export OpenPlugin(int OpenFrom, int Item)
{
HANDLE retCode=INVALID_HANDLE_VALUE;
...
PluginPanelItem *ppi=NULL; // тут будет перечень файлов, которые
// надо поместить на временную панель
DWORD ppi_total=0; // количество этих файлов
...
if(ppi_total)
{
// TmpPanel мы инициализировали где-то раньше!
HANDLE hTmp=TmpPanel.OpenPlugin(OPEN_FINDLIST,0);
if(INVALID_HANDLE_VALUE!=hTmp)
{
// временная панель успешно создана,
// теперь добавим в нее файлы
if(!TmpPanel.SetFindList(hTmp, ppi, ppi_total))
{
// не удалось поместить файлы на панель,
// закроем ее
TmpPanel.ClosePlugin(hTmp);
hTmp=INVALID_HANDLE_VALUE;
}
retCode=hTmp;
}
else
{
// создать временную панель не удалось
}
}
...
return retCode;
}
Ну, и наконец, следует экспортировать все прочие функции, которые которые мы цепляем из tmppanel.dll (кроме SetFindList - зачем плодить сущности?!), в противном случае, пользователь не сможет полноценно работать с "временной панелью", открытой путем вызова нашего плагина. Код экспортируемых функций простейший:
void WINAPI ClosePlugin(HANDLE hPlugin)
{
TmpPanel.ClosePlugin(hPlugin);
}
void WINAPI GetOpenPluginInfo(HANDLE hPlugin, struct OpenPluginInfo *Info)
{
TmpPanel.GetOpenPluginInfo(hPlugin, Info);
}
int WINAPI GetFindData(HANDLE hPlugin, struct PluginPanelItem **pPanelItem,
int *pItemsNumber, int OpMode)
{
return TmpPanel.GetFindData(hPlugin, pPanelItem, pItemsNumber, OpMode);
}
int WINAPI SetDirectory(HANDLE hPlugin,const char *Dir,int OpMode)
{
return TmpPanel.SetDirectory(hPlugin, Dir, OpMode);
}
int WINAPI PutFiles(HANDLE hPlugin, struct PluginPanelItem *PanelItem,
int ItemsNumber,int Move, int OpMode)
{
return TmpPanel.PutFiles(hPlugin, PanelItem, ItemsNumber, Move, OpMode);
}
int WINAPI ProcessEvent(HANDLE hPlugin, int Event, void *Param)
{
return TmpPanel.ProcessEvent(hPlugin, Event, Param);
}
int WINAPI ProcessKey(HANDLE hPlugin, int Key, unsigned int ControlState)
{
return TmpPanel.ProcessKey(hPlugin, Key, ControlState);
}
Это все, буду рад, если указанная статья поможет кому-то при написании собственного плагина.
15.04.2006
P.S. На момент написания данной статьи соответствующая версия S&R публично не доступна, т.к. находится все еще в стадии разработки. Следите за рекламой! :-)