Как написать плагин для total commander

Функциональность программ
зачастую могут улучшать не только
их непосредственные разработчики,
но и другие люди. Они, как правило,
являются пользователями тех
программ, которые хотят улучшить.
Возможно это в тех случаях, когда
программа поддерживает различные
дополнения. Дополнения эти обычно
реализуются в виде плагинов.


Что такое плагины?

Конечно, возможны всякие
вариации, но обычно под плагином
понимается какая-то динамически
компонуемая библиотека (DLL — Dynamic Link
Library) специального формата, которая
благодаря находящимся в ней
функциям расширяет возможности
«родительского» приложения.
Конечно, в широком смысле слова под
плагинами можно понимать не только
DLL’ки, а, например, и такие
комплексные вещи, как дополнения к
Mozilla Firefox. Но обычно плагин — это
именно специальная динамическая
библиотека.

Чем же эта динамическая
библиотека такая вся из себя
специальная? Всё дело в том, какие
функции она экспортирует. Чтобы
приложение могло загрузить эту
библиотеку и использовать
содержащиеся в ней функции, ему
должен быть известен их вид — в
общем-то, это всегда так с
динамической компоновкой. Поэтому
все плагины для одной программы
имеют одинаковые имена и форматы
функций, используемых в приложении.
Конечно, среди экспортируемых
функций могут быть и другие, но
тогда спрашивается, зачем они там
нужны.

Универсального формата плагинов,
который подходил бы всем
приложениям, не существует. Причина
этого проста: очень разные функции
выполняют разные программы, и было
бы странно пользоваться плагинами
к Adobe Photoshop из Sound Forge. Но, тем не
менее, свои стандарты есть, а потому
приложения, выполняющие сходные
функции, часто «понимают»
плагины своих конкурентов.
Например, среди графических
приложений стандартом де-факто
стали уже упоминавшиеся плагины к
Photoshop, а среди приложений для работы
со звуком распространён формат VST.


Какие плагины писать?

Плагин зачастую включает в себя
такое множество сложных вещей, что
его можно считать самостоятельным
программным продуктом. В первую
очередь, это относится к
профессиональным плагинам для
сложных программ — например, к тому
же 3D Studio MAX. Чтобы создавать такие
плагины, нужна не только высокая
программистская квалификация
разработчика, но и хорошие знания в
той области, в которой работает
программа. Как правило, над
графическими и звуковыми фильтрами
работают команды из нескольких
человек, и большая часть времени
уходит не на написание кода, а на
математическое моделирование
преобразований, реализуемых в
плагине.

Но для некоторых программ плагины
можно писать и не будучи докой по
части эффектов Photoshop и
гармонического анализа. Множество
энтузиастов пишет свои плагины к
популярным пользовательским
программам — таким, как известная
программа для мгновенного обмена
сообщениями Miranda. Откровенно
говоря, «Мирандой» без
плагинов вообще пользоваться
довольно-таки затруднительно.
Чтобы написать свой плагин для неё,
зачастую достаточно знаний
школьника, интересующегося
программированием, да и вообще
любого человека, для которого
программирование — не основная
работа, а просто хобби.

Чтобы продемонстрировать вам
основные принципы написания
плагинов на практике, я расскажу,
как написать собственный
полнофункциональный плагин для
популярного файлового менеджера
Total Commander. Почему именно для него?
Потому что писать к нему плагины
достаточно просто, а сам Total Commander я
считаю лучшей из программ этого
класса.


Итак, пишем?

Писать плагины можно на любом
языке программирования, код на
котором можно скомпилировать и
скомпоновать в динамическую
библиотеку. Но сейчас для простоты
и наглядности я буду пользоваться
Delphi, поскольку с этим языком может
работать практически каждый, кто в
школе научился Паскалю. Да и сам Total
Commander, как известно, написан именно
на Delphi.

Плагины к «Командиру» бывают
разные. Очень разные, я бы даже
сказал. Всего их, на сегодняшний
день, четыре типа: плагины
встроенного архиватора, плагины
встроенного просмотрщика (Lister’а),
расширения файловой системы и
контент-плагины. Плагины для
архиватора позволяют работать
через Total Commander с новыми форматами
архивов как с обычными папками,
плагины Lister’а позволяют
просматривать по нажатию на кнопку
F3 файлы новых форматов. Плагины
файловой системы позволяют
работать со структурированными
хранилищами данных как с обычными
каталогами и файлами, лежащими на
диске. В принципе, они чем-то похожи
на архивные плагины.
Контент-плагины появились в Total
Commander’е сравнительно недавно,
начиная с версии 6.50 (на момент
написания статьи самой новой была
версия 7.01). Они позволяют
отображать дополнительную
информацию о разных файлах в
главном окне программы.

Естественно, разные виды плагинов
имеют разные принципы работы и
разную внутреннюю структуру.
Поэтому для дальнейших действий
стоит определиться с тем, какой
именно плагин мы с вами будем
писать. Все плагины хороши, но лично
мне кажется, что лучше всего
показать на примере плагин для
Lister’а, потому что всех форматов
файлов не перечислишь, да и новые
постоянно появляются. В общем,
написание плагинов к Lister’у —
полезный практический навык,
который может пригодиться в жизни.

Чтобы особо не мучиться с идеей
для нашего плагина, будем писать
плагин для показа RTF-файлов. Хоть
«Командир» это умеет и без
того, сейчас наша цель — научиться, а
не сотворить нечто поражающее
воображение.


Итак, пишем!

Для того, чтобы писать плагин,
нужна сначала некоторая
предварительная подготовка. Для
начала, наверное, неплохо бы
установить Delphi. Если она уже
установлена, то удалять и ставить
по-новому не надо. Кроме самой Delphi,
нужны заголовочные файлы, чтобы
экспортировать правильные функции
и найти, таким образом, общий язык с
Total Commander’ом. Скачать их нужно с
сайта Total Commander’а (ghisler.com/plugins.htm),
называется это «LS-Plugin writer’s guide».
Прямая ссылка такая: ghisler.fileburst.com/lsplugins/listplughelp1.5.zip,
но она, как видите, содержит в себе
номер версии, который может
измениться.

Ну вот, если вы всё скачали, то
можно переходить непосредственно к
действиям. Запустите среду Delphi и в
окне создания нового проекта
выберите «DLL» (в некоторых
версиях Delphi это называется «DLL
Wizard», но это не принципиально:
главная идея такова, что проект
должен быть проектом динамически
компонуемой библиотеки).

Один небольшой нюанс: поскольку
типов плагинов к TC несколько, им
всем принято давать разные
расширения. Плагины для Lister’а
традиционно имеют расширение WLX.
Поэтому лучше заранее сменить
расширение выходного файла в
настройках проекта.

В разделе uses главного файла
проекта нужно добавить модули Windows,
поскольку нам понадобятся
некоторые типы данных оттуда для
экспорта функций нашим плагином.
Всего же функций, которые нам
необходимо экспортировать, три. Вот
как они выглядят:

function ListLoad(ParentWin: thandle; FileToLoad: pchar;
 ShowFlags: integer): thandle; stdcall;
procedure ListCloseWindow(ListWin: thandle); stdcall;
procedure ListGetDetectString(DetectString: pchar;
maxlen: integer); stdcall;

Первая из них вызывает плагин для
работы. Её параметры — это
дескриптор окна Lister’а (с его помощью
мы будем поддерживать связь между
окном плагина и окном Lister’а), имя
загружаемого файла и комбинацию
настроек Lister’а. Но настройки нам
пока что не понадобятся, так что на
ShowFlags сейчас можно не обращать
внимания. Вторая функция (вернее, в
терминах Delphi это процедура)
вызывается, когда завершается
работа приложения, и требуется
закрытие окна. Её параметр — тоже
дескриптор окна Lister’а. Третья
функция позволяет Lister’у определить,
может ли плагин правильно
разобраться с переданным ему
файлом, или лучше его отобразить с
помощью какого-то другого плагина.
В общем-то, можно обойтись и без
второй по счёту функции, поскольку
в случае её отсутствия окно плагина
будет само уничтожено стандартными
средствами Windows API.

Добавьте в проект форму
(«File»->»New»->Form) и удалите
из файла формы глобальную
переменную Form1: TForm1. Особенности
написания DLL на Delphi таковы, что
глобальные переменные во время
этого процесса под запретом. На
форму положите компонент RichEdit (он
находится на вкладке «Win32»
палитры компонентов Delphi).
Установите его свойство Align на alClient
(для этого нужно использовать окно
Object Inspector, находящееся в левой части
среды).

Окно нашего плагина не должно
иметь рамки и заголовка, иначе окно
Lister’а будет выглядеть, мягко говоря,
странновато. Поэтому для создания
окна мы должны переопределить одну
процедуру. В секции Protected класса
окна нужно записать следующее:

procedure CreateParams(var Params: TCreateParams); override;

В теле процедуры пишем следующее:

inherited CreateParams(Params);
Params.Style := (WS_CHILD or WS_MAXIMIZE) and
not WS_CAPTION and not WS_BORDER;
Params.WindowClass.cbWndExtra := SizeOf(Pointer);

Первая строчка вызывает
унаследованный стандартный метод.
Вторая обрезает рамку окна, чтобы
оно не выглядывало из окна Lister’а
неподобающим образом. Третья
резервирует место для того, чтобы
мы могли потом уничтожить окно
(глобальными переменными
пользоваться нельзя — помните?).

Теперь можно перейти к
непосредственному заполнению тела
экспортируемых функций. Тело
процедуры ListDetectString будет коротким:

StrLCopy(DetectString, 'FORCE | EXT="RTF"', MaxLen);

Эта функция копирует строку
«FORCE | EXT=»RTF»» в переменную
DetectString. А строка сообщает Lister’у, что
мы будем открывать своим плагином
файлы с расширением «RTF» и при
этом переопределяем стандартную
функциональность просмотрщика.

Тело ListLoad будет уже гораздо более
длинным:

Var Form1: TForm1;
begin
 Result := 0;
 try
  if LowerCase(ExtractFileExt(string(FileToLoad))) <> '.rtf'
   then Exit;
  Form1 := TForm1.CreateParented(ListerWin);
  Form1.RichEdit1.Lines.LoadFromFile(string(FileToLoad));
  Form1.Show;
  SetWindowLong(Form1.Handle, GWL_USERDATA, Integer(@Form1));
  PostMessage(Form1.Handle, WM_SETFOCUS, 0, 0);
  Form1.RichEdit1.SetFocus;
  Result := Form1.Handle;
 except
 end;
end;

Переменная Form1 — это и есть,
собственно, окно плагина. Мы
проверяем расширение подсунутого
плагину файла, загружаем его,
показываем окно, сохраняем
указатель на него, чтобы при случае
изничтожить. Потом устанавливаем
фокус на наш RichEdit и возвращаем
дескриптор окна плагина. Поскольку
код находится в DLL-библиотеку,
помещаем всё внутрь блока try…except,
чтобы сбой плагина не привёл к сбою
«командира».

Код ListerCloseWindow таков:

Form1 := Pointer(GetWindowLong(PluginWin, GWL_USERDATA));
Form1.Close;
Form1.Free;

Получаем сохранённый адрес формы,
закрываем её и освобождаем память.
Нужно только не забыть объявить
переменную Form1 и «обернуть»
всё, что происходит внутри
процедуры try…except’ом.

Ну вот, осталось только
откомпилировать плагин и
установить его в Total Commander’е. По идее,
никаких сложностей с этим быть не
должно. Если вы заинтересовались
написанием плагинов к TC, то зайдите
на сайт русскоязычного сообщества
пользователей этой программы wincmd.ru. В разделе
«Статьи» вы найдёте материалы
по написанию плагинов на Delphi и C++, а
в разделе закачек есть
полнофункциональные плагины с
открытым исходным кодом.

Вадим СТАНКЕВИЧ

  • Download binary — 1.07 MB
  • Download source — 2.3 MB

Introduction

Windows Explorer is not enough. People who have the same feeling as me can choose of several alternative file managers. One of the most popular is Total Commander — a shareware file manager for Microsoft Windows. It has the classic two-panes view known from Norton Commander from old DOS times, and supports many file operations. One of the important features of Total Commander is the ability to be extended via plug-ins. This article describes how to write a Total Commander plug-in in a managed language (Visual Basic or C#), in general, and with special focus on File System plug-ins (WFX).

Plug-in architecture

Total Commander itself is written in the Delphi (Object Pascal) language (unmanaged). The Delphi compiler produces C-style executables and libraries. The plug-in interface is designed for plug-ins written in C/C++. The author provides C header files for plug-in authors. There are four plug-in types:

Content plug-in (WDX)
Allows Total Commander to show additional details of files (like ID3 track name, or Exif camera model) and use them for search or renaming.
File System plug-in (WFX)
Allows Total Commander to access other file systems like Linux-formatted partitions, device file systems, or FTP servers.
Lister plug-ins (WLX)
Allows Total Commander to show contents of files of various types (like MP3, HTML etc.).
Packer plug-ins (WCX)
Allows Total Commander to access — show, extract, pack, and manipulate contents of various types of archives (like CAB or MSI).

Each plug-in is created as a C DLL library which exports a defined set of functions. The library has the extensions wdx/wfx/wlx/wcx instead of DLL depending on which type of plug-in it represents. The set of functions that a plug-in must export is defined by the Total Commander plug-in interface. There are only a few compulsory functions for each type of plug-in, and then there are plenty of optional functions. The compulsory functions provide the very basic necessary functionality of a plug-in. For a file system plug-in, only four functions providing initialization and the list of files and directories are compulsory. Then, there are optional functions for downloading, uploading, deleting, renaming etc. Of course, there are no limitations on the Total-Commander-unrecognized functions a plug-in can export. As newer and newer versions of Total Commander are developed, the set of supported plug-in functions grows. So, when using a plug-in designed for a newer version of TC with an older one, some functions are never called.

Writing a Total Commander plug-in in C++ is an easy task. It’s an easy task in any language that can export functions in a C-like way, like Delphi or PowerBasic. But, managed .NET languages (like Visual Basic or C#) cannot export functions this way. .NET can export COM objects, but not those Win32-API-like functions, even though .NET can import them using DllImportAttribute and Visual Basic has a special syntax for importing DLL functions. As far as I know, it is even technically impossible to export those functions from a managed assembly because .NET does not provide a way to place functions on a static address — .NET functions get their addresses when they are JIT-compiled. Another limitation, especially for Visual Basic, is the lack of support for pointers widely used in TC plug-in functions. OK, the simple answer to the question, «Can I write a Total Commander plug-in in Visual Basic (C#)?» is «No». As you probably guessed, «No» is not the answer I put up with.

There is one special language in .NET that can combine managed and unmanaged code in one assembly, that can export Win32-style functions, that can define global functions, and that can work with pointers — it’s C++/CLI. So, my solution to write a managed Total Commander plug-in is:

  1. Write an interface between Total Commander and the managed code in C++/CLI.
  2. Write the plug-in in any managed language.

The C++/CLI interface is called by the Total Commander, and it converts all the ugly data types from C++ like char* to nice managed types like String. The managed plug-in, then, does its work and returns what it should return. The C++/CLI interface converts the managed return value to an unmanaged one, and passes it back to Total Commander in the requested fashion.

If an interface is implemented simply as written above, you must write (copy and paste) the interface again and again for each plug-in you’ll write. What I wanted was some general purpose solution. So, I’ve created a C++/CLI assembly that contains some support classes and structures for passing data between managed and unmanaged code, and above all, it contains the plug-in base class the actual plug-in implementations are derived from. So, the way the plug-in is implemented is fully object oriented as it is common in .NET. The basic parts of the Total Commander plug-in managed framework are:

C++/CLI unmanaged ↔ managed interface (the Tools.TotalCommander assembly)
The plug-in base class and the support classes and structures are contained in this assembly.
Plug-in implementation
The managed assembly (DLL) that implements the plug-in. It can even implement more than one plug-in or plug-ins of different types. It can be written in any managed language that can derive from the plug-in base class.
Plug-in assembly
A small assembly written in C++/CLI. This assembly represents the plug-in from the Total Commander point of view. It has the required extension (wdx/wfx/wlx/wcx), and it exports all the necessary functions. It initializes the plug-in instance, and then it simply calls the plug-in instance function whenever Total Commander calls the plug-in function. This assembly is generated by the Total Commander Plug-in Builder from the plug-in implementation.
Total Commander Plug-in Builder
The command line tool that builds the plug-in assembly from the predefined template using information from the plug-in implementation. Especially, it ensures that the right type of plug-in is built and that only functions implemented by the plug-in-implementing class are exported by the plug-in library.

Interface between Total Commander and managed code

As written above, this assembly is written in C++/CLI, and performs marshaling between unmanaged (Total Commander) and managed (plug-in implementation) code. It contains some support classes and structures, some of them are visible from managed code. It also defines several attributes used to specify how the Plug-in Builder builds the plug-in assembly. In fact, this assembly contains only a very little portion of the unmanaged code — only the definition of the unmanaged structures imported from Christian-Ghisler-provided header files (and those header files include a few Windows SDK header files). But, the code in this assembly deals with those unmanaged types and with pointers and C++-like strings (char*). It’s something we avoid in C# and can’t do in Visual Basic.

The plug-in (abstract) base class simply contains non-virtual (not overridable in VB) functions accepting and returning unmanaged types, and then it contains virtual (overridable in VB) functions overridden by the actual plug-in implementation. Virtual functions accept and return managed and CLS-compliant types. Non-virtual functions convert the parameters from unmanaged to managed types, and pass them to the virtual function. When the virtual function returns, its return value (and out parameter values) is converted from managed to unmanaged and passed to the caller. The caller is actually a global function in the plug-in assembly which passes the values back to the Total Commander. A virtual function differs in behavior from a non-virtual one. For example, exceptions are used instead of error return codes, and return values instead of output parameters (sometimes, output parameters cannot be avoided — multiple return values).

In a file system plug-in, the mapping between non-virtual and virtual functions is usually 1:1. For almost each non-virtual function (starting with the FS prefix), a corresponding virtual function exists. Virtual functions for compulsory functions have no implementation, which effectively makes the plug-in author to implement them in the derived class. Optional functions have default implementations throwing NotSupportedException. It is ensured that the Total Commander never calls an optional function which is not overridden in the plug-in class because such a function is not exported by the plug-in assembly (the Total Commander Plug-in Builder does not generate exports for them). From an object oriented point of view, it can be determined that the actual implementation of an optional function does nothing but throws NotSupportedException by the MethodNotSupportedAttribute applied on the method.

Sometimes, the interface provides a little bit higher level of abstraction than the unmanaged Total Commander plug-in interface. It does not use handles, but actual objects — bitmaps and icons.

The plugin assembly

The plug-in assembly contains code responsible for creating an instance of a plug-in, and it actually exports the functions to unmanaged environments and passes function calls from the Total Commander to the plug-in abstract base class. As the plug-in assembly is generated by the Total Commander Plug-in Builder from a template using information from the plug-in implementation, it is customized for the actual plug-in it represents — and it always represents only one plug-in. In case the plug-in implementation assembly contains more plug-ins, more plug-in assembles are generated.

The most tricky part of the work in the plug-in assembly is the assembly binding. The plug-in assembly has references to Tools.TotalCommander and to the plug-in implementing assembly. Tools.TotalCommander refers to Tools, and the plug-in implementing the assembly may refer to any assembly — locally copied or GAC. Total Commander plug-ins usually reside in subfolders of the subfolder plugins of the Total Commander installation folder. And now, problems arise: the plug-in assembly is loaded to the totalcmd.exe process. Totalcmd.exe resides two or more folders above the plug-in assembly. The plug-in assembly refers to the plug-in implementation assembly and Tools.TotalCommander, neither of them is in GAC. By default, .NET looks for references in the same directory as the process was started in. Subfolders are not examined. So, references are not found and the plug-in crashes. Total Commander can recover from it, but the plug-in is not loaded and it does not work. The only assembly that is correctly loaded is the plug-in assembly, because it is loaded as part of something that seems to be an unmanaged Win32-API-style DLL. So, the plug-in assembly must ensure that references will be searched where they lie. We can solve the issue in several ways:

Place all the assemblies in the same directory as totalcmd.exe
It is not a good idea as many, possibly conflicting, files will be in the Total Commander installation directory. This may lead to chaos.
Place all the necessary assemblies in GAC
This is also not a very good solution. First, it puts additional demands to the plug-in installation process and requires administrator privileges. It prevents the plug-in from being carried with Total Commander when it is installed on a flash drive. And, single-purpose assemblies as the Total Commander plug-in implementation assembly should not be in the GAC.
Intercept assembly resolution
We can handle the event AppDomain.AssemblyResolve which is raised when assembly resolution fails. The handler of this event can load the assembly and return it. Problems will arise when multiple managed plug-ins are used with Total Commander. Plug-ins can use different versions of Tools.TotalCommander, or plug-ins need not be based on this framework. This assembly resolution can effectively destroy other managed plug-ins.
Load each plug-in into a separate application domain
This is the way I’ve finally chosen. Only the plug-in assembly is loaded to the default domain. It creates another domain and sets its base directory to the directory it is located in. Then, it creates an instance of the helper class in the newly created application domain. The class creates an instance of the plug-in class — it is resolved correctly, because all references are in its base directory. Assemblies loaded by the plug-in does not interfere with other plug-ins because application domains are separated. There is only some overhead because TC calls the global function in the plug-in assembly, it calls the function in the assembly domain helper, it calls the function in the plug-in helper, it calls non-virtual functions in the plug-in class, and it finally calls the virtual function in the plug-in class.

A command line tool that creates a plug-in assembly for each plug-in class in the plug-in implementation assembly. It can be invoked from the command line, or it can be used programmatically. It needs access to the C++/CLI compiler, vcbuild.exe. It is written in Visual Basic. The best way of using the Total Commander Plugin Builder is to have it in the post-build event in Visual Studio.

It enumerates all types in the plug-in implementation assembly, and for those that represent Total Commander, the plug-in generates a plug-in assembly. While generating a plug-in assembly, the plug-in class is examined to determine which plug-inplugin functions are implemented (overridden) by the plug-in class. Methods that are not implemented are not generated in the plug-in assembly. It is achieved simply by writing several C++ preprocessor #defines to control how the plug-in assembly will be compiled. Same way, the name of the class to create an instance of it is specified. A reference to the plug-in implementation assembly is set by the #using C++ directive. the Total Commander Plug-in Builder also examines certain attributes of the plug-in implementation assembly and the plug-in class to set the plug-in assembly attributes and to refine the generation behavior.

The code

OK, I’m not gonna re-type all the code here. Download the attached example. Only a few interesting parts of the code:

Creation of the application domain

The following code snippet in C++/CLI shows how the application domain is created:

namespace Tools{namespace TotalCommanderT{
    extern bool RequireInitialize;
    extern gcroot<AppDomainHolder^> holder;
        PluginInstanceHolder::PluginInstanceHolder(){
        this->instance = TC_WFX;
                    }
        AppDomainHolder::AppDomainHolder(){
        this->holder = gcnew PluginInstanceHolder();
    }
                void Initialize(){
        if(!RequireInitialize) return;
        RequireInitialize = false;
        PluginSelfAssemblyResolver::Setup();
        AppDomainSetup^ setup = gcnew AppDomainSetup();
        Assembly^ currentAssembly = Assembly::GetExecutingAssembly();
        setup->ApplicationBase = IO::Path::GetDirectoryName(currentAssembly->Location);
        AppDomain^ pluginDomain = AppDomain::CreateDomain(PLUGIN_NAME,nullptr,setup);
        AppDomainHolder^ iholder = 
          (AppDomainHolder^)pluginDomain->CreateInstanceFromAndUnwrap(
              currentAssembly->CodeBase,AppDomainHolder::typeid->FullName);
        Tools::TotalCommanderT::holder = iholder;
    }
}}

PluginSelfAssemblyResolver is a simple helper class that allows the resolution of the assembly itself when it cannot be found by .NET. It contains only two functions:

namespace Tools{namespace TotalCommanderT{
    Assembly^ PluginSelfAssemblyResolver::OnResolveAssembly(Object^ sender, 
                                          ResolveEventArgs^ args){
        AssemblyName^ name = gcnew AssemblyName(args->Name);
        if(AssemblyName::ReferenceMatchesDefinition(name, 
                thisAssembly->GetName())) return thisAssembly;
        else return nullptr;
    }
    inline void PluginSelfAssemblyResolver::Setup(){
        AppDomain::CurrentDomain->AssemblyResolve += 
          gcnew ResolveEventHandler( PluginSelfAssemblyResolver::OnResolveAssembly );
    }
}}

Definition of plug-in functions

Optional as well as compulsory Total Commander plug-in functions are wrapped in #ifdef#endif blocks. The corresponding #defines for those blocks are written by the Total Commander Plug-in Builder to the define.h file. Due to the architecture using the application domains, those functions are in the plug-in assembly three times with identical signature and similar body; I’ve extracted them to a separate file, wfxFunctionCalls.h. This file is included at three different places with several C++ preprocessor #defines to control how it is compiled. Each function is defined like this:

#ifdef TC_FS_INIT
    TCPLUGF int FUNC_MODIF FsInit(int PluginNr,tProgressProc pProgressProc, 
                tLogProc pLogProc,tRequestProc pRequestProc){
        return FUNCTION_TARGET->FsInit(PluginNr,pProgressProc,pLogProc,pRequestProc);
    }
#endif

TCPLUGF is defined as empty (not used). Once I thought about using __declspec(dllexport) to export functions. Lately, I’ve switched to a separate Exports.def file. FUNC_MODIF is either __stdcall or the class name (AppDomainHolder:: or PluginInstanceHolder::). __stdcall is used for exported functions; internal calls use the managed calling convention. Finally, FUNCTION_TARGET is the instance to call the function on. It is Tools::TotalCommanderT::holder in exported (global) functions, this->holder in AppDomainHolder, and this->instance in PluginInstanceHolder.

WfxFunctionCalls.h is included like this:

#define TCPLUGF
#define FUNC_MODIF AppDomainHolder::
#define FUNCTION_TARGET this->holder
#include "FunctionCalls.h"

I know, it may be more comprehensible typing it thrice. But, so many functions are typed thrice — I’m really lazy, and besides, when some change is needed, it needs to be done only once (in the plug-in assembly template, then in the plug-in base class and in the common header file, and …).

Marshalling

Marshalling from unmanaged to managed code is quite simple. The Total Commander plug-in interface uses several structures and constants. For structures, I’ve created managed counterparts, and before passing an object to managed code, the structure is converted to a managed one. Before a structure is returned to unmanaged code, it is converted back. Constant values are represented by managed enumeration values, and are simply cast. Something very often passed between the Total Commander and a plug-in are strings. Total Commander passes and accepts strings as char* (sometimes, char[MAX_PATH]) — always null-terminated. Marshaling those values from unmanaged to managed code is easy, because System.String has a constructor that accepts char* (System.SByte*).

Note: In the current version, Total Commander neither passes to plug-ins nor accepts from them Unicode strings (wchar_t*) although a few Win32 API structures used by Total Commander are declared as Unicode. Total Commander uses the current system encoding. Unicode support will be available in one of future versions of Total Commander. This is not a limitation of my framework but of Total Commander itself.

Passing a string to unmanaged code is a little more tricky. It is possible to enumerate all the characters of a string easily in .NET. But, those characters are Unicode code points. They must be converted to default system encoding values. Finally, I’ve created my own StringCopy functions:

namespace Tools{namespace TotalCommanderT{
    void StringCopy(String^ source, char* target, int maxlen){
        if(source == nullptr)
            target[0]=0;
        else{
            System::Text::Encoding^ enc = System::Text::Encoding::Default;
            cli::array<unsigned char>^ bytes = enc->GetBytes(source);
            for(int i = 0; i < bytes->Length && i < maxlen-1; i++)
                target[i]= bytes[i];
            target[source->Length > maxlen-1 ? maxlen-1 : source->Length] = 0;
        }
    }
    void StringCopy(String^ source, wchar_t* target, int maxlen){
        StringCopy(source,(char*)(void*)target,maxlen);
    }
}}

The second function simply treats a wchar_t* as char*, see note above.

The function encodes a string using the default system encoding and then copies the encoded bytes to an unmanaged buffer (maximally, maxlen - 1 characters). The character after the last used character is set to nullchar.

Note: I’m not sure if the default encoding will behave correctly in systems where the default encoding is multibyte (e.g., Chinese). I hope for Unicode implementations in future version of TC.

Sample plug-in

The sample plug-in is the simplest that can be written. It simply accesses the local file system. Christian Ghisler provides such an example plug-in in C++. Mine is written in Visual Basic. It shows how to utilize the Managed Total Commander Plug-in Framework.

Notes

  • Both, Tools.TotalCommander and the sample plug-in uses (not very extensively) my open source library Tools. It can be downloaded from codeplex.com/Tools and also the latest version of the plug-in framework.
  • Edit the Post-Build event and the debug command line of the wfx sample project to reflect the actual location of the Total Commander before building. You need to run Visual Studio with elevated privileges on Vista, or have write rights to the target directory (C:Program FilestotalcmdpluginsTestwfx sampleDebug).
  • The example source code is only example source code. The project has commented out several pre-build and post-build events needed when larger modifications are done! Uncommenting the events requires several utilities to perform the events. Download the full source from Codeplex.

Known issues

  • The current version of the framework supports only file system plug-ins (WFX). Other types of plug-ins will be supported in future.
  • The current version of framework does not support Unicode. This is due to the Total Commander limitation for plug-ins. It will be hopefully removed in one of the near-future versions of Total Commander.
  • The only way the sample plug-in can access UNC paths is when the user manually changes to a UNC directory from the Total Commander command line.
  • The properties window of a file/folder in the sample plugin is not modal to the Total Commander main window. This is not a limitation of the plug-in framework. I use Win32 API to show the properties dialog, and I have no idea how to make it modal.

License

The plug-in framework, the plug-in builder, the sample plug-in, as well as any other code in this article is released under the Open Source license at codeplex.com/Tools.

History

  • 2009-03-08 — Initial release.
  • 2009-03-08 — Updated source (missing statement in AssemblyResolver.cpp).

This member has not yet provided a Biography. Assume it’s interesting and varied, and probably something to do with programming.

  • Download binary — 1.07 MB
  • Download source — 2.3 MB

Introduction

Windows Explorer is not enough. People who have the same feeling as me can choose of several alternative file managers. One of the most popular is Total Commander — a shareware file manager for Microsoft Windows. It has the classic two-panes view known from Norton Commander from old DOS times, and supports many file operations. One of the important features of Total Commander is the ability to be extended via plug-ins. This article describes how to write a Total Commander plug-in in a managed language (Visual Basic or C#), in general, and with special focus on File System plug-ins (WFX).

Plug-in architecture

Total Commander itself is written in the Delphi (Object Pascal) language (unmanaged). The Delphi compiler produces C-style executables and libraries. The plug-in interface is designed for plug-ins written in C/C++. The author provides C header files for plug-in authors. There are four plug-in types:

Content plug-in (WDX)
Allows Total Commander to show additional details of files (like ID3 track name, or Exif camera model) and use them for search or renaming.
File System plug-in (WFX)
Allows Total Commander to access other file systems like Linux-formatted partitions, device file systems, or FTP servers.
Lister plug-ins (WLX)
Allows Total Commander to show contents of files of various types (like MP3, HTML etc.).
Packer plug-ins (WCX)
Allows Total Commander to access — show, extract, pack, and manipulate contents of various types of archives (like CAB or MSI).

Each plug-in is created as a C DLL library which exports a defined set of functions. The library has the extensions wdx/wfx/wlx/wcx instead of DLL depending on which type of plug-in it represents. The set of functions that a plug-in must export is defined by the Total Commander plug-in interface. There are only a few compulsory functions for each type of plug-in, and then there are plenty of optional functions. The compulsory functions provide the very basic necessary functionality of a plug-in. For a file system plug-in, only four functions providing initialization and the list of files and directories are compulsory. Then, there are optional functions for downloading, uploading, deleting, renaming etc. Of course, there are no limitations on the Total-Commander-unrecognized functions a plug-in can export. As newer and newer versions of Total Commander are developed, the set of supported plug-in functions grows. So, when using a plug-in designed for a newer version of TC with an older one, some functions are never called.

Writing a Total Commander plug-in in C++ is an easy task. It’s an easy task in any language that can export functions in a C-like way, like Delphi or PowerBasic. But, managed .NET languages (like Visual Basic or C#) cannot export functions this way. .NET can export COM objects, but not those Win32-API-like functions, even though .NET can import them using DllImportAttribute and Visual Basic has a special syntax for importing DLL functions. As far as I know, it is even technically impossible to export those functions from a managed assembly because .NET does not provide a way to place functions on a static address — .NET functions get their addresses when they are JIT-compiled. Another limitation, especially for Visual Basic, is the lack of support for pointers widely used in TC plug-in functions. OK, the simple answer to the question, «Can I write a Total Commander plug-in in Visual Basic (C#)?» is «No». As you probably guessed, «No» is not the answer I put up with.

There is one special language in .NET that can combine managed and unmanaged code in one assembly, that can export Win32-style functions, that can define global functions, and that can work with pointers — it’s C++/CLI. So, my solution to write a managed Total Commander plug-in is:

  1. Write an interface between Total Commander and the managed code in C++/CLI.
  2. Write the plug-in in any managed language.

The C++/CLI interface is called by the Total Commander, and it converts all the ugly data types from C++ like char* to nice managed types like String. The managed plug-in, then, does its work and returns what it should return. The C++/CLI interface converts the managed return value to an unmanaged one, and passes it back to Total Commander in the requested fashion.

If an interface is implemented simply as written above, you must write (copy and paste) the interface again and again for each plug-in you’ll write. What I wanted was some general purpose solution. So, I’ve created a C++/CLI assembly that contains some support classes and structures for passing data between managed and unmanaged code, and above all, it contains the plug-in base class the actual plug-in implementations are derived from. So, the way the plug-in is implemented is fully object oriented as it is common in .NET. The basic parts of the Total Commander plug-in managed framework are:

C++/CLI unmanaged ↔ managed interface (the Tools.TotalCommander assembly)
The plug-in base class and the support classes and structures are contained in this assembly.
Plug-in implementation
The managed assembly (DLL) that implements the plug-in. It can even implement more than one plug-in or plug-ins of different types. It can be written in any managed language that can derive from the plug-in base class.
Plug-in assembly
A small assembly written in C++/CLI. This assembly represents the plug-in from the Total Commander point of view. It has the required extension (wdx/wfx/wlx/wcx), and it exports all the necessary functions. It initializes the plug-in instance, and then it simply calls the plug-in instance function whenever Total Commander calls the plug-in function. This assembly is generated by the Total Commander Plug-in Builder from the plug-in implementation.
Total Commander Plug-in Builder
The command line tool that builds the plug-in assembly from the predefined template using information from the plug-in implementation. Especially, it ensures that the right type of plug-in is built and that only functions implemented by the plug-in-implementing class are exported by the plug-in library.

Interface between Total Commander and managed code

As written above, this assembly is written in C++/CLI, and performs marshaling between unmanaged (Total Commander) and managed (plug-in implementation) code. It contains some support classes and structures, some of them are visible from managed code. It also defines several attributes used to specify how the Plug-in Builder builds the plug-in assembly. In fact, this assembly contains only a very little portion of the unmanaged code — only the definition of the unmanaged structures imported from Christian-Ghisler-provided header files (and those header files include a few Windows SDK header files). But, the code in this assembly deals with those unmanaged types and with pointers and C++-like strings (char*). It’s something we avoid in C# and can’t do in Visual Basic.

The plug-in (abstract) base class simply contains non-virtual (not overridable in VB) functions accepting and returning unmanaged types, and then it contains virtual (overridable in VB) functions overridden by the actual plug-in implementation. Virtual functions accept and return managed and CLS-compliant types. Non-virtual functions convert the parameters from unmanaged to managed types, and pass them to the virtual function. When the virtual function returns, its return value (and out parameter values) is converted from managed to unmanaged and passed to the caller. The caller is actually a global function in the plug-in assembly which passes the values back to the Total Commander. A virtual function differs in behavior from a non-virtual one. For example, exceptions are used instead of error return codes, and return values instead of output parameters (sometimes, output parameters cannot be avoided — multiple return values).

In a file system plug-in, the mapping between non-virtual and virtual functions is usually 1:1. For almost each non-virtual function (starting with the FS prefix), a corresponding virtual function exists. Virtual functions for compulsory functions have no implementation, which effectively makes the plug-in author to implement them in the derived class. Optional functions have default implementations throwing NotSupportedException. It is ensured that the Total Commander never calls an optional function which is not overridden in the plug-in class because such a function is not exported by the plug-in assembly (the Total Commander Plug-in Builder does not generate exports for them). From an object oriented point of view, it can be determined that the actual implementation of an optional function does nothing but throws NotSupportedException by the MethodNotSupportedAttribute applied on the method.

Sometimes, the interface provides a little bit higher level of abstraction than the unmanaged Total Commander plug-in interface. It does not use handles, but actual objects — bitmaps and icons.

The plugin assembly

The plug-in assembly contains code responsible for creating an instance of a plug-in, and it actually exports the functions to unmanaged environments and passes function calls from the Total Commander to the plug-in abstract base class. As the plug-in assembly is generated by the Total Commander Plug-in Builder from a template using information from the plug-in implementation, it is customized for the actual plug-in it represents — and it always represents only one plug-in. In case the plug-in implementation assembly contains more plug-ins, more plug-in assembles are generated.

The most tricky part of the work in the plug-in assembly is the assembly binding. The plug-in assembly has references to Tools.TotalCommander and to the plug-in implementing assembly. Tools.TotalCommander refers to Tools, and the plug-in implementing the assembly may refer to any assembly — locally copied or GAC. Total Commander plug-ins usually reside in subfolders of the subfolder plugins of the Total Commander installation folder. And now, problems arise: the plug-in assembly is loaded to the totalcmd.exe process. Totalcmd.exe resides two or more folders above the plug-in assembly. The plug-in assembly refers to the plug-in implementation assembly and Tools.TotalCommander, neither of them is in GAC. By default, .NET looks for references in the same directory as the process was started in. Subfolders are not examined. So, references are not found and the plug-in crashes. Total Commander can recover from it, but the plug-in is not loaded and it does not work. The only assembly that is correctly loaded is the plug-in assembly, because it is loaded as part of something that seems to be an unmanaged Win32-API-style DLL. So, the plug-in assembly must ensure that references will be searched where they lie. We can solve the issue in several ways:

Place all the assemblies in the same directory as totalcmd.exe
It is not a good idea as many, possibly conflicting, files will be in the Total Commander installation directory. This may lead to chaos.
Place all the necessary assemblies in GAC
This is also not a very good solution. First, it puts additional demands to the plug-in installation process and requires administrator privileges. It prevents the plug-in from being carried with Total Commander when it is installed on a flash drive. And, single-purpose assemblies as the Total Commander plug-in implementation assembly should not be in the GAC.
Intercept assembly resolution
We can handle the event AppDomain.AssemblyResolve which is raised when assembly resolution fails. The handler of this event can load the assembly and return it. Problems will arise when multiple managed plug-ins are used with Total Commander. Plug-ins can use different versions of Tools.TotalCommander, or plug-ins need not be based on this framework. This assembly resolution can effectively destroy other managed plug-ins.
Load each plug-in into a separate application domain
This is the way I’ve finally chosen. Only the plug-in assembly is loaded to the default domain. It creates another domain and sets its base directory to the directory it is located in. Then, it creates an instance of the helper class in the newly created application domain. The class creates an instance of the plug-in class — it is resolved correctly, because all references are in its base directory. Assemblies loaded by the plug-in does not interfere with other plug-ins because application domains are separated. There is only some overhead because TC calls the global function in the plug-in assembly, it calls the function in the assembly domain helper, it calls the function in the plug-in helper, it calls non-virtual functions in the plug-in class, and it finally calls the virtual function in the plug-in class.

A command line tool that creates a plug-in assembly for each plug-in class in the plug-in implementation assembly. It can be invoked from the command line, or it can be used programmatically. It needs access to the C++/CLI compiler, vcbuild.exe. It is written in Visual Basic. The best way of using the Total Commander Plugin Builder is to have it in the post-build event in Visual Studio.

It enumerates all types in the plug-in implementation assembly, and for those that represent Total Commander, the plug-in generates a plug-in assembly. While generating a plug-in assembly, the plug-in class is examined to determine which plug-inplugin functions are implemented (overridden) by the plug-in class. Methods that are not implemented are not generated in the plug-in assembly. It is achieved simply by writing several C++ preprocessor #defines to control how the plug-in assembly will be compiled. Same way, the name of the class to create an instance of it is specified. A reference to the plug-in implementation assembly is set by the #using C++ directive. the Total Commander Plug-in Builder also examines certain attributes of the plug-in implementation assembly and the plug-in class to set the plug-in assembly attributes and to refine the generation behavior.

The code

OK, I’m not gonna re-type all the code here. Download the attached example. Only a few interesting parts of the code:

Creation of the application domain

The following code snippet in C++/CLI shows how the application domain is created:

namespace Tools{namespace TotalCommanderT{
    extern bool RequireInitialize;
    extern gcroot<AppDomainHolder^> holder;
        PluginInstanceHolder::PluginInstanceHolder(){
        this->instance = TC_WFX;
                    }
        AppDomainHolder::AppDomainHolder(){
        this->holder = gcnew PluginInstanceHolder();
    }
                void Initialize(){
        if(!RequireInitialize) return;
        RequireInitialize = false;
        PluginSelfAssemblyResolver::Setup();
        AppDomainSetup^ setup = gcnew AppDomainSetup();
        Assembly^ currentAssembly = Assembly::GetExecutingAssembly();
        setup->ApplicationBase = IO::Path::GetDirectoryName(currentAssembly->Location);
        AppDomain^ pluginDomain = AppDomain::CreateDomain(PLUGIN_NAME,nullptr,setup);
        AppDomainHolder^ iholder = 
          (AppDomainHolder^)pluginDomain->CreateInstanceFromAndUnwrap(
              currentAssembly->CodeBase,AppDomainHolder::typeid->FullName);
        Tools::TotalCommanderT::holder = iholder;
    }
}}

PluginSelfAssemblyResolver is a simple helper class that allows the resolution of the assembly itself when it cannot be found by .NET. It contains only two functions:

namespace Tools{namespace TotalCommanderT{
    Assembly^ PluginSelfAssemblyResolver::OnResolveAssembly(Object^ sender, 
                                          ResolveEventArgs^ args){
        AssemblyName^ name = gcnew AssemblyName(args->Name);
        if(AssemblyName::ReferenceMatchesDefinition(name, 
                thisAssembly->GetName())) return thisAssembly;
        else return nullptr;
    }
    inline void PluginSelfAssemblyResolver::Setup(){
        AppDomain::CurrentDomain->AssemblyResolve += 
          gcnew ResolveEventHandler( PluginSelfAssemblyResolver::OnResolveAssembly );
    }
}}

Definition of plug-in functions

Optional as well as compulsory Total Commander plug-in functions are wrapped in #ifdef#endif blocks. The corresponding #defines for those blocks are written by the Total Commander Plug-in Builder to the define.h file. Due to the architecture using the application domains, those functions are in the plug-in assembly three times with identical signature and similar body; I’ve extracted them to a separate file, wfxFunctionCalls.h. This file is included at three different places with several C++ preprocessor #defines to control how it is compiled. Each function is defined like this:

#ifdef TC_FS_INIT
    TCPLUGF int FUNC_MODIF FsInit(int PluginNr,tProgressProc pProgressProc, 
                tLogProc pLogProc,tRequestProc pRequestProc){
        return FUNCTION_TARGET->FsInit(PluginNr,pProgressProc,pLogProc,pRequestProc);
    }
#endif

TCPLUGF is defined as empty (not used). Once I thought about using __declspec(dllexport) to export functions. Lately, I’ve switched to a separate Exports.def file. FUNC_MODIF is either __stdcall or the class name (AppDomainHolder:: or PluginInstanceHolder::). __stdcall is used for exported functions; internal calls use the managed calling convention. Finally, FUNCTION_TARGET is the instance to call the function on. It is Tools::TotalCommanderT::holder in exported (global) functions, this->holder in AppDomainHolder, and this->instance in PluginInstanceHolder.

WfxFunctionCalls.h is included like this:

#define TCPLUGF
#define FUNC_MODIF AppDomainHolder::
#define FUNCTION_TARGET this->holder
#include "FunctionCalls.h"

I know, it may be more comprehensible typing it thrice. But, so many functions are typed thrice — I’m really lazy, and besides, when some change is needed, it needs to be done only once (in the plug-in assembly template, then in the plug-in base class and in the common header file, and …).

Marshalling

Marshalling from unmanaged to managed code is quite simple. The Total Commander plug-in interface uses several structures and constants. For structures, I’ve created managed counterparts, and before passing an object to managed code, the structure is converted to a managed one. Before a structure is returned to unmanaged code, it is converted back. Constant values are represented by managed enumeration values, and are simply cast. Something very often passed between the Total Commander and a plug-in are strings. Total Commander passes and accepts strings as char* (sometimes, char[MAX_PATH]) — always null-terminated. Marshaling those values from unmanaged to managed code is easy, because System.String has a constructor that accepts char* (System.SByte*).

Note: In the current version, Total Commander neither passes to plug-ins nor accepts from them Unicode strings (wchar_t*) although a few Win32 API structures used by Total Commander are declared as Unicode. Total Commander uses the current system encoding. Unicode support will be available in one of future versions of Total Commander. This is not a limitation of my framework but of Total Commander itself.

Passing a string to unmanaged code is a little more tricky. It is possible to enumerate all the characters of a string easily in .NET. But, those characters are Unicode code points. They must be converted to default system encoding values. Finally, I’ve created my own StringCopy functions:

namespace Tools{namespace TotalCommanderT{
    void StringCopy(String^ source, char* target, int maxlen){
        if(source == nullptr)
            target[0]=0;
        else{
            System::Text::Encoding^ enc = System::Text::Encoding::Default;
            cli::array<unsigned char>^ bytes = enc->GetBytes(source);
            for(int i = 0; i < bytes->Length && i < maxlen-1; i++)
                target[i]= bytes[i];
            target[source->Length > maxlen-1 ? maxlen-1 : source->Length] = 0;
        }
    }
    void StringCopy(String^ source, wchar_t* target, int maxlen){
        StringCopy(source,(char*)(void*)target,maxlen);
    }
}}

The second function simply treats a wchar_t* as char*, see note above.

The function encodes a string using the default system encoding and then copies the encoded bytes to an unmanaged buffer (maximally, maxlen - 1 characters). The character after the last used character is set to nullchar.

Note: I’m not sure if the default encoding will behave correctly in systems where the default encoding is multibyte (e.g., Chinese). I hope for Unicode implementations in future version of TC.

Sample plug-in

The sample plug-in is the simplest that can be written. It simply accesses the local file system. Christian Ghisler provides such an example plug-in in C++. Mine is written in Visual Basic. It shows how to utilize the Managed Total Commander Plug-in Framework.

Notes

  • Both, Tools.TotalCommander and the sample plug-in uses (not very extensively) my open source library Tools. It can be downloaded from codeplex.com/Tools and also the latest version of the plug-in framework.
  • Edit the Post-Build event and the debug command line of the wfx sample project to reflect the actual location of the Total Commander before building. You need to run Visual Studio with elevated privileges on Vista, or have write rights to the target directory (C:Program FilestotalcmdpluginsTestwfx sampleDebug).
  • The example source code is only example source code. The project has commented out several pre-build and post-build events needed when larger modifications are done! Uncommenting the events requires several utilities to perform the events. Download the full source from Codeplex.

Known issues

  • The current version of the framework supports only file system plug-ins (WFX). Other types of plug-ins will be supported in future.
  • The current version of framework does not support Unicode. This is due to the Total Commander limitation for plug-ins. It will be hopefully removed in one of the near-future versions of Total Commander.
  • The only way the sample plug-in can access UNC paths is when the user manually changes to a UNC directory from the Total Commander command line.
  • The properties window of a file/folder in the sample plugin is not modal to the Total Commander main window. This is not a limitation of the plug-in framework. I use Win32 API to show the properties dialog, and I have no idea how to make it modal.

License

The plug-in framework, the plug-in builder, the sample plug-in, as well as any other code in this article is released under the Open Source license at codeplex.com/Tools.

History

  • 2009-03-08 — Initial release.
  • 2009-03-08 — Updated source (missing statement in AssemblyResolver.cpp).

This member has not yet provided a Biography. Assume it’s interesting and varied, and probably something to do with programming.

    msm.ru

    Нравится ресурс?

    Помоги проекту!

    Пожалуйста, выделяйте текст программы тегом [сode=pas] … [/сode]. Для этого используйте кнопку [code=pas] в форме ответа или комбобокс, если нужно вставить код на языке, отличном от Дельфи/Паскаля.


    Следующие вопросы задаются очень часто, подробно разобраны в FAQ и, поэтому, будут безжалостно удаляться:
    1. Преобразовать переменную типа String в тип PChar (PAnsiChar)
    2. Как «свернуть» программу в трей.
    3. Как «скрыться» от Ctrl + Alt + Del (заблокировать их и т.п.)
    4. Как прочитать список файлов, поддиректорий в директории?
    5. Как запустить программу/файл?
    … (продолжение следует) …


    Вопросы, подробно описанные во встроенной справочной системе Delphi, не несут полезной тематической нагрузки, поэтому будут удаляться.
    Запрещается создавать темы с просьбой выполнить какую-то работу за автора темы. Форум является средством общения и общего поиска решения. Вашу работу за Вас никто выполнять не будет.


    Внимание
    Попытки открытия обсуждений реализации вредоносного ПО, включая различные интерпретации спам-ботов, наказывается предупреждением на 30 дней.
    Повторная попытка — 60 дней. Последующие попытки бан.
    Мат в разделе — бан на три месяца…

    >
    Plugins for Total Commander
    , требуется документация

    • Подписаться на тему
    • Сообщить другу
    • Скачать/распечатать тему



    Сообщ.
    #1

    ,
    13.07.04, 16:41

      Senior Member

      ****

      Рейтинг (т): 22

      Кто-нить писал плагины *.wcx, *.wlx, *.wfx для Тотала?
      будьте добры, поделитесь опытом :wub:
      я даже не знаю, с чего начать :wall:


      Петрович



      Сообщ.
      #2

      ,
      13.07.04, 17:16

        Да все как обычно. Читать описания, смотреть примеры и исходники уже написанных плагинов. Всего этого много на http://www.wincmd.ru/

        Добавлено в 13.07.04, 17:20:
        Вот например статейка с того сайта: Листер плагин на Borland Delphi 7 для начинающих


        ych_boriss



        Сообщ.
        #3

        ,
        13.07.04, 19:44

          Senior Member

          ****

          Рейтинг (т): 22

          Петрович, большое спасибо!


          ych_boriss



          Сообщ.
          #4

          ,
          14.07.04, 11:23

            Senior Member

            ****

            Рейтинг (т): 22

            как написать Lister-plugin я разобрался. вот даже накидал простенький *.CSV-View Lister Plugin. (в пристежке).
            таки, появились еще вопросы:
            1:) как отлаживать плагины
            2:) как писать плагины *.wfx и *.wсx (для начала — *.wfx)

            Сообщение отредактировано: ych_boriss — 14.07.04, 11:24


            Прикреплённый файлПрикреплённый файлview_csv.zip (10.28 Кбайт, скачиваний: 86)


            Curve



            Сообщ.
            #5

            ,
            14.07.04, 11:45

              Full Member

              ***

              Рейтинг (т): 22

              Цитата ych_boriss @ 14.07.04, 14:23

              как писать плагины *.wfx и *.wсx (для начала — *.wfx)

              http://www.wincmd.ru/developer.php


              Петрович



              Сообщ.
              #6

              ,
              14.07.04, 13:09

                Цитата

                ych_boriss, 14.07.04, 14:23
                как написать Lister-plugin я разобрался. вот даже накидал простенький *.CSV-View Lister Plugin. (в пристежке).
                таки, появились еще вопросы:
                1:) как отлаживать плагины
                2:) как писать плагины *.wfx и *.wсx (для начала — *.wfx)

                Я так понял, на http://www.wincmd.ru/ так и нее сходил. :'(


                ych_boriss



                Сообщ.
                #7

                ,
                14.07.04, 13:23

                  Senior Member

                  ****

                  Рейтинг (т): 22

                  Петрович, сходил… ;) с *.wlx в общем-то разобрался. это самое легкое…
                  вот *.wfx — потруднее, немогу наити ни документации, ни простеньких исходних примеров.. это и огорчает…
                  ну ничево, поиски продолжаются…


                  ych_boriss



                  Сообщ.
                  #8

                  ,
                  14.07.04, 14:59

                    Senior Member

                    ****

                    Рейтинг (т): 22

                    все… нашол сорец!!!
                    завтра буду делать WFX-плугin ;)


                    Петрович



                    Сообщ.
                    #9

                    ,
                    14.07.04, 16:54

                      Цитата

                      ych_boriss, 14.07.04, 16:23
                      … вот *.wfx — потруднее, немогу наити ни документации, …

                      Странно, на указанном мной сайте есть документация по написанию всех типов плагинов для Total Commander’а. Вот Curve даже ссылку на конкретную страничку дал :)

                      0 пользователей читают эту тему (0 гостей и 0 скрытых пользователей)

                      0 пользователей:

                      • Предыдущая тема
                      • Delphi: Общие вопросы
                      • Следующая тема

                      Рейтинг@Mail.ru

                      [ Script execution time: 0,0289 ]   [ 16 queries used ]   [ Generated: 4.03.23, 06:03 GMT ]  

                      Не боги горшки обжигают:

                      Наверное, данная статья является не учебным пособием, а попыткой обобщить опыт, полученный автором в процессе разработки плагина xBaseView на Delphi 7, когда пришлось столкнуться с проблемами, довольно неприятными программисту, привыкшему мощной поддержке VCL среды.

                      Задача: создать плагин для просмотра RTF файлов.

                      Создаем проект DLL библиотеки и сохраняем его под именем ListSimpleBcb.bpr в отдельной папке, изменим расширение имени плагина на WLX в опциях проекта.

                      Модуль Unit1.cpp сохраняем под именем ListSimple.cpp.

                      Плагин должен экспортировать из библиотеки три функции:

                      ListGetDetectString,
                      ListLoad,
                      ListCloseWindow.

                      ListGetDetectString должна записать в параметр DetectString строку, которая содержит RTF.

                      ListLoad вызывает плагин для работы и передает параметры:

                      ListerWin - дескриптор окна Листера;
                      FileToLoad - полное имя RTF файла.

                      Плагин должен создать свое окно в качестве дочерней по отношению к окну Листера и вернуть дескриптор этого окна. Эту инициализацию мы будем осуществлять в функции ShowRTF:

                      HWND ShowRTF(HWND ListerWin, char* FileToLoad);

                      ListCloseWindow требует от плагина завершения работы, передавая дескриптор окна плагина. Это освобождение ресурсов мы будем делать в функции HideRTF:

                      void HideRTF(HWND PluginWin);

                      Надо добавить в проект форму, изменить ее имя на fmMain и сохранить модуль формы под именем unMain.cpp. Эта форма и есть окно плагина.

                      В заголовочный файл unMain.h надо добавить определения двух вышеприведенных функции и уда-лить бесполезную глобальную переменную:

                      extern PACKAGE TfmMain *fmMain;

                      Замечание: ПЛАГИН НЕ ДОЛЖЕН ИСПОЛЬЗОВАТЬ ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ!

                      Бросив на форму компонент RichEdit, надо установить ему такие свойства:

                      Align = alClient,
                      ReadOnly = True,
                      ScrollBars = ssBoth.

                      Объект RichEdit1 обеспечивает всю работу с RTF файлом.

                      В плагинах часто используется контекстное меню, поэтому надо бросить на форму компонент PopupMenu и создать два пункта меню Help и About, назначив им клавиши F1 и F2, соответственно. Необходимо прикрепить контекстное меню объекту RichEdit1, установив свойство RichEdit1:

                      PopupMenu = PopupMenu1.

                      Окно плагина является дочерним, следовательно, оно не должно иметь рамку и заголовок. Для этого надо переопределить виртуальную функцию CreateParams класса TCustomForm. Здесь, мы кроме настройки стилей окна:

                      Params.Style = WS_CHILD | WS_MAXIMIZE & !WS_CAPTION & !WS_BORDER;

                      зарезервируем память для хранения указателя на нашу форму:

                      Params.WindowClass.cbWndExtra = sizeof(void *);

                      Указатель нужен функции HideRTF для корректного закрытия плагина. В функции ShowRTF мы сохраняем указатель в WinAPI структуре окна плагина следующим вызовом:

                      SetWindowLong(fmMain->Handle, GWL_USERDATA, (LONG)p);

                      Позднее мы вытащим указатель из структуры, используя дескриптор нашего окна, который является параметром HideRTF, вызывая функцию GetWindowLong:

                      GetWindowLong(PluginWin, GWL_USERDATA);

                      Чтобы создать дочернее окно воспользуемся статической функцией CreateParentedControl класса TWinControl, т.е. в теле функции функция ShowRTF воспользуемся следующим оператором:

                      fmMain = (TfmMain *)(TWinControl::CreateParentedControl(__classid(TfmMain), ListerWin));

                      Для работы плагина нам понадобятся следующие данные: дескрипторы окон Тотал Командира и Листера, и режим работы плагина: в обычном окне или на одной из двух панелей Тотал Командира (так называемый Quick View — режим быстрого просмотра). Дескрипторы нужны, чтобы избежать краха по клавишам выхода Alt+X и для реагирования на быстрые клавиши Листера. Эти данные будем хранить, соответственно, в переменных TotCmdWin, ParentWin и QuickView. Добавим их определения в public раздел класса формы в файле unMain.h. Дескриптор окна Тотал Командира мы можем найти по имени класса этого окна (это не VCL класс!) с помощью WinAPI функции FindWindow.

                      Немного теорий. Особенностью библиотеки визуальных компонентов Borland VCL является ис-пользование глобального объекта «приложение» — Application. Этот Application имеет скрытое окно, которое должно быть владельцем всех окон приложения, что обеспечивает корректное поведение всех окон программы. И DLL библиотека плагина, и исполняемый EXE файл Тотал Командира имеют собственный глобальный объект Application, следовательно, требуется их синхронизация. Для этого надо присвоить дескриптор скрытого окна объекта Application исполняемого файла к дескриптору объекта Application библиотеки. К сожалению, Тотал Командир, хотя он написан на Delphi и использует все удобства и прелести VCL, не передает нам дескриптор данного скрытого окна. (Может причиной этого является проблема версии компиляторов Delphi?). За неимением оно-го, вместо него будем использовать для синхронизации дескриптор окна Листера, в противном слу-чае, наше модальное окно (например, окно About) поведет себя неестественно, оно появится на панели задач Windows. Эта синхронизация является задачей для функции ShowRTF.

                      Чтобы защитить, Тотал Командир от сбоев плагина, будем использовать событие OnException объ-екта Application, которое перехватывает необработанное исключение. Кинем на форму компонент ApplicationEvents, переименуем его на App и создаем обработчик события OnException, куда введем вызов функции MessageBox для выдачи сообщения о сбое. Сохранив проект и модуль, можно уда-лить из формы ненужный компонент ApplicationEvents. Установка адреса обработчика события в Application.OnException также является задачей для функции ShowRTF.

                      Все готово для ShowRTF? Увы, «маленькая неточность» в Plugin API Тотал Командира (включая v.6.02) значительно усложняет нашу работу:

                      Если пользователь откроет окно плагина, затем переключится на Тотал Командир и закроет Тотал Командир, то мы не получим вызов через ListCloseWindow для завершения своей работы. Вместо этого Тотал Командир или Листер (кто его знает?) уничтожает наше окно вызовом WinAPI функции DestroyWindow, а потом, то же самое попытается сделать наш объект Application, в конечном счете плагин вылетает с нарушением защиты памяти (General Protection Fault)!

                      Если мы не будем делать вышеописанную синхронизацию, этой проблемы удалось бы избежать. Что ж, будем расплачиваться за это и поставим ловушку для перехвата сообщений, которое получа-ет наше же окно, подобно змее, которая пожирает себя за свой собственный хвост! Не будем вда-ваться в подробности Windows API (кому же он нравиться?), вкратце суть дела такова.

                      Объявляем структуру TPlugInfo, где будем хранить данные, которые нужны для закрытия плагина. При инициализации выделяем память под эту структуру и заполняем ее. Определяем функцию HookDestroy, который будет перехватывать оконные сообщения, чтобы среагировать на сообщение WM_DESTROY (уничтожение окна). Вызовом SetWindowLong(:, GWL_WNDPROC, :) подменя-ем стандартный обработчик оконных сообщений на функцию HookDestroy. Похожим вызовом SetWindowLong в функции завершения HideRTF обратно восстанавливаем прежний обработчик.

                      Важно то, что когда функция HookDestroy поймает сообщение WM_DESTROY, она вызывает функцию завершения HideRTF точно так же, как это делает функция ListCloseWindow. В результате мы всегда успеваем нормально закрыть окно и убрать за собой, что, к примеру демонстрирует free(p) из HideRTF, освобождая память, выделенную в ShowRTF.

                      Остается создать диалоговую форму About, обработчики контекстного меню и обработчик RichEdit1KeyDown, который ловит нажатие специальных клавиш и передает их с помощью PostMessage в соответствующее окно. Если здесь не перехватывать клавиши Alt+X, это приведет к краху плагина. Причем, здесь придется удерживать фокус ввода в RichEdit1, проверяя режим рабо-ты плагина, т.е. значение переменной QuickView.

                      Об отладке и тестировании плагина.

                      Листер имеет встроенную возможность просмотра RTF файлов, поэтому перед запуском плагина надо отключить ее, сняв пометку у флага RTF в окне Configure Lister.

                      Чтобы использовать интегрированный отладчик IDE BCB, закройте Total Commander, а в BCB через меню «Run/Parameters» откройте диалог и в поле Host Application введите путь к Total Commander с помощью кнопки [Browse]. Теперь можно запускать плагин из-под BCB.

                      Ссылка для скачивания архива с примером здесь

                      PS. Уважаемый Читатель! Программирование — многовариантно и я никак не претендую на абсолютную истину. В качестве дополнительного материала, предлагаю Вам ознакомиться с исходными текстами плагина xBaseView — «Просмотр DBF, DB, MDB, ADO и ODBC баз данных», который предоставляется с исходными текстами, и где применяется аналогичный подход.

                      PPS. Прошу Читателя-Полиглота помочь мне перевести статью на английский язык, чтобы послать ее Кристиану Гислеру, автору знаменитого файл менеджера Тотал Командир, с призрачной надеждой, что он предпримет какие-то шаги навстречу нам — VCL программистам.

                      Лет этак десять назад написал я парочку плагинов к всенародно любимому файл-менеджеру Total Commander. В те годы он только-только сменил имя и плагинов к нему было не так много, как сейчас. Мне лично не хватало пары функций, и среди прочего — возможности нормального просмотра сохранённых HTML. Многие ещё помнят такую штуку, как dialup. Тогда он ещё оставался для многих жителей глубинки единственным способом доступа в Интернет. Безумно (по нынешним меркам) дорогим и с очень неудобной тарификацией — повременной. В таких условиях читать длинные статьи в режиме online не было решительно никакой возможности (если вы, конечно, не были миллионером). А потому вся мало-мальски интересная информация в огромном количестве сваливалась на жёсткий диск, дабы быть прочитанной позже — в оффлайне.

                      Иногда возникала необходимость быстренько пролистать с десяток таких вот сохранённых страниц. А поскольку основным рабочим инструментом тогда для меня был (да и сейчас остаётся) именно Total Commander, логично было использовать его возможности. Но, увы, просмотрщик TC (aka Lister) HTML смотрит весьма посредственно — без картинок и прочих прелестей. Именно тогда я задумал написать плагин, решающий эту проблему — т.е. умеющий смотреть HTML со всеми плюшками (в частности, картинками и CSS).

                      WebView

                      Вот как-то так выглядит WebView

                      Первая версия WebView была написана на Delphi с использованием компонента ThtmlViewer (ныне заброшенного). В сравнении с «голым» Lister’ом возможности уже были неплохие — отображалось форматирование (частично) и изображения (правда, не все). Но этого было недостаточно, и я стал искать другие варианты. Самый простой путь был очевиден — использовать движок Trident. Этот вариант отпал сам собой — в годы властвования IE6 с его набившим оскомину всем web-мастерам косяками от «ослика» лучше было держаться подальше. Chromium тогда ещё не существовал даже в проекте, его предок WebKit был доступен только для MacOS X, а Presto можно купить только за пару миллионов норвежских крон ( 🙂 ). Оставался один вариант — Gecko (читай — Firefox). Всё его плюсы, правда, для меня лично перечёркивал один жирный минус — написан этот движок на С++, а я этот язык недолюбливаю, предпочитая современные реализации Pascal (в ту пору это был Object Pascal в Delphi, сейчас — Free Pascal и Lazarus). Решение, как ни странно, всё же нашлось довольно быстро — в лице проекта Mozilla ActiveX. Реализация движка Gecko в виде ActiveX-контрола давала возможность легко использовать его в Delphi-программе — достаточно немного разобраться в API. В результате скрещивания ужа с ежом Delphi и Gecko и появился на свет WebView в его окончательном виде. Финальная (на сегодняшний день) версия этого плагина увидела свет в январе 2005 года. Скачать её по сей день можно из архива плагинов на wincmd.ru.

                      В течение всех этих лет мне периодически приходили письма от пользователей моей поделки. По большей части присылали переводы языкового файла на разные языки (всего переводов на сегодняшний день 20, включая русский и английский, сделанные мной). Несколько человек просили опубликовать исходники. И вот, наконец, у меня дошли руки — и сегодня я выкладываю исходники WebView. Сразу предупреждаю: ничего особенного интересного они из себя не представляют. Большая часть кода написана в 2003-2004 годах, и я уже сам не помню, как там всё устроено. Даже не проверял, компилируется ли этот код на новых версиях Delphi (я собирал последнюю версию в Delphi 7). Кроме всего прочего, проект Mozilla ActiveX давно почил, а значит использует старую версию движка Gecko.

                      Как бы то ни было — вот код и бинарники WebView последней версии. Будем считать, что код опубликован под лицензией BSD. Можете использовать в качестве примера работы с Gecko под Delphi, также как образец говнокода написания плагина к TC на Delphi.

                      PS. Если соберусь, то в ближайшее время выложу ещё несколько своих исходников — в годы оны понаписал немало, вроде как жалко, что всё лежит без дела. Но пока ничего не обещаю.

                      Понравилась статья? Поделись с друзьями!

                      Я пытаюсь создать простой плагин WCX для Total Commander.

                      Я скачал wcxhead.pas отсюда (это в файле справки): http://ghisler.fileburst.com/plugins/wcx_ref2.21se_chm.zip

                      Это официальный файл, связанный здесь: https://www.ghisler.com/plugins.htm

                      И создал мою собственную DLL:

                      library TST;
                      
                      uses
                        Windows,
                        SysUtils,
                        Classes,
                        wcxhead;
                      
                      {$E wcx}
                      
                      var Num: Integer;
                      
                      function OpenArchive (var ArchiveData: tOpenArchiveData): THANDLE; stdcall;
                      begin
                        Result := 99999;
                      
                        Num := 0;
                      end;
                      
                      function ReadHeader(hArcData: THANDLE; var HeaderData: tHeaderData): Integer; stdcall;
                      begin
                        with HeaderData do begin
                      
                          FileName := 'my_file.ext';
                          PackSize := 1000;
                          UnpSize  := 2000;
                          FileTime := 1876543;
                          FileAttr := $3F;
                      
                        end;
                      
                        if Num < 1 then Result := 0
                        else Result := E_END_ARCHIVE;
                      
                        Inc(Num);
                      end;
                      
                      function ProcessFile(hArcData: THANDLE; Operation: Integer; DestPath: PChar; DestName: PChar): Integer; stdcall;
                      begin
                        Result := 0;
                      end;
                      
                      function CloseArchive(hArcData: THANDLE): Integer; stdcall;
                      begin
                        Result := 0;
                      end;
                      
                      procedure SetChangeVolProc(hArcData: THANDLE; pChangeVolProc1: tChangeVolProc);stdcall;
                      begin
                      //
                      end;
                      
                      procedure SetProcessDataProc(hArcData: THANDLE; pProcessDataProc: tProcessDataProc); stdcall;
                      begin
                      //
                      end;
                      
                      exports
                        OpenArchive, ReadHeader, ProcessFile, CloseArchive, SetChangeVolProc, SetProcessDataProc;
                      
                      end.
                      

                      Вот wcxhead.pas

                      unit wcxhead;
                      
                      interface
                      
                      
                      
                      
                      
                      const       {Error codes returned to calling application}
                        E_END_ARCHIVE=     10;       {No more files in archive}
                        E_NO_MEMORY=       11;       {Not enough memory}
                        E_BAD_DATA=        12;       {CRC error in the data of the currently unpacked file}
                        E_BAD_ARCHIVE=     13;       {The archive as a whole is bad, e.g. damaged headers}
                        E_UNKNOWN_FORMAT=  14;       {Archive format unknown}
                        E_EOPEN=           15;       {Cannot open existing file}
                        E_ECREATE=         16;       {Cannot create file}
                        E_ECLOSE=          17;       {Error closing file}
                        E_EREAD=           18;       {Error reading from file}
                        E_EWRITE=          19;       {Error writing to file}
                        E_SMALL_BUF=       20;       {Buffer too small}
                        E_EABORTED=        21;       {Function aborted by user}
                        E_NO_FILES=        22;       {No files found}
                        E_TOO_MANY_FILES=  23;       {Too many files to pack}
                        E_NOT_SUPPORTED=   24;       {Function not supported}
                      
                        {Unpacking flags}
                        PK_OM_LIST=           0;
                        PK_OM_EXTRACT=        1;
                      
                        {Flags for ProcessFile}
                        PK_SKIP=              0;     {Skip file (no unpacking)}
                        PK_TEST=              1;     {Test file integrity}
                        PK_EXTRACT=           2;     {Extract file to disk}
                      
                        {Flags passed through ChangeVolProc}
                        PK_VOL_ASK=           0;     {Ask user for location of next volume}
                        PK_VOL_NOTIFY=        1;     {Notify app that next volume will be unpacked}
                      
                        {Packing flags}
                      
                        {For PackFiles}
                        PK_PACK_MOVE_FILES=   1;    {Delete original after packing}
                        PK_PACK_SAVE_PATHS=   2;    {Save path names of files}
                        PK_PACK_ENCRYPT=      4;    {Ask user for password, then encrypt}
                      
                      
                        {Returned by GetPackCaps}
                        PK_CAPS_NEW=          1;    {Can create new archives}
                        PK_CAPS_MODIFY=       2;    {Can modify exisiting archives}
                        PK_CAPS_MULTIPLE=     4;    {Archive can contain multiple files}
                        PK_CAPS_DELETE=       8;    {Can delete files}
                        PK_CAPS_OPTIONS=     16;    {Supports the options dialogbox}
                        PK_CAPS_MEMPACK=     32;    {Supports packing in memory}
                        PK_CAPS_BY_CONTENT=  64;    {Detect archive type by content}
                        PK_CAPS_SEARCHTEXT= 128;    {Allow searching for text in archives
                                                    { created with this plugin}
                        PK_CAPS_HIDE=       256;    { Show as normal files (hide packer icon) }
                                                    { open with Ctrl+PgDn, not Enter }
                        PK_CAPS_ENCRYPT=    512;    { Plugin supports PK_PACK_ENCRYPT option }
                      
                        BACKGROUND_UNPACK=1;        { Which operations are thread-safe? }
                        BACKGROUND_PACK=2;
                        BACKGROUND_MEMPACK=4;       { For tar.pluginext in background }
                      
                        {Flags for packing in memory}
                        MEM_OPTIONS_WANTHEADERS=1;  {Return archive headers with packed data}
                      
                        {Errors returned by PackToMem}
                        MEMPACK_OK=           0;    {Function call finished OK, but there is more data}
                        MEMPACK_DONE=         1;    {Function call finished OK, there is no more data}
                      
                        {Flags for PkCryptProc callback}
                        PK_CRYPT_SAVE_PASSWORD=1;
                        PK_CRYPT_LOAD_PASSWORD=2;
                        PK_CRYPT_LOAD_PASSWORD_NO_UI=3;   { Load password only if master password has already been entered!}
                        PK_CRYPT_COPY_PASSWORD=4;         { Copy encrypted password to new archive name}
                        PK_CRYPT_MOVE_PASSWORD=5;         { Move password when renaming an archive}
                        PK_CRYPT_DELETE_PASSWORD=6;       { Delete password}
                      
                      
                        PK_CRYPTOPT_MASTERPASS_SET = 1;   // The user already has a master password defined
                      
                      
                      type
                        {Definition of callback functions called by the DLL}
                        {Ask to swap disk for multi-volume archive}
                        PChangeVolProc=^TChangeVolProc;
                        TChangeVolProc=function(ArcName:pchar;Mode:longint):longint; stdcall;
                        PChangeVolProcW=^TChangeVolProcW;
                        TChangeVolProcW=function(ArcName:pwidechar;Mode:longint):longint; stdcall;
                        {Notify that data is processed - used for progress dialog}
                        PProcessDataProc=^TProcessDataProc;
                        TProcessDataProc=function(FileName:pchar;Size:longint):longint; stdcall;
                        PProcessDataProcW=^TProcessDataProcW;
                        TProcessDataProcW=function(FileName:pwidechar;Size:longint):longint; stdcall;
                        PPkPluginCryptProc=^TPkPluginCryptProc;
                        TPkPluginCryptProc=function(CryptoNr:integer;mode:integer;ArchiveName,
                          Password:pchar;maxlen:integer):integer; stdcall;
                        PPkPluginCryptProcW=^TPkPluginCryptProcW;
                        TPkPluginCryptProcW=function(CryptoNr:integer;mode:integer;ArchiveName,
                          Password:pwidechar;maxlen:integer):integer; stdcall;
                      
                      type
                        THeaderData=packed record
                          ArcName:array [0..259] of char;
                          FileName:array [0..259] of char;
                          Flags,
                          PackSize,
                          UnpSize,
                          HostOS,
                          FileCRC,
                          FileTime,
                          UnpVer,
                          Method,
                          FileAttr:longint;
                          CmtBuf:pchar;
                          CmtBufSize,
                          CmtSize,
                          CmtState:longint;
                        end;
                      
                        THeaderDataEx=packed record
                          ArcName:array [0..1023] of char;
                          FileName:array [0..1023] of char;
                          Flags:longint;
                          PackSize,
                          PackSizeHigh,
                          UnpSize,
                          UnpSizeHigh: Cardinal;
                          HostOS,
                          FileCRC,
                          FileTime,
                          UnpVer,
                          Method,
                          FileAttr:longint;
                          CmtBuf:pchar;
                          CmtBufSize,
                          CmtSize,
                          CmtState:longint;
                          Reserved:array[0..1023] of char;
                        end;
                      
                        THeaderDataExW=packed record
                          ArcName:array [0..1023] of widechar;
                          FileName:array [0..1023] of widechar;
                          Flags:longint;
                          PackSize,
                          PackSizeHigh,
                          UnpSize,
                          UnpSizeHigh: Cardinal;
                          HostOS,
                          FileCRC,
                          FileTime,
                          UnpVer,
                          Method,
                          FileAttr:longint;
                          CmtBuf:pchar;
                          CmtBufSize,
                          CmtSize,
                          CmtState:longint;
                          Reserved:array[0..1023] of char;
                        end;
                      
                        tOpenArchiveData=packed record
                          ArcName:pchar;
                          OpenMode,
                          OpenResult:longint;
                          CmtBuf:pchar;
                          CmtBufSize,
                          CmtSize,
                          CmtState:longint;
                        end;
                      
                        tOpenArchiveDataW=packed record
                          ArcName:pwidechar;
                          OpenMode,
                          OpenResult:longint;
                          CmtBuf:pwidechar;
                          CmtBufSize,
                          CmtSize,
                          CmtState:longint;
                        end;
                      
                        tPackDefaultParamStruct=record
                          size,
                          PluginInterfaceVersionLow,
                          PluginInterfaceVersionHi:longint;
                          DefaultIniName:array[0..259] of char;
                        end;
                        pPackDefaultParamStruct=^tPackDefaultParamStruct;
                      
                      
                      
                      
                      implementation
                      
                      end.
                      

                      Я установил плагин в Total Commander. Теперь Total Commander открывает мои файлы .TST, но не показывает никаких файлов внутри. Это должно показать «my_file.ext».

                      Я даже нашел простой плагин WCX, но я не вижу никакой разницы с моим кодом в важных вещах: https://github.com/ypid-bot/doublecmd/blob/master/plugins/wcx/unbz2/src/bz2func.pas

                      Заголовок был явно создан со времен до Unicode Delphi. Предполагается, что char = AnsiChar, что не соответствует истине с Delphi 2009.

                      IIRC с форумов, 32-разрядная версия TotalCommander по-прежнему компилируется с Delphi 2 или Delphi 3 с большим количеством настраиваемых RTL, что делает его небольшим и эффективным.

                      Если вы используете версию Delphi для Unicode, попробуйте изменить char на AnsiChar и использовать экспорт *W() с поддержкой Unicode.

                      Также не используйте глобальные переменные для поддержания состояния доступа упаковщика: вы должны использовать значение THandle, чтобы открыть / закрыть данный файл архива. Два архива могут быть открыты одновременно.

                      Изменить: так как вы используете Delphi 7, это не проблема Unicode. Но я бы попробовал

                      strpcopy(filename,'my_file.ext');
                      


                      1

                      Arnaud Bouchez
                      15 Янв 2020 в 15:58

                      Понравилась статья? Поделить с друзьями:
                    • Как написать плагин для notepad
                    • Как написать плагин для modx revo
                    • Как написать плагин для minecraft на python
                    • Как написать плагин для minecraft на java
                    • Как написать плагин для kodi