<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/atom10russianfull.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:openSearch="http://a9.com/-/spec/opensearch/1.1/" xmlns:georss="http://www.georss.org/georss" xmlns:gd="http://schemas.google.com/g/2005" xmlns:thr="http://purl.org/syndication/thread/1.0" xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" gd:etag="W/&quot;CE8ERHo6fyp7ImA9WhRUFkU.&quot;"><id>tag:blogger.com,1999:blog-1702873441071265539</id><updated>2012-01-27T20:46:45.417+04:00</updated><category term="не делай так" /><category term="прочее" /><category term="Vista" /><category term="ты можешь это сделать" /><category term="роботы/киберпанк" /><category term="Королевство Delphi" /><category term="работа" /><category term="Delphi" /><category term="случайные мысли" /><category term="начинающим" /><category term="блог" /><category term="Коты" /><category term="7" /><category term="журнал" /><category term="EurekaLog" /><category term="задачки" /><category term="Windows" /><category term="Tiburon" /><category term="обработка ошибок" /><category term="x64" /><category term="TasksEx" /><category term="Статья" /><title>Блог GunSmoker-а</title><subtitle type="html">...when altering one's mind becomes as easy as programming a computer, what does it mean to be human?..</subtitle><link rel="http://schemas.google.com/g/2005#feed" type="application/atom+xml" href="http://www.gunsmoker.ru/feeds/posts/default" /><link rel="alternate" type="text/html" href="http://www.gunsmoker.ru/" /><link rel="next" type="application/atom+xml" href="http://www.blogger.com/feeds/1702873441071265539/posts/default?start-index=6&amp;max-results=5&amp;redirect=false&amp;v=2" /><author><name>Александр Алексеев</name><uri>https://profiles.google.com/113168002104297556003</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-aUMttZQBsuI/AAAAAAAAAAI/AAAAAAAAC3Y/QuZ7K9t_WzE/s512-c/photo.jpg" /></author><generator version="7.00" uri="http://www.blogger.com">Blogger</generator><openSearch:totalResults>184</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>5</openSearch:itemsPerPage><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" type="application/atom+xml" href="http://feeds.feedburner.com/GunSmokersBlog" /><feedburner:info uri="gunsmokersblog" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com/" /><feedburner:emailServiceId>GunSmokersBlog</feedburner:emailServiceId><feedburner:feedburnerHostname>http://feedburner.google.com</feedburner:feedburnerHostname><feedburner:feedFlare href="http://add.my.yahoo.com/rss?url=http%3A%2F%2Ffeeds.feedburner.com%2FGunSmokersBlog" src="http://us.i1.yimg.com/us.yimg.com/i/us/my/addtomyyahoo4.gif">Subscribe with My Yahoo!</feedburner:feedFlare><feedburner:feedFlare href="http://www.newsgator.com/ngs/subscriber/subext.aspx?url=http%3A%2F%2Ffeeds.feedburner.com%2FGunSmokersBlog" src="http://www.newsgator.com/images/ngsub1.gif">Subscribe with NewsGator</feedburner:feedFlare><feedburner:feedFlare href="http://feeds.my.aol.com/add.jsp?url=http%3A%2F%2Ffeeds.feedburner.com%2FGunSmokersBlog" src="http://o.aolcdn.com/favorites.my.aol.com/webmaster/ffclient/webroot/locale/en-US/images/myAOLButtonSmall.gif">Subscribe with My AOL</feedburner:feedFlare><feedburner:feedFlare href="http://www.bloglines.com/sub/http://feeds.feedburner.com/GunSmokersBlog" src="http://www.bloglines.com/images/sub_modern11.gif">Subscribe with Bloglines</feedburner:feedFlare><feedburner:feedFlare href="http://www.netvibes.com/subscribe.php?url=http%3A%2F%2Ffeeds.feedburner.com%2FGunSmokersBlog" src="http://www.netvibes.com/img/add2netvibes.gif">Subscribe with Netvibes</feedburner:feedFlare><feedburner:feedFlare href="http://fusion.google.com/add?feedurl=http%3A%2F%2Ffeeds.feedburner.com%2FGunSmokersBlog" src="http://buttons.googlesyndication.com/fusion/add.gif">Subscribe with Google</feedburner:feedFlare><feedburner:feedFlare href="http://www.pageflakes.com/subscribe.aspx?url=http%3A%2F%2Ffeeds.feedburner.com%2FGunSmokersBlog" src="http://www.pageflakes.com/ImageFile.ashx?instanceId=Static_4&amp;fileName=ATP_blu_91x17.gif">Subscribe with Pageflakes</feedburner:feedFlare><feedburner:feedFlare href="http://lenta.yandex.ru/settings.xml?name=feed&amp;url=http%3A%2F%2Ffeeds.feedburner.com%2FGunSmokersBlog" src="http://lenta.yandex.ru/i/addfeed.gif">?????? ? ??????.?????</feedburner:feedFlare><feedburner:feedFlare href="http://www.plusmo.com/add?url=http%3A%2F%2Ffeeds.feedburner.com%2FGunSmokersBlog" src="http://plusmo.com/res/graphics/fbplusmo.gif">Subscribe with Plusmo</feedburner:feedFlare><feedburner:feedFlare href="http://www.thefreedictionary.com/_/hp/AddRSS.aspx?http%3A%2F%2Ffeeds.feedburner.com%2FGunSmokersBlog" src="http://img.tfd.com/hp/addToTheFreeDictionary.gif">Subscribe with The Free Dictionary</feedburner:feedFlare><feedburner:feedFlare href="http://www.bitty.com/manual/?contenttype=rssfeed&amp;contentvalue=http%3A%2F%2Ffeeds.feedburner.com%2FGunSmokersBlog" src="http://www.bitty.com/img/bittychicklet_91x17.gif">Subscribe with Bitty Browser</feedburner:feedFlare><feedburner:feedFlare href="http://www.newsalloy.com/?rss=http%3A%2F%2Ffeeds.feedburner.com%2FGunSmokersBlog" src="http://www.newsalloy.com/subrss3.gif">Subscribe with NewsAlloy</feedburner:feedFlare><feedburner:feedFlare href="http://www.live.com/?add=http%3A%2F%2Ffeeds.feedburner.com%2FGunSmokersBlog" src="http://tkfiles.storage.msn.com/x1piYkpqHC_35nIp1gLE68-wvzLZO8iXl_JMledmJQXP-XTBOLfmQv4zhj4MhcWEJh_GtoBIiAl1Mjh-ndp9k47If7hTaFno0mxW9_i3p_5qQw">Subscribe with Live.com</feedburner:feedFlare><feedburner:feedFlare href="http://mix.excite.eu/add?feedurl=http%3A%2F%2Ffeeds.feedburner.com%2FGunSmokersBlog" src="http://image.excite.co.uk/mix/addtomix.gif">Subscribe with Excite MIX</feedburner:feedFlare><feedburner:feedFlare href="http://download.attensa.com/app/get_attensa.html?feedurl=http%3A%2F%2Ffeeds.feedburner.com%2FGunSmokersBlog" src="http://www.attensa.com/blogs/attensa/WindowsLiveWriter/BadgeredintoBadges_10C02/attensa_feed_button5.gif">Subscribe with Attensa for Outlook</feedburner:feedFlare><feedburner:feedFlare href="http://www.webwag.com/wwgthis.php?url=http%3A%2F%2Ffeeds.feedburner.com%2FGunSmokersBlog" src="http://www.webwag.com/images/wwgthis.gif">Subscribe with Webwag</feedburner:feedFlare><feedburner:feedFlare href="http://www.podcastready.com/oneclick_bookmark.php?url=http%3A%2F%2Ffeeds.feedburner.com%2FGunSmokersBlog" src="http://www.podcastready.com/images/podcastready_button.gif">Subscribe with Podcast Ready</feedburner:feedFlare><feedburner:feedFlare href="http://www.flurry.com/pushRssFeed.do?r=fb&amp;url=http%3A%2F%2Ffeeds.feedburner.com%2FGunSmokersBlog" src="http://www.flurry.com/images/flurry_rss_logo2.gif">Subscribe with Flurry</feedburner:feedFlare><feedburner:feedFlare href="http://www.wikio.com/subscribe?url=http%3A%2F%2Ffeeds.feedburner.com%2FGunSmokersBlog" src="http://www.wikio.com/shared/img/add2wikio.gif">Subscribe with Wikio</feedburner:feedFlare><feedburner:feedFlare href="http://www.dailyrotation.com/index.php?feed=http%3A%2F%2Ffeeds.feedburner.com%2FGunSmokersBlog" src="http://www.dailyrotation.com/rss-dr2.gif">Subscribe with Daily Rotation</feedburner:feedFlare><entry gd:etag="W/&quot;CU8MQHs5cCp7ImA9WhRVEkk.&quot;"><id>tag:blogger.com,1999:blog-1702873441071265539.post-7680018511720040678</id><published>2012-01-11T05:04:00.002+04:00</published><updated>2012-01-11T05:04:41.528+04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-01-11T05:04:41.528+04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Delphi" /><category scheme="http://www.blogger.com/atom/ns#" term="Статья" /><title>Разработка системы плагинов, часть 3: активные плагины</title><content type="html">В &lt;a title="Разработка системы плагинов в Delphi, часть 2: разработка API" href="http://www.gunsmoker.ru/2012/01/delphi-2-api.html"&gt;прошлой части&lt;/a&gt; мы реализовали пассивные плагины: они просто предоставляли функции, а ядро их вызывало по мере надобности. Сам же плагин был полностью пассивен, он не инициировал никаких действий в ядре. В этой части мы рассмотрим более сложные методы общения между ядром и плагинами.&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;
&lt;h1&gt;Оглавление&lt;/h1&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="#n1" title="Что хотим"&gt;Что хотим&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#n2" title="Управление меню"&gt;Меню&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#n3" title="Управление редактором"&gt;Редактор&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;br /&gt;
&lt;a name="n1"&gt;&lt;/a&gt;&lt;h1&gt;Что хотим&lt;/h1&gt;
Здесь и далее я лишь кратко буду озвучивать идею метода - для экономии места и времени, но при этом я предполагаю, что в вашей реальной программе вы будете прорабатывать всё это полностью - как мы делали это в &lt;a title="Разработка системы плагинов в Delphi, часть 2: разработка API" href="http://www.gunsmoker.ru/2012/01/delphi-2-api.html"&gt;прошлый раз&lt;/a&gt;, включая предварительное проектирование, формальное определение контракта и документацию.&lt;br /&gt;
&lt;br /&gt;
В качестве примера к этой статье я предлагаю разрешить плагинам встраиваться в меню нашей программы. Тогда, если пользователь щёлкнет по пункту меню плагина, плагин должен отреагировать и выполнить какую-то работу. Понятно, что для этого плагину придётся "дёргать" ядро, уже по инициативе самого плагина, а не ядра.&lt;br /&gt;
&lt;br /&gt;
Пока у нас нет ничего, что плагин мог бы "дёргать", поэтому сейчас никакой полезной работы он (плагин) выполнить не сможет. По этой причине я предлагаю также ввести в программу возможность манипулирования текстовым редактором. Тогда мы могли бы написать плагин, который, скажем, добавлял бы в меню такие пункты: "Преобразовать разделители строк в &amp;lt;br/&amp;gt;", "Обрамить жирный текст в &amp;lt;b&amp;gt;&amp;lt;/b&amp;gt;" или "Изменить регистр всех символов на ВЕРХНИЙ". В общем, много чего придумать можно.&lt;br /&gt;
&lt;br /&gt;
От меню требуется дать возможность плагинам добавлять туда свои пункты меню, а также модифицировать их (например, отключать или менять заголовок) и реагировать на события (например, щелчки мышью). В принципе, это можно сделать, как и ранее - пусть плагин реализует гипотетический интерфейс &lt;code&gt;IPluginMenuItem&lt;/code&gt;. Тогда ядро может это увидеть и создать для плагина пункт меню при его загрузке. Это будет повторение уже известного "пассивного" подхода. Но если в прошлом примере с экспортом/импортом этот подход работал отлично, то здесь уже он не достаточно гибок - ведь плагину может потребоваться более одного пункта меню.&lt;br /&gt;
&lt;br /&gt;
Поэтому я предлагаю использовать другой подход, с обратным вызовом (callback). Вспомните, что при загрузке плагина ядро передаёт ему "себя" - &lt;code&gt;ICore&lt;/code&gt;. Плагин волен делать с этой ссылкой всё, что ему угодно, в любой момент времени. Понятно, что сейчас там есть всего один метод - получение версии ядра, но никто же не запрещает нам ввести туда метод вида "а ну-ка, создай для меня новый пункт меню!". Тогда плагин в любой момент времени (но, конечно же, чаще всего - при загрузке) может вызвать этот метод ядра (и даже несколько раз подряд) и создать себе сколько угодно пунктов меню. Это и есть активный подход: уже не ядро командует плагином, а сам плагин дёргает ядро.&lt;br /&gt;
&lt;br /&gt;
С редактором текста программы имеет место быть полностью аналогичная ситуация. Ядро предоставляет плагинами интерфейс для манипуляции редактором текста, так что плагины могут выполнять произвольные операции с текстом.&lt;br /&gt;
&lt;br /&gt;
&lt;a name="n2"&gt;&lt;/a&gt;&lt;h1&gt;Управление меню&lt;/h1&gt;
Итак, нам понадобится, во-первых, интерфейс для создания и удаления пунктов меню, методы которого будет вызывать плагин. Выглядит он очень просто:
&lt;pre class="brush:delphi"&gt;type
  IMenuManager = interface
  ['{216082F8-8FE8-4B51-83E5-C8324452AD18}']
    function CreateMenuItem: IMenuItem; safecall;
    procedure DeleteMenuItem(var AItem: IMenuItem); safecall;
  end;&lt;/pre&gt;
Я поясню выбор &lt;code&gt;var&lt;/code&gt; для параметра &lt;code&gt;AItem&lt;/code&gt; в методе &lt;code&gt;DeleteMenuItem&lt;/code&gt; чуть позднее.&lt;br /&gt;
&lt;br /&gt;
Далее, нам нужен интерфейс для самого пункта меню. Предлагаю такой вариант:
&lt;pre class="brush:delphi"&gt;type
  IMenuItem = interface
  ['{B1A0A830-532B-4573-AE19-33EAA4D76096}']
  // private
    function GetCaption: WideString; safecall;
    function GetChecked: BOOL; safecall;
    function GetEnabled: BOOL; safecall;
    function GetHint: WideString; safecall;
    procedure SetCaption(const AValue: WideString); safecall;
    procedure SetChecked(const AValue: BOOL); safecall;
    procedure SetEnabled(const AValue: BOOL); safecall;
    procedure SetHint(const AValue: WideString); safecall;
  // public
    property Caption: WideString read GetCaption write SetCaption;
    property Checked: BOOL read GetChecked write SetChecked;
    property Enabled: BOOL read GetEnabled write SetEnabled;
    property Hint: WideString read GetHint write SetHint;
    procedure RegisterExecuteHandler(const AEventHandler: INotifyEvent); safecall;
  end;&lt;/pre&gt;
Этот интерфейс позволит нам изменять основные свойства пункта меню. Вы можете добавить что-то ещё или что-то убрать - это ваш выбор. Метод &lt;code&gt;RegisterExecuteHandler&lt;/code&gt; служит как замена события &lt;code&gt;OnClick&lt;/code&gt;. Я подробнее продемонстрирую его работу позже. &lt;code&gt;INotifyEvent&lt;/code&gt; же объявлен так:
&lt;pre class="brush:delphi"&gt;type
  INotifyEvent = interface
  ['{80B8A93C-B69B-49BA-BD7C-749629C9D97B}']
    procedure Execute(Sender: IInterface); safecall;
  end;&lt;/pre&gt;
Это что касается заголовочников. Теперь по реализации.&lt;br /&gt;
&lt;br /&gt;
Давайте начнём с конца. Кто должен реализовывать &lt;code&gt;IMenuItem&lt;/code&gt;? Ну, это точно не менеджер плагинов - ведь он один, а пунктов меню много. И это точно не обёртка плагина в менеджере плагинов - их хоть и много, но у нас есть всего одна обёртка для плагина, в то время как один плагин может создать несколько пунктов меню.&lt;br /&gt;
&lt;br /&gt;
Наиболее разумно будет выбрать сам элемент меню для реализации &lt;code&gt;IMenuItem&lt;/code&gt;. Делается это очень просто:
&lt;pre class="brush:delphi"&gt;type
  TPluginMenuItem = class(TMenuItem, IUnknown, IMenuItem)
  private
    FClickHandler: INotifyEvent;
  protected
    // IMenuItem
    function GetCaption: WideString; safecall;
    function GetChecked: BOOL; safecall;
    function GetEnabled: BOOL; safecall;
    function GetHint: WideString; safecall;
    procedure SetCaption(const AValue: WideString); safecall;
    procedure SetChecked(const AValue: BOOL); safecall;
    procedure SetEnabled(const AValue: BOOL); safecall;
    procedure SetHint(const AValue: WideString); safecall;
    procedure RegisterExecuteHandler(const AEventHandler: INotifyEvent); safecall;
  public
    procedure Click; override;
  end;

{ TPluginMenuItem }

procedure TPluginMenuItem.Click;
begin
  inherited; // вызываем стандартную обработку

  // Вызываем плагин
  if Enabled and Assigned(FClickHandler) then
    FClickHandler.Execute(Self);
end;

function TPluginMenuItem.GetCaption: WideString;
begin
  Result := Caption;
end;

function TPluginMenuItem.GetChecked: BOOL;
begin
  Result := Checked;
end;

function TPluginMenuItem.GetEnabled: BOOL;
begin
  Result := Enabled;
end;

function TPluginMenuItem.GetHint: WideString;
begin
  Result := Hint;
end;

procedure TPluginMenuItem.SetCaption(const AValue: WideString);
begin
  Caption := AValue;
end;

procedure TPluginMenuItem.SetChecked(const AValue: BOOL);
begin
  Checked := AValue;
end;

procedure TPluginMenuItem.SetEnabled(const AValue: BOOL);
begin
  Enabled := AValue;
end;

procedure TPluginMenuItem.SetHint(const AValue: WideString);
begin
  Hint := AValue;
end;

procedure TPluginMenuItem.RegisterExecuteHandler(const AEventHandler: INotifyEvent);
begin
  FClickHandler := AEventHandler;
end;&lt;/pre&gt;
Как видите, этот код тривиален - простой переходник &lt;code&gt;TMenuItem&lt;/code&gt; &amp;lt;-&amp;gt; &lt;code&gt;IMenuItem&lt;/code&gt;.&lt;br /&gt;
&lt;br /&gt;
Далее, эти самые элементы меню кто-то должен создавать. Понятно, что делаться это будет по указке плагина, но внутри программы эти пункты меню должен создавать код программы. Кто это будет? В принципе, вот тут уже этим мог бы заниматься менеджер программы. Но тут есть два "но":
&lt;ol&gt; 
&lt;li&gt;Мы делаем код менеджер плагинов зависимым от нашей программы. Я уже сделал такую "плохую вещь" в &lt;a title="Разработка системы плагинов в Delphi, часть 2: разработка API" href="http://www.gunsmoker.ru/2012/01/delphi-2-api.html"&gt;прошлый раз&lt;/a&gt; - введением свойства &lt;code&gt;FilterIndex&lt;/code&gt;. Тогда я пояснил, что это - "плохо", но выбрал этот путь как самый простой. Здесь ситуация аналогична - можно сделать быстро и грязно, а можно... сейчас посмотрим как.&lt;/li&gt;
&lt;li&gt;Недостаток два - это то, что менеджер плагинов будет заниматься не своими обязанностями. Какова задача менеджера плагинов? Служить менеджером плагинов, конечно же! Управление меню - это не обслуживание плагинов. Это - обслуживание интерфейса пользователя. А какой класс занимается обслуживанием UI? Конечно же, форма!&lt;/li&gt;
&lt;/ol&gt; 
Итак, как вы уже поняли - подходящим кандидатом для реализации &lt;code&gt;IMenuManager&lt;/code&gt; будет форма (в моём примере - &lt;code&gt;TMainForm&lt;/code&gt;):
&lt;pre class="brush:delphi"&gt;type
  TMainForm = class(TForm, IMenuManager)
  ...
  protected
    // IMenuManager
    function CreateMenuItem: IMenuItem; safecall;
    procedure DeleteMenuItem(var AItem: IMenuItem); safecall;
  end;

...

implementation

...

function TMainForm.CreateMenuItem: IMenuItem;
var
  MI: TPluginMenuItem;
begin
  MI := TPluginMenuItem.Create(miPlugins);
  Result := MI;
  miPlugins.Add(MI);

  miPlugins.Visible := True;
end;

procedure TMainForm.DeleteMenuItem(var AItem: IMenuItem);

  function GetComponent(var AItem: IMenuItem): TPluginMenuItem;
  var
    Internal: IInterfaceComponentReference;
  begin
    if not Supports(AItem, IInterfaceComponentReference, Internal) then
      Assert(False);
    Result := Internal.GetComponent as TPluginMenuItem;
    Internal := nil;
    AItem := nil;
  end;

begin
  GetComponent(AItem).Free;

  miPlugins.Visible := (miPlugins.Count &gt; 0);
end;

...

end.&lt;/pre&gt;
Вот тут начинается самое интересное. Видите ли, элемент меню - это компонент (наследник &lt;code&gt;TComponent&lt;/code&gt;). Это означает, что он удаляется явно - вызовом деструктора, а вовсе не тогда, когда исчезнет последняя ссылка на него. С другой стороны, использование интерфейсов диктует другую идеологию. Это классическая проблема стыкования двух различных механизмов управления временем жизни: ручного и автоматического на базе подсчёта ссылок. Для этой проблемы есть несколько решений.&lt;br /&gt;
&lt;br /&gt;
Прежде всего замечу, что удалять пункт меню по удалению последней ссылки на него будет не самой удачной идеей. Почему? Потому что чаще всего плагину нужно просто создать пункт меню, настроить его и... всё. Дальше пункт меню должен жить, но плагину нет нужды хранить на него ссылку, потому что ему не нужно ничего менять. Поэтому хорошо бы дать ему возможность "забыть" ссылку без удаления пункта меню.&lt;br /&gt;
&lt;br /&gt;
Метод номер два заключается в введении метода явного удаления - аналога деструктора в интерфейс. Скажем, &lt;code&gt;Delete&lt;/code&gt;. Когда этот метод вызывается - пункт меню надо удалить (даже если ссылки на интерфейс ещё не обнулены). После вызова метода пункт меню будет удалён, а интерфейс останется жить, но любое обращение к его методам вызовет ошибку "нет пункта меню". Так что единственное, что с этим интерфейсом можно будет сделать - освободить (уменьшить число ссылок). Этот способ довольно привлекателен, но имеет и минусы: вам нужно &lt;b&gt;два&lt;/b&gt; объекта. Один - это пункт меню. Этот объект будет удаляться при вызове метода &lt;code&gt;Delete&lt;/code&gt;. Второй объект - это реализатор интерфейса. Он удаляется при уменьшении счётчика ссылок до нуля. Это не может быть тот же самый объект, потому что первый объект нужно удалять по вызову &lt;code&gt;Delete&lt;/code&gt; - когда счётчик ссылок ещё не равен нулю.&lt;br /&gt;
&lt;br /&gt;
Я предлагаю компромиссный способ 3, при котором нам понадобится один объект (&lt;code&gt;TPluginMenuItem&lt;/code&gt;), но при этом для плагина остаётся возможность "забыть" ссылку без удаления пункта меню. Способ заключается в том, что мы вводим функцию удаления пункта меню в менеджер меню. Функция принимает ссылку на пункт меню, который нужно удалить. При этом ставится условие, что это должна быть последняя ссылка на пункт меню. Тогда: если плагину не нужен пункт меню после его создания - он просто забудет ссылку на него и ничего не произойдёт, пункт меню удалён не будет. Если же плагину в итоге нужно будет удалить пункт меню, он сохраняет ссылку на него, а для удаления он сначала удаляет все свои дополнительные ссылки на пункт меню (если он их создавал), после чего передаёт оставшуюся (последнюю) ссылку в функцию удаления. Функция удаления удаляет пункт меню и ссылку одновременно - таким образом, два механизма управления временем жизни остаются согласованными.&lt;br /&gt;
&lt;br /&gt;
Конечно же, вы можете использовать другой метод. Я подчеркну, что я описываю &lt;b&gt;какой-то&lt;/b&gt; способ реализации плагинов. Он не является единственным правильным. Тут можно делать как угодно. Вы &lt;b&gt;проектируете&lt;/b&gt; API. Это означает, что будет так, как скажете вы. Это означает, что тут всё зависит от ваших потребностей и фантазии. &lt;br /&gt;
&lt;br /&gt;
Ещё замечу, что в указанной модели подразумевается, что пункты меню, созданные плагином, будут отслеживаться и удаляться при выгрузке плагина. Поскольку у меня не сделано отслеживание пунктов меню, то функциональность "забыть ссылку без удаления" сейчас работать не будет. Вернее, работать-то она будет, но пункты плагина останутся висеть в меню вечно. Я оставлю доработку на следующий раз, где поговорю о вопросах общения плагинов между собой (это тесно связанный с указанной темой вопрос), а пока пусть плагины за собой чистят явно.&lt;br /&gt;
&lt;br /&gt;
В рамках же указанного подхода остаётся лишь сделать замечание по реализации этой самой функции удаления пункта меню (&lt;code&gt;DeleteMenuItem&lt;/code&gt;). Во-первых, вы уже поняли, почему у её параметра стоит модификатор &lt;code&gt;var&lt;/code&gt;, а не что-то другое: чтобы можно было обнулить ссылку. Далее, поскольку мы удаляем объект &lt;code&gt;TMenuItem&lt;/code&gt; (с ручным управлением временем жизни), то мы не можем просто обнулить ссылку - это не удалит объект. Нам нужно получить сам объект и вызвать его деструктор (кстати, ссылку надо обнулить &lt;b&gt;до&lt;/b&gt; вызова деструктора). Окей, как можно получить ссылку на реализующий объект по интерфейсу? Есть аж четыре способа:
&lt;ol&gt;
&lt;li&gt;Перебор: перебрать все пункты меню и найти с подходящим интерфейсом.&lt;/li&gt;
&lt;li&gt;&lt;a title="Хак №7: Interface в Object" href="http://www.transl-gunsmoker.ru/2010/09/7-interface-object.html"&gt;Грязный хак&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Дополнительный служебный интерфейс, возвращающий &lt;code&gt;Self&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a title="Преобразование ссылки на интерфейс для реализации класса в Delphi 2010" href="http://habrahabr.ru/blogs/delphi/85504/"&gt;Новые возможности интерфейсов в Delphi 2010 и выше&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;
Перебор - это долго, &lt;a title="Что такое хак? (и почему его не стоит использовать)" href="http://www.transl-gunsmoker.ru/2010/07/blog-post_06.html"&gt;хак - это грязно&lt;/a&gt;, Delphi 2010 используют не все (к сожалению), поэтому выход - номер 3.&lt;br /&gt;
&lt;br /&gt;
Поскольку я делал этот пример в Delphi XE, то у меня уже есть весь готовый код для этого. Если же вы используете более старую версию Delphi, то вам может понадобится изменить код так:
&lt;pre class="brush:delphi"&gt;type
  IInterfaceComponentReference = interface
  ['{41D23E9C-0974-4ED4-BBE8-4375D65E1129}']
    function GetComponent: TObject;
  end;

...

type
  TPluginMenuItem = class(TMenuItem, IUnknown, IInterfaceObjectReference, IMenuItem)
  ...
  protected
    function GetComponent: TObject;
    ... 
  public
    ...
  end;

function TPluginMenuItem.GetComponent: TObject;
begin
  Result := Self;
end;&lt;/pre&gt;
Код достаточно очевиден: мы ввели скрытый интерфейс для служебных целей. Интерфейс просто выставляет нам &lt;code&gt;Self&lt;/code&gt; - что и будет объектом, который реализует интерфейс.&lt;br /&gt;
&lt;br /&gt;
Осталось пояснить, почему я выделил подпрограмму &lt;code&gt;GetComponent&lt;/code&gt;. Вы могли бы подумать, что я сделал это &lt;a title="НЕ пишите комментарии!" href="http://www.gunsmoker.ru/2010/07/blog-post_31.html"&gt;с точки зрения хорошего стиля&lt;/a&gt;, но это лишь часть правды. Дело тут в том, что мы должны освободить все ссылки на объект до его удаления. С &lt;code&gt;AItem&lt;/code&gt; вопросов нет - это последняя ссылка, нам это должен гарантировать плагин. Но мы работаем с интерфейсами в своём коде. И проблема тут в том, что при этом компилятор может создавать скрытые интерфейсные ссылки, которые освобождаются в момент выхода их из области видимости - т.е. в строчке "end;" этой процедуры. А это - после вызова деструктора. К этой проблеме есть два решения. Первое: просто просмотреть код и убедиться, что скрытых ссылок нет. Дело это не всегда тривиальное и тут можно легко ошибиться - особенно при дальнейшей модификации и поддержке кода. Поэтому я предлагаю более простое решение - вынести весь код по работе с интерфейсами в отдельную подпрограмму, вызываемую до удаления пункта меню. Таким образом, если скрытые ссылки и будут созданы - они будут удалены в момент завершения этой вложенной подпрограммы, что случится до удаления пункта меню.&lt;br /&gt;
&lt;br /&gt;
Так, что касается согласования двух моделей управления временем жизни - с этим всё. Замечу только, что у нас есть ещё форма, которая тоже, вроде бы, управляется вручную, но для неё указанная проблема не возникает - это потому, что форма живёт дольше, чем плагины (я добавил выгрузку плагинов в &lt;code&gt;OnDestroy&lt;/code&gt; формы - нет смысла делать иначе, поскольку плагины в нашей программе работают только с UI). Это означает, что время жизни формы больше времени жизни плагинов, они не пересекаются. Т.е. последняя ссылка на форму (менеджер меню) будет удалена до удаления формы - таким образом, никаких проблем, требующих решения, не возникает.&lt;br /&gt;
&lt;br /&gt;
Итак, теперь у нас есть готовый менеджер меню, который позволяет плагинам создавать пункты меню, менять их и реагировать на щелчки в них. Остаётся небольшой вопрос: как плагин получит доступ к менеджеру меню? Действительно: плагин инициализируется передачей ему менеджера плагинов, но никакой ссылки на менеджер меню там нет. Вот эту проблему сейчас нам и нужно решить. Делается это так (файл PluginManager.pas):
&lt;pre class="brush:delphi"&gt;type
  IPluginManager = interface
    ...
    procedure RegisterServiceProvider(const AProvider: IUnknown);
  end;

...

implementation

...

type
  TPluginManager = class(TInterfacedObject, IUnknown, IPluginManager, ICore)
  private
    ...
    FProviders: TInterfaceList;
  protected
    ...
    procedure RegisterServiceProvider(const AProvider: IUnknown);
    function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
  end;

...

constructor TPluginManager.Create;
begin
  ...
  FProviders := TInterfaceList.Create;
  ...
end;

destructor TPluginManager.Destroy;
begin
  FreeAndNil(FProviders);
  ...
end;

procedure TPluginManager.UnloadAll;
begin
  Finalize(FItems);
  FProviders.Clear;
end;

procedure TPluginManager.RegisterServiceProvider(const AProvider: IUnknown);
begin
  FProviders.Add(AProvider);
end;

function TPluginManager.QueryInterface(const IID: TGUID; out Obj): HResult;
var
  X: Integer;
begin
  Result := inherited QueryInterface(IID, Obj);
  if Failed(Result) then
  begin
    for X := 0 to FProviders.Count - 1 do
    begin
      Result := FProviders[X].QueryInterface(IID, Obj);
      if Succeeded(Result) then
        Break;
    end;
  end;
end;

...

end.&lt;/pre&gt;
И файл remain.pas:
&lt;pre class="brush:delphi"&gt;procedure TMainForm.FormCreate(Sender: TObject);
begin
  SetErrorMode(SetErrorMode(0) or SEM_NOOPENFILEERRORBOX or SEM_FAILCRITICALERRORS);
  Plugins.SetVersion(1);
  Plugins.RegisterServiceProvider(Self); // &lt;- добавлено
  Plugins.LoadPlugins(ExtractFilePath(ParamStr(0)) + 'Plugins', SPluginExt);
  BuildFilterList;

  ...
end;&lt;/pre&gt;
Если вы посмотрите внимательнее, то увидите, что весь трюк здесь заключается в подмене метода &lt;code&gt;QueryInterface&lt;/code&gt;: мы модифицируем его так, чтобы он возвращал не только интерфейсы самого объекта, но и ещё "чьи-то". Если вы помните, то эту технику мы уже использовали: в &lt;a title="Разработка системы плагинов в Delphi, часть 2: разработка API" href="http://www.gunsmoker.ru/2012/01/delphi-2-api.html"&gt;прошлый раз&lt;/a&gt; - подмена &lt;code&gt;QueryInterface&lt;/code&gt; в классе &lt;code&gt;TPlugin&lt;/code&gt;. Только в последнем примере мы ищем дополнительные интерфейсы не у кого-то конкретного, а в целом списке, позволяя при этом стороннему коду (стороннему - по отношению к менеджеру плагинов) производить регистрацию себя в качестве "поставщика интерфейсов". В итоге у нас получается так, что плагин может запрашивать у менеджера плагинов (ядра) что угодно, но сам менеджер плагинов понятия не имеет, что же спрашивает у него плагин - зато про это имеет понятие программа, которая и регистрирует в менеджере плагинов интерфейсы для раздачи их плагинам.&lt;br /&gt;
&lt;br /&gt;
С основной программой мы на этом закончили. Давайте кратко просуммируем сделанное: мы реализовали менеджер меню, научились согласовывать две модели управления временем жизни и реализовали "правильный" подход к менеджеру плагинов (регистрация функциональности ядра).&lt;br /&gt;
&lt;br /&gt;
Теперь давайте посмотрим на реализацию плагина. Поскольку редактор у нас пока не готов для общения с плагинами, то я пока что покажу простой пример плагина, который работает только с меню: пусть он создаст несколько пунктов меню и покажет, как он умеет с ними обращаться:
&lt;pre class="brush:delphi"&gt;library SimpleMenu;

uses
  Windows,
  SysUtils,
  Classes,
  ActiveX,
  AxCtrls,
  PluginAPI in 'PluginAPI\Headers\PluginAPI.pas';

{$R *.res}
{$E .rep}

type
  TPluginMenuItem = class(TObject, IUnknown, INotifyEvent)
  private
    FManager: IMenuManager;
    FItem: IMenuItem;
    FClick: TNotifyEvent;
  protected
    // IUnknown
    function QueryInterface(const IID: TGUID; out Obj): HResult; virtual; stdcall;
    function _AddRef: Integer; stdcall;
    function _Release: Integer; stdcall;
    // INotifyEvent
    procedure Execute(Sender: IInterface); safecall;
  public
    constructor Create(const ACore: ICore);
    destructor Destroy; override;
    property Item: IMenuItem read FItem;

    procedure Click; virtual;
    property OnClick: TNotifyEvent read FClick write FClick;
  end;

  TPlugin = class(TInterfacedObject, IUnknown)
  private
    FCore: ICore;
    FMenuItem1: TPluginMenuItem;
    FMenuItem2: TPluginMenuItem;
    FMenuItem3: TPluginMenuItem;
    procedure MenuItem1Click(Sender: TObject);
    procedure MenuItem2Click(Sender: TObject);
    procedure MenuItem3Click(Sender: TObject);
  public
    constructor Create(const ACore: ICore);
    destructor Destroy; override;
  end;

{ TPlugin }

constructor TPlugin.Create(const ACore: ICore);
begin
  inherited Create;
  FCore := ACore;
  Assert(FCore.Version &gt;= 1);

  FMenuItem1 := TPluginMenuItem.Create(FCore);
  FMenuItem2 := TPluginMenuItem.Create(FCore);
  FMenuItem3 := TPluginMenuItem.Create(FCore);

  FMenuItem1.Item.Caption := 'SimpleMenu: Отмечается галочкой';
  FMenuItem1.Item.Hint := 'Элемент №1 от плагина SimpleMenu';
  FMenuItem1.Item.Checked := True;
  FMenuItem1.OnClick := MenuItem1Click;

  FMenuItem2.Item.Caption := 'SimpleMenu: Показ сообщения';
  FMenuItem2.Item.Hint := 'Элемент №2 от плагина SimpleMenu';
  FMenuItem2.OnClick := MenuItem2Click;

  FMenuItem3.Item.Caption := 'SimpleMenu: Убрать все мои пункты меню';
  FMenuItem3.Item.Hint := 'Элемент №3 от плагина SimpleMenu';
  FMenuItem3.OnClick := MenuItem3Click;
end;

destructor TPlugin.Destroy;
begin
  FreeAndNil(FMenuItem3);
  FreeAndNil(FMenuItem2);
  FreeAndNil(FMenuItem1);
  inherited;
end;

procedure TPlugin.MenuItem1Click(Sender: TObject);
begin
  FMenuItem1.Item.Checked := not FMenuItem1.Item.Checked;
end;

procedure TPlugin.MenuItem2Click(Sender: TObject);
begin
  MessageBox(0, 'Привет от плагина SimpleMenu!', 'SimpleMenu', MB_OK or MB_ICONINFORMATION);
end;

procedure TPlugin.MenuItem3Click(Sender: TObject);
begin
  FreeAndNil(FMenuItem1);
  FreeAndNil(FMenuItem2);
  FMenuItem3.Item.Enabled := False;
end;

{ TPluginMenuItem }

constructor TPluginMenuItem.Create(const ACore: ICore);
begin
  inherited Create;
  if not Supports(ACore, IMenuManager, FManager) then
    Assert(False);

  FItem := FManager.CreateMenuItem;

  FItem.Caption := 'PluginMenuItem';
  FItem.Hint := '';
  FItem.Enabled := True;
  FItem.Checked := False;
  FItem.RegisterExecuteHandler(Self);
end;

destructor TPluginMenuItem.Destroy;
begin
  FManager.DeleteMenuItem(FItem);
  FManager := nil;
  inherited;
end;

procedure TPluginMenuItem.Execute(Sender: IInterface);
begin
  Click;
end;

procedure TPluginMenuItem.Click;
begin
  if FItem.Enabled then
  begin
    if Assigned(FClick) then
      FClick(Self);
  end;
end;

function TPluginMenuItem.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
  if GetInterface(IID, Obj) then
    Result := S_OK
  else
    Result := E_NOINTERFACE;
end;

function TPluginMenuItem._AddRef: Integer;
begin
  Result := -1;   // -1 indicates no reference counting is taking place
end;

function TPluginMenuItem._Release: Integer;
begin
  Result := -1;   // -1 indicates no reference counting is taking place
end;

// _________________________________________________________________

function Init(const ACore: ICore): IUnknown; safecall;
begin
  Result := TPlugin.Create(ACore);
end;

procedure Done; safecall;
begin

end;

exports
  Init name SPluginInitFuncName,
  Done name SPluginDoneFuncName;

end.&lt;/pre&gt;
Давайте начнём разбор кода. Во-первых, мы ввели вспомогательный класс &lt;code&gt;TPluginMenuItem&lt;/code&gt;. Зачем он нужен? Дело в том, что мы хотим создать 3 пункта меню. При этом нам, конечно же, хотелось бы реагировать на щелчки по всем этим пунктам меню - т.е. нам нужно три обработчика событий &lt;code&gt;OnClick&lt;/code&gt;. За это событие у нас отвечает интерфейс &lt;code&gt;INotifyEvent&lt;/code&gt; с (единственным) методом &lt;code&gt;Execute&lt;/code&gt;. С другой стороны, &lt;code&gt;TPlugin&lt;/code&gt; - он у нас один, и он не может "реализовывать трижды" один и тот же интерфейс. Поэтому нам надо три объекта. Три &lt;code&gt;TPlugin&lt;/code&gt; мы сделать не можем (плагин - он же один), поэтому мы и ввели &lt;code&gt;TPluginMenuItem&lt;/code&gt; - это позволяет нам иметь несколько объектов, по одному на каждый пункт меню. Вспомогательный объект берёт на себя реализацию интерфейса, выставляя нам наружу удобный &lt;code&gt;OnClick&lt;/code&gt; - прямо как в Delphi. Ну и, конечно же, объект предоставляет методы по управлению пунктом меню. Замечу, что тут я поленился и сделал просто &lt;code&gt;property Item: IMenuItem&lt;/code&gt; вместо копирования функциональности - что сэкономило мне код, но привело к не слишком удобному обращению вида &lt;code&gt;FMenuItem2.&lt;b&gt;Item.&lt;/b&gt;Caption&lt;/code&gt;. И снова я повторюсь, что вы можете делать как угодно - на свой вкус.&lt;br /&gt;
&lt;br /&gt;
Когда у нас есть вспомогательный объект (код которого тривиален), мы уже легко можем создавать пункты меню - работа с ними происходит в точности так же, как вы бы работали с пунктами меню в приложении Delphi без плагинов. Вы создали пункт меню, настроили его и назначили обработчик.&lt;br /&gt;
&lt;br /&gt;
Что ещё хотелось бы тут отметить: возьмите на заметку то, в каком состоянии создаётся пункт меню. Например, такой вопрос: какой заголовок имеет пункт меню после вызова &lt;code&gt;CreateMenuItem&lt;/code&gt;? Это - вопрос для фиксации в документации (это я вроде как напоминаю). Вы должны явно это указать. Конечно, вы можете сказать: "состояние пункта меню после создания не определено, вы должны явно инициализировать все его свойства" - что снимет с вас (как разработчика программы) всякую ответственность, но также приведёт к огромному дублированию кода - ведь тогда &lt;i&gt;каждому&lt;/i&gt; плагину при создании &lt;i&gt;каждого&lt;/i&gt; пункта меню придётся делать, к примеру, Enabled := True (ведь вы же чёрным по белому сказали "после создания - может быть любым"). Поэтому лучше бы, конечно, всё же явно установить состояние и придерживаться его в дальнейшем.&lt;br /&gt;
&lt;br /&gt;
Второй момент - в этом примере я оставил предыдущие наработки. В частности, в программе есть плагины экспорта и импорта, и я приложил старые плагины для работы с RTF. Это показывает широкие возможности системы плагинов - работу с разнотипными плагинами. Кстати, обратите внимание, что у нас, по сути, вообще нет такого понятия "тип плагина". У нас всё упирается в то, какие интерфейсы поддерживает плагин. К примеру, один и тот же плагин теоретически может быть и плагином импорта и плагином экспорта (если только вы явно не запретите такую комбинацию в коде или в документации, как я указывал это в прошлый раз) и при этом же создавать пункты меню (например, для вызова диалога настроек импорта).&lt;br /&gt;
&lt;br /&gt;
&lt;a title="Скачать PluginAPI_4.zip" href="http://dl.dropbox.com/u/201788/Projects/Demos/PluginAPI_4.zip"&gt;Скачать пример&lt;/a&gt;.
&lt;br /&gt;
&lt;a name="n3"&gt;&lt;/a&gt;&lt;h1&gt;Управление редактором&lt;/h1&gt;
Когда мы реализовали меню и подробно обсудили все моменты, у вас не должно возникнуть никаких сложностей с редактором меню. Как позволить плагинам работать с редактором? Реализовать интерфейс, конечно же! Кто должен реализовывать интерфейс? Ну, либо форма, либо сам редактор. А как плагины увидят этот интерфейс? А мы его зарегистрируем в менеджере плагинов.&lt;br /&gt;
&lt;br /&gt;
Как видите, всё практически дословно повторяет предыдущий пример. Поэтому я кратко покажу код без объяснений. Полный исходный код можно скачать по ссылке в конце статьи.&lt;br /&gt;
&lt;br /&gt;
Как будет выглядеть интерфейс? Например, так:
&lt;pre class="brush:delphi"&gt;type
  TEditorSearchTypes = DWORD;

const
  esfWholeWord = 0;
  esfMatchCase = 1;

type
  IEditor = interface
  ['{F82E46C3-A744-4137-B9A0-242E50CC0041}']
  // private
    function GetText: WideString; safecall;
    procedure SetText(const AValue: WideString); safecall;
    function GetSelText: WideString; safecall;
    procedure SetSelText(const AValue: WideString); safecall;
    function GetSelStart: Integer; safecall;
    procedure SetSelStart(const AValue: Integer); safecall;
    function GetSelLength: Integer; safecall;
    procedure SetSelLength(const AValue: Integer); safecall;
    function GetModified: BOOL; safecall;
    function GetCanUndo: BOOL; safecall;
    function GetCaretPos: TPoint; safecall;
    procedure SetCaretPos(const AValue: TPoint); safecall;

  // public
    property Text: WideString read GetText write SetText;
    property SelText: WideString read GetSelText write SetSelText;
    property SelStart: Integer read GetSelStart write SetSelStart;
    property SelLength: Integer read GetSelLength write SetSelLength;
    procedure SelectAll; safecall;
    property Modified: BOOL read GetModified;
    property CanUndo: BOOL read GetCanUndo;
    procedure Undo; safecall;
    procedure ClearUndo; safecall;
    procedure Clear; safecall;
    procedure ClearSelection; safecall;
    procedure CopyToClipboard; safecall;
    procedure CutToClipboard; safecall;
    procedure PasteFromClipboard; safecall;
    property CaretPos: TPoint read GetCaretPos write SetCaretPos;
    function FindText(const SearchStr: WideString; const StartPos, Length: Integer; Options: TEditorSearchTypes): Integer; safecall;
  end;&lt;/pre&gt;
(обсуждение TEditorSearchTypes мне хотелось бы оставить на другой раз)&lt;br /&gt;
&lt;br /&gt;
Как будет выглядеть реализация? Например, так:
&lt;pre class="brush:delphi"&gt;type
  TMainForm = class(TForm, IEditor, IMenuManager)
  ...
  protected
    ...
    // IEditor
    function GetText: WideString; safecall;
    procedure SetText(const AValue: WideString); safecall;
    function GetSelText: WideString; safecall;
    procedure SetSelText(const AValue: WideString); safecall;
    function GetSelStart: Integer; safecall;
    procedure SetSelStart(const AValue: Integer); safecall;
    function GetSelLength: Integer; safecall;
    procedure SetSelLength(const AValue: Integer); safecall;
    function GetModified: BOOL; safecall;
    function GetCanUndo: BOOL; safecall;
    function GetCaretPos: TPoint; safecall;
    procedure SetCaretPos(const AValue: TPoint); safecall;
    procedure SelectAll; safecall;
    procedure Undo; safecall;
    procedure ClearUndo; safecall;
    procedure Clear; safecall;
    procedure ClearSelection; safecall;
    procedure CopyToClipboard; safecall;
    procedure CutToClipboard; safecall;
    procedure PasteFromClipboard; safecall;
    function FindText(const SearchStr: WideString; const StartPos, Length: Integer; Options: TEditorSearchTypes): Integer; safecall;
  end;

...

implementation

...

function TMainForm.GetText: WideString;
begin
  Result := Editor.Text;
end;

procedure TMainForm.SetText(const AValue: WideString);
begin
  Editor.Text := AValue;
end;

// Позвольте мне не приводить остальные методы - они однотипны

...

// Единственное исключение:

function TMainForm.FindText(const SearchStr: WideString; const StartPos, Length: Integer; Options: TEditorSearchTypes): Integer;
var
  Opts: TSearchTypes;
begin
  Opts := [];
  if (Options and esfWholeWord) &lt;&gt; 0 then
    Include(Opts, stWholeWord);
  if (Options and esfMatchCase) &lt;&gt; 0 then
    Include(Opts, stMatchCase);
  Result := Editor.FindText(SearchStr, StartPos, Length, Opts);
end;

...

end.&lt;/pre&gt;
Как будет выглядеть регистрация интерфейса в менеджере плагинов? А никак - она у нас уже есть:
&lt;pre class="brush:delphi"&gt;procedure TMainForm.FormCreate(Sender: TObject);
begin
  SetErrorMode(SetErrorMode(0) or SEM_NOOPENFILEERRORBOX or SEM_FAILCRITICALERRORS);
  Plugins.SetVersion(1);
  Plugins.RegisterServiceProvider(Self); // &lt;- тыц-тыц
  Plugins.LoadPlugins(ExtractFilePath(ParamStr(0)) + 'Plugins', SPluginExt);
  BuildFilterList;

  ...
end;&lt;/pre&gt;
Обратите внимание, что мы регистрируем не конкретный интерфейс, а объект - поставщик интерфейсов. Т.е. единожды зарегистрировав форму, мы отдаём плагинам все её интерфейсы. Т.е. теперь нам достаточно просто реализовывать в форме интерфейсы - и они автоматически будут доступны плагинами.&lt;br /&gt;
&lt;br /&gt;
Теперь сам плагин:
&lt;pre class="brush:delphi"&gt;library HTMLConvert;

uses
  Windows,
  SysUtils,
  Classes,
  ActiveX,
  AxCtrls,
  PluginAPI in 'PluginAPI\Headers\PluginAPI.pas';

{$R *.res}
{$E .rep}

type
  TPlugin = class(TInterfacedObject, IUnknown, IDestroyNotify, INotifyEvent)
  private
    FCore: ICore;
    FMenuItem: IMenuItem;
    procedure Delete; safecall;
    procedure Execute(Sender: IInterface); safecall;
  public
    constructor Create(const ACore: ICore);
    destructor Destroy; override;
  end;

{ TPlugin }

constructor TPlugin.Create(const ACore: ICore);
var
  Manager: IMenuManager;
begin
  inherited Create;
  FCore := ACore;
  Assert(FCore.Version &gt;= 1);

  if Supports(FCore, IMenuManager, Manager) then
  begin
    FMenuItem := Manager.CreateMenuItem;
    FMenuItem.Caption := 'Вставить дату';
    FMenuItem.Hint := 'Вставляет в документ текущую дату и время в местном формате';
    FMenuItem.Enabled := True;
    FMenuItem.Checked := False;
    FMenuItem.RegisterExecuteHandler(Self);
  end;
end;

procedure TPlugin.Delete;
var
  Manager: IMenuManager;
begin
  if Assigned(FMenuItem) and
     Supports(FCore, IMenuManager, Manager) then
    Manager.DeleteMenuItem(FMenuItem);
end;

destructor TPlugin.Destroy;
begin
  Delete;
  inherited;
end;

procedure TPlugin.Execute(Sender: IInterface);
var
  Editor: IEditor;
begin
  if not Supports(FCore, IEditor, Editor) then
    Exit;

  Editor.SelText := DateTimeToStr(Now);
end;

// _________________________________________________________________

function Init(const ACore: ICore): IUnknown; safecall;
begin
  Result := TPlugin.Create(ACore);
end;

procedure Done; safecall;
begin

end;

exports
  Init name SPluginInitFuncName,
  Done name SPluginDoneFuncName;

end.&lt;/pre&gt;
Этот пример ещё проще предыдущего: у нас всего один пункт меню, а значит нам не нужен вспомогательный объект. Реакцию на щелчок может взять на себя сам &lt;code&gt;TPlugin&lt;/code&gt;.&lt;br /&gt;
&lt;br /&gt;
Единственный интересный момент тут - новый интерфейс &lt;code&gt;IDestroyNotify&lt;/code&gt;:
&lt;pre class="brush:delphi"&gt;type
  IDestroyNotify = interface
  ['{B50C8ABE-A10A-4BD9-AA17-0311326FE1A6}']
    procedure Delete; safecall;
  end;&lt;/pre&gt;
Я думаю, вы узнали его - я говорил про этот метод выше, хотя и выбрал в тот раз другой способ. Как я говорил, этот способ используется для разрешения проблемы циклических ссылок.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Упражнение:&lt;/b&gt; где в этом примере циклическая ссылка? Зачем понадобился интерфейс &lt;code&gt;IDestroyNotify&lt;/code&gt; в этом примере? И почему мы обошлись без него в предыдущем плагине? В чём отличие двух ситуаций?&lt;br /&gt;
&lt;br /&gt;
Пару слов об интерфейсе редактора: в данном примере я сделал его практически зеркалом RichEdit-а из программы, но это вовсе не обязательно: вы можете добавлять методы, которых оригинальный объект не имеет. Подумайте, какие операции могут понадобится плагинам к вашей программе, и введите вспомогательные методы для этого.&lt;br /&gt;
&lt;br /&gt;
Сам же интерфейс описывается тривиально. Хочу лишь напомнить про установленные нами в &lt;a title="Разработка системы плагинов в Delphi" href="http://www.gunsmoker.ru/2011/12/delphi.html#n5"&gt;первой части&lt;/a&gt; правила. При создании интерфейсов нужно не забывать добавлять &lt;code&gt;safecall&lt;/code&gt;, нужно не забывать изменять типы данных (&lt;code&gt;String&lt;/code&gt; на &lt;code&gt;WideString&lt;/code&gt;, &lt;code&gt;Boolean&lt;/code&gt; на &lt;code&gt;BOOL&lt;/code&gt; и т.п.) - т.е. никаких Delphi-типов у вас быть не должно.&lt;br /&gt;
&lt;br /&gt;
И в заключение я хотел бы обсудить ещё вот какой вопрос. В последнем примере я создал интерфейс &lt;code&gt;IEditor&lt;/code&gt;, где ввёл всего несколько методов для управления редактором. Это означает, что возможности плагинов ограничиваются возможностями интерфейса, которого вы им выдаёте. Т.е. если вы хотите иметь мощные плагины - вы должны дать им много интерфейсов с кучей методов, которые выставят наружу внутренности вашей программы. Чем больше вы сделаете интерфейсов, тем мощнее будут плагины. В примере я схалявил, реализовав всего несколько методов для редактора. В частности, я не создал ни одного метода для управления атрибутами текста (жирный, курсив и т.п.).&lt;br /&gt;
&lt;br /&gt;
Плюсы этого подхода: вы явно контролируете то, что будет доступно плагинам. Кроме того, вид для плагинов не обязан отражать реальную структуру вашей программы (к примеру, в нашем примере менеджер меню и редактор реализовывались формой, хотя по факту это &lt;code&gt;TMainMenu&lt;/code&gt; и &lt;code&gt;TRichEdit&lt;/code&gt; - разные объекты). Минусы: вам нужно вручную писать код-переходник. И чем мощнее вы хотите сделать возможности плагинов, тем больше кода вам придётся писать.&lt;br /&gt;
&lt;br /&gt;
И если ваша программа обладает очень сложным пользовательским интерфейсом, а вы хотите дать плагинам полный доступ к нему, плюс к тому же работа с пользовательским интерфейсом программы будет основной задачей для плагинов - то полноценные плагины будут не самой удачной архитектурой. Я говорил про это во вступлении к &lt;a title="Разработка системы плагинов в Delphi" href="http://www.gunsmoker.ru/2011/12/delphi.html#n2"&gt;первой части&lt;/a&gt;. Более удачным выбором будут скрипты.&lt;br /&gt;
&lt;br /&gt;
А если вы всё же используете плагины в виде библиотек, а не скрипты, то помимо указанного способа (проектирование интерфейсов и написание кода-обёртки) есть ещё один способ: можно дать плагинам прямой доступ к какому-то элементу программы. К примеру, для нашего редактора это может быть либо описатель (свойство &lt;code&gt;Handle&lt;/code&gt;), либо уже готовый системный &lt;a title="IRichEditOle interface" href="http://msdn.microsoft.com/en-us/library/windows/desktop/bb774306(v=vs.85).aspx"&gt;интерфейс &lt;code&gt;IRichEditOle&lt;/code&gt;&lt;/a&gt;. Вы можете передать плагину одну из этих вещей, а плагин сможет работать с ней, используя системный API. Разумеется, вы не можете передавать плагину сам объект &lt;code&gt;TRichEdit&lt;/code&gt;, потому что это - класс языка Delphi, он не "кросс-языковен" (чёрт, как тут иначе сказать?).&lt;br /&gt;
&lt;br /&gt;
Плюсы такого подхода: ничего не надо писать, просто передайте описатель. Минусы: вы не сможете контролировать плагин. К примеру, плагин сможет просто скрыть редактор или вовсе его удалить. Потому что у него есть описатель редактора, а значит - полный и неконтролируемый доступ. С другой стороны, с подходом из примера выше плагин не сможет сделать это (скрытие или удаление редактора) - потому что мы не дали ему методов. Второй минус - сильная зависимость от реализации программы. К примеру, вот выставили мы плагинам наш редактор, который сейчас у нас - RichEdit. А в следующей версии программы мы поняли, что нам мало RichEdit-а. Поэтому мы заменили его на custom-контрол с собственной прорисовкой. Если мы используем свои интерфейсы - нет проблем, плагины даже не заметят подмены. А если мы передаём описатель редактора плагинам, то... опс, (старый) плагин ожидает увидеть RichEdit, а получает что-то непонятное.&lt;br /&gt;
&lt;br /&gt;
&lt;a href="http://dl.dropbox.com/u/201788/Projects/Demos/PluginAPI_5.zip" title="Скачать PluginAPI_5.zip"&gt;Скачать пример&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
На следующий раз у нас остаётся взаимодействие плагинов друг с другом, пользовательский интерфейс в плагинах, а также обработка ошибок и отладка.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1702873441071265539-7680018511720040678?l=www.gunsmoker.ru' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/GunSmokersBlog?a=fK4gfT7xayA:4R5KzqCh2_g:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GunSmokersBlog?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GunSmokersBlog?a=fK4gfT7xayA:4R5KzqCh2_g:4cEx4HpKnUU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GunSmokersBlog?i=fK4gfT7xayA:4R5KzqCh2_g:4cEx4HpKnUU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GunSmokersBlog?a=fK4gfT7xayA:4R5KzqCh2_g:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GunSmokersBlog?i=fK4gfT7xayA:4R5KzqCh2_g:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GunSmokersBlog?a=fK4gfT7xayA:4R5KzqCh2_g:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GunSmokersBlog?i=fK4gfT7xayA:4R5KzqCh2_g:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GunSmokersBlog?a=fK4gfT7xayA:4R5KzqCh2_g:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GunSmokersBlog?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GunSmokersBlog?a=fK4gfT7xayA:4R5KzqCh2_g:-BTjWOF_DHI"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GunSmokersBlog?i=fK4gfT7xayA:4R5KzqCh2_g:-BTjWOF_DHI" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/GunSmokersBlog/~4/fK4gfT7xayA" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.gunsmoker.ru/feeds/7680018511720040678/comments/default" title="Комментарии к сообщению" /><link rel="replies" type="text/html" href="http://www.gunsmoker.ru/2012/01/3.html#comment-form" title="Комментарии: 3" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1702873441071265539/posts/default/7680018511720040678?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1702873441071265539/posts/default/7680018511720040678?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/GunSmokersBlog/~3/fK4gfT7xayA/3.html" title="Разработка системы плагинов, часть 3: активные плагины" /><author><name>Александр Алексеев</name><uri>https://profiles.google.com/113168002104297556003</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-aUMttZQBsuI/AAAAAAAAAAI/AAAAAAAAC3Y/QuZ7K9t_WzE/s512-c/photo.jpg" /></author><thr:total>3</thr:total><feedburner:origLink>http://www.gunsmoker.ru/2012/01/3.html</feedburner:origLink></entry><entry gd:etag="W/&quot;D0cHQns-fSp7ImA9WhRVE00.&quot;"><id>tag:blogger.com,1999:blog-1702873441071265539.post-5777257077220706959</id><published>2012-01-08T00:18:00.000+04:00</published><updated>2012-01-11T22:03:53.555+04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-01-11T22:03:53.555+04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Delphi" /><category scheme="http://www.blogger.com/atom/ns#" term="Статья" /><title>Разработка системы плагинов в Delphi, часть 2: разработка API</title><content type="html">&lt;a href="http://www.gunsmoker.ru/2011/12/delphi.html" title="Разработка системы плагинов в Delphi"&gt;Первая часть&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
В этой части мы рассмотрим реализацию функциональности плагинов.&lt;br /&gt;
&lt;br /&gt;
Для начала я напомню, что в качестве демонстрационного приложения я использовал программу-пример из комплекта демок Delphi - RichEdit Demo. Программа представляет собой простенький текстовый редактор типа WordPad:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://2.bp.blogspot.com/-SRj9LD0b59o/Twh0qwmc2kI/AAAAAAAADTo/J0yGcErVjo4/s1600/RichEditDemo.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="259" src="http://2.bp.blogspot.com/-SRj9LD0b59o/Twh0qwmc2kI/AAAAAAAADTo/J0yGcErVjo4/s400/RichEditDemo.png" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
Напомню, что в &lt;a href="http://www.gunsmoker.ru/2011/12/delphi.html" title="Разработка системы плагинов в Delphi"&gt;прошлый раз&lt;/a&gt; мы создали в этой программе инфраструктуру для плагинов и написали пустой плагин. Теперь настало время для реализации чего-то полезного.&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;
&lt;h1&gt;Оглавление&lt;/h1&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="#n1" title="Спецификация"&gt;Спецификация&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#n2" title="Формализация в коде"&gt;Формализация&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#n3" title="Окончательное формирование контракта"&gt;Контракт&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#n4" title="Реализация"&gt;Реализация&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;br /&gt;
&lt;a href="" name="n1"&gt;&lt;/a&gt;&lt;h1&gt;Спецификация&lt;/h1&gt;
Да, я знаю, что это скучно. Но начать нужно именно с более-менее формального изложения того, что мы хотим получить от плагинов. В каком-то виде в дальнейшем это войдёт в вашу документацию по разработке плагинов, но сейчас нам нужно иметь что-то, от чего мы будем отталкиваться.&lt;br /&gt;
&lt;br /&gt;
Итак, что же нам можно сделать? В качестве примера я предлагаю разработать плагины импорта/экспорта. Посмотрите: если вы в программе выберите File/Save as или File/Open - вы увидите диалоги выбора файлов с двумя возможными форматами: RTF и TXT. Т.е. либо Rich Text, либо просто текст. Но что если мы хотим сохранять наш файл в HTML? Как насчёт DOC? Ну или хотя бы ODT? И экспорт в PDF был бы полезен...
Понятно, что в самой программе мы не можем заранее предусмотреть все возможные форматы файлов - ведь некоторые из форматов могут просто не существовать в момент создания программы, а появиться позже. Поскольку у нас нет машины времени, мы не можем предсказать, какие форматы будут полезны пользователям нашей программы.&lt;br /&gt;
&lt;br /&gt;
Поэтому я предлагаю ввести в программу плагины импорта и экспорта. Каждый плагин будет поддерживать один какой-то формат файлов, которого нет в списке по умолчанию (RTF и TXT). Плагин экспорта должен уметь создать такой файл по RTF тексту. Плагин импорта должен уметь распарсить входной файл и создать по нему RTF (т.е. преобразовать одно в другое).&lt;br /&gt;
&lt;br /&gt;
Понятно, что задачи это не всегда тривиальные, различные по сложности. К примеру, одно дело - сгенерировать HTML файл, а совсем другое - его распарсить. Некоторые задачи отличаются повышенной сложностью - например, импорт формата DOC. Который мало того, что является закрытым форматом (т.е. официально не описан), так ещё и обладает неимоверно сложной внутренней структорой.&lt;br /&gt;
&lt;br /&gt;
Как видите, эти задачи - "тяжеловесные". Они хорошо решаются на обычных языках программирования, где у вас есть возможность использовать мощные языковые средства, подключать готовые библиотеки кода, взаимодействовать с другими программами, но они слабо подходят для их решения на скриптовых языках ("легковесные плагины").&lt;br /&gt;
&lt;br /&gt;
Итак, сейчас я сформулировал "чего мы хотим". Теперь надо подумать про то, что нам может понадобится для реализации.&lt;br /&gt;
&lt;br /&gt;
Давайте введём правило, что готовые плагины экспорта/импорта наша программа будет автоматически загружать при запуске из подпапки Plugins. Для этого у нас уже есть готовая инфраструктура, разработанная в прошлой части. На самом деле, мы там реализовали даже больше чем надо - там есть и выборочная загрузка и т.п.&lt;br /&gt;
&lt;br /&gt;
Окей, что дальше? Тут надо рассуждать так: что должен делать плагин после своей загрузки? Ну, как минимум в диалоге сохранения/загрузки должны появится пункты "сохранить в формат XXX", где XXX - это формат, поддерживаемый плагином. Ага, значит плагин должен сообщить ядру две вещи:
&lt;br /&gt;
&lt;ol&gt;
&lt;li&gt;Расширение файла (для использования в фильтре). Например, "htm". Или маску файлов. Например, "*.htm".&lt;/li&gt;
&lt;li&gt;Читабельное название формата файлов (для показа в списке выбора файлов). Например, "Гипертекстовые документы (HTM/HTML)".&lt;/li&gt;
&lt;/ol&gt;
Отлично, первый шаг есть. Формализовать его кодом мы будем чуть позже. А сейчас будем думать дальше: что ещё потребуется от плагина? Ну, когда его пользователь выберет плагин в диалоге выбора файлов, ядро должно вызвать плагин для экспорта или импорта RTF. Это значит, что плагин экспорта должен предоставлять функцию экспорта, а плагин импорта - функцию импорта. Ядро вызовет эту функцию, когда пользователь нажмёт на кнопку "OK" в диалоге выбора файлов. Соответственно, ядро передаст туда как минимум имя файла, а также... что?&lt;br /&gt;
&lt;br /&gt;
Рассмотрим ситуацию с плагинами экспорта. У нас в программе есть Rich Edit. Нам нужно как-то передать его плагину, чтобы тот сохранил RTF-текст в какой-то свой формат. Как это можно сделать? Ну, можно тупо взять и передать сам Rich Edit. Это будет работать, но это - плохое решение. Почему? Потому что вы выставляете плагину свои внутренности. По идее, плагин занимается преобразованием RTF в другой формат. Нужен ему для этого визуальный редактор? Нет, ему нужен RTF-текст. Или вы можете посмотреть на это с такой стороны: предположим вы делаете консольный конвертер файлов. Будет ли в нём TRichEdit? Нет, откуда? Ведь у консольной программы нет GUI. Т.е. все эти мысли намекают вам, что передача RichEdit-а плагину - не самое удачное решение.&lt;br /&gt;
&lt;br /&gt;
Что же делать? Как нам ещё можно передать данные плагину? Как-как - с использованием &lt;a href="http://www.gunsmoker.ru/2011/11/blog-post_12.html#tstreamadapter" title="Сериализация: потоки данных"&gt;потоков данных&lt;/a&gt;, конечно же! RichEdit может сохранить своё содержание (RTF текст) в поток данных, а мы затем можем передать этот поток данных плагину. Плагин может прочитать RTF-текст и преобразовать его как душе угодно.&lt;br /&gt;
&lt;br /&gt;
Аналогичные рассуждения верны и для плагинов импорта, с той лишь разницей, что у них ввод и вывод поменяны местами.&lt;br /&gt;
&lt;br /&gt;
Примечание: аналогичным образом, вместо имени файла можно использовать потоки данных. Таким образом, ядро может передавать плагину входной поток данных и выходной поток данных. Задача плагина - преобразовать одно в другое. А уж то, к чему привязывать эти потоки данных (к файлу ли или RichEdit-у на форме) - забота ядра, а не плагина. Тем не менее, в примере я буду использовать поток с одной стороны и имя файла с другой стороны.&lt;br /&gt;
&lt;br /&gt;
Что ещё? Ну вроде как и всё. Этого будет достаточно для реализации простых плагинов иморта и экспорта. Сейчас мы только что набросали черновую спецификацию работы наших плагинов. Как видите, это было несложно.&lt;br /&gt;
&lt;br /&gt;
&lt;a name="n2"&gt;&lt;/a&gt;&lt;h1&gt;Формализация в коде&lt;/h1&gt;
Теперь по этому словесному описанию надо создать код.&lt;br /&gt;
&lt;br /&gt;
Предварительно я напомню, что здесь и далее я буду писать заголовочники сразу в виде .pas файлов, но в реальности вам нужно создавать их в редакторе библиотеки типов, если вы хотите распространять их в другие языки. Я подробно разжевал эту мысль в первой части, так что не буду повторять её здесь.&lt;br /&gt;
&lt;br /&gt;
Итак, начнём с информационной части - нам нужно сообщить ядру расширение или маску, а также текстовое описание. Нет проблем:
&lt;pre class="brush:delphi"&gt;IPlugin = interface
['{D6B90C88-647D-4265-9052-2EE1BD274979}']
// private
  function GetMask: WideString;
  function GetDescription: WideString;
// public
  property Mask: WideString read GetMask;
  property Description: WideString read GetDescription;
end;&lt;/pre&gt;
Как видите, эта часть - общая для плагинов обоих типов (и экспорта и импорта).&lt;br /&gt;
&lt;br /&gt;
Далее - плагину экспорта нужна функция экспорта, а плагину импорта - функция импорта. Без проблем - как описали, так и пишем:
&lt;pre class="brush:delphi"&gt;IExportPlugin = interface
['{09428378-34BA-4326-8550-BF1CA72FDF53}']
  procedure ExportRTF(const ARTF: IStream; const AFileName: WideString); safecall;
end;

IImportPlugin = interface
['{6C85B093-7AAF-4EF0-B98E-D9DBDE950718}']
  procedure ImportRTF(const AFileName: WideString; const ARTF: IStream); safecall;
end;&lt;/pre&gt;
Как видите, наши слова из предыдущего раздела практически буквально ложатся на код.&lt;br /&gt;
&lt;br /&gt;
Вот, собственно, и всё. Заголовочники готовы. Когда вы словами описали, чего вы хотите, написать код - дело пяти секунд. Если же вы не вполне представляете себе, чего вы хотите, этот шаг будет нетривиален.&lt;br /&gt;
&lt;br /&gt;
&lt;a name="n3"&gt;&lt;/a&gt;&lt;h1&gt;Окончательное формирование контракта&lt;/h1&gt;
После того, как вы получили код в заголовочниках - вы получили "синтаксический контракт". Т.е. набор правил, которому нужно следовать плагину и ядру, чтобы успешно понять друг друга. Но это - ещё не всё. Теперь вам нужно создать "семантический контракт" - набор правил, говорящий про то, как использовать этот код.&lt;br /&gt;
&lt;br /&gt;
Я помещу этот раздел до начала реализации - просто чтобы у нас сразу был полностью готовый контракт ещё до того, как мы начнём писать код. В реальности же, ничего страшного если некоторые детали контракта будут выясняться лишь по ходу написания реализации. Главное - чтобы они вообще появлялись.&lt;br /&gt;
&lt;br /&gt;
Возможно, сказанное выше сейчас не очень понятно - что такое я имею в виду? На самом деле, тут ничего сложного нет. Я имею в виду, что вы должны явно написать правила типа таких: "имя файла не должно быть пустой строкой". Вам, как создателю всей архитектуры программы, это может быть очевидно - вы же знаете весь свой код. Но сторонним разработчикам это может быть не очевидно. Что если пустое имя файла в плагине импорта означает создание пустого документа? Хм, об этом вы не подумали. Или, вот, другой пример: если в функцию импорта/экспорта передали поток данных - надо ли его позиционировать на начало перед работой или нужно работать с текущей позиции? Тоже интересный вопрос. А вот и ещё один: имя файла, передаваемое в функцию импорта/экспорта - всегда ли оно абсолютное и полное? Не может ли это быть просто 'MyFileName.htm'? Ведь последнее означает, что функция импорта/экспорта уже больше не может свободно распоряжаться текущим каталогом. И такой вопрос: надо ли плагину экспорта выводить предупреждение о перезаписи файла, если файл-назначение уже существует? Или же предупреждение вынесет ядро?&lt;br /&gt;
&lt;br /&gt;
Я привёл пример нескольких вопросов, на которые вам, как разработчику системы плагинов, необходимо ответить. По каждому вопросу вам нужно принять решение (к примеру: "Имя файла не пусто и всегда содержит полный и абсолютный путь до файла. Файл для импорта всегда существует, а файл для экспорта может как существовать, так и не существовать. В первом случае функция экспорта обязана безусловно перезаписать файл, не выводя подтверждения. Поток данных всегда передаётся спозиционированным на своё начало, данные для работы плагина начинаются с начала потока данных и заканчиваются в его конце"). Вы можете выбирать различные решения указанных вопросов - но, какие бы решения вы ни выбрали, вы обязаны задокументировать их в документации к плагинам.&lt;br /&gt;
&lt;br /&gt;
К примеру, в вашей документации к плагинам могла бы быть такая статья:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;blockquote&gt;
&lt;h1&gt;
&lt;span lang="EN-US"&gt;IExportPlugin.ExportRTF&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;div class="MsoNormal" style="line-height: 13.5pt; margin-bottom: .0001pt; margin-bottom: 0cm;"&gt;
&lt;span style="color: #2a2a2a; font-family: &amp;quot;Segoe UI&amp;quot;,&amp;quot;sans-serif&amp;quot;; font-size: 9.0pt; mso-fareast-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-language: RU;"&gt;Экспортирует текст программы в сторонний формат файлов.&lt;/span&gt;&lt;span lang="EN-US" style="color: #2a2a2a; font-family: &amp;quot;Segoe UI&amp;quot;,&amp;quot;sans-serif&amp;quot;; font-size: 9.0pt; mso-ansi-language: EN-US; mso-fareast-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-language: RU;"&gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div class="MsoNormal" style="line-height: 13.5pt; margin-bottom: .0001pt; margin-bottom: 0cm;"&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div class="MsoNormal" style="margin-bottom: 0.0001pt;"&gt;
&lt;span style="color: #db7100; font-family: &amp;quot;Segoe UI Light&amp;quot;,&amp;quot;sans-serif&amp;quot;; font-size: 13.5pt; mso-bidi-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-language: RU;"&gt;Синтаксис&lt;/span&gt;&lt;span lang="EN-US" style="color: #db7100; font-family: &amp;quot;Segoe UI Light&amp;quot;,&amp;quot;sans-serif&amp;quot;; font-size: 13.5pt; mso-ansi-language: EN-US; mso-bidi-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-language: RU;"&gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div class="MsoNormal" style="margin-bottom: 0.0001pt; word-break: break-all;"&gt;
&lt;span lang="EN-US" style="font-family: Consolas; font-size: 10pt;"&gt;procedure ExportRTF(&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div class="MsoNormal" style="margin-bottom: 0.0001pt; word-break: break-all;"&gt;
&lt;span lang="EN-US" style="font-family: Consolas; font-size: 10pt;"&gt;&amp;nbsp; const
ARTF: IStream; &lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div class="MsoNormal" style="margin-bottom: 0.0001pt; word-break: break-all;"&gt;
&lt;span lang="EN-US" style="font-family: Consolas; font-size: 10pt;"&gt;&amp;nbsp; const
AFileName: WideString&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div class="MsoNormal" style="margin-bottom: 0.0001pt; word-break: break-all;"&gt;
&lt;span lang="EN-US" style="font-family: Consolas; font-size: 10pt;"&gt;); safecall;&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div class="MsoNormal" style="margin-bottom: 0.0001pt; word-break: break-all;"&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div class="MsoNormal" style="margin-bottom: 0.0001pt;"&gt;
&lt;span style="color: #db7100; font-family: &amp;quot;Segoe UI Light&amp;quot;,&amp;quot;sans-serif&amp;quot;; font-size: 13.5pt; mso-bidi-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-language: RU;"&gt;Параметры&lt;/span&gt;&lt;span lang="EN-US" style="color: #db7100; font-family: &amp;quot;Segoe UI Light&amp;quot;,&amp;quot;sans-serif&amp;quot;; font-size: 13.5pt; mso-ansi-language: EN-US; mso-bidi-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-language: RU;"&gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div class="MsoNormal" style="margin-bottom: 0.0001pt;"&gt;
&lt;i&gt;&lt;span lang="EN-US" style="font-family: 'Segoe UI', sans-serif; font-size: 9pt;"&gt;ARTF&lt;/span&gt;&lt;/i&gt;&lt;span lang="EN-US" style="font-family: 'Segoe UI', sans-serif; font-size: 9pt;"&gt;&amp;nbsp;[&lt;/span&gt;&lt;span style="font-family: 'Segoe UI', sans-serif; font-size: 9pt;"&gt;входной&lt;/span&gt;&lt;span lang="EN-US" style="font-family: 'Segoe UI', sans-serif; font-size: 9pt;"&gt;]&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div class="MsoNormal" style="line-height: 13.5pt; margin-bottom: .0001pt; margin-bottom: 0cm; margin-left: 36.0pt; margin-right: 0cm; margin-top: 0cm;"&gt;
&lt;span style="color: #2a2a2a; font-family: &amp;quot;Segoe UI&amp;quot;,&amp;quot;sans-serif&amp;quot;; font-size: 9.0pt; mso-fareast-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-language: RU;"&gt;Тип&lt;/span&gt;&lt;span lang="EN-US" style="color: #2a2a2a; font-family: &amp;quot;Segoe UI&amp;quot;,&amp;quot;sans-serif&amp;quot;; font-size: 9.0pt; mso-ansi-language: EN-US; mso-fareast-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-language: RU;"&gt;:&amp;nbsp;&lt;b&gt;IStream&lt;/b&gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div class="MsoNormal" style="line-height: 13.5pt; margin-bottom: .0001pt; margin-bottom: 0cm; margin-left: 36.0pt; margin-right: 0cm; margin-top: 0cm;"&gt;
&lt;span style="color: #2a2a2a; font-family: &amp;quot;Segoe UI&amp;quot;,&amp;quot;sans-serif&amp;quot;; font-size: 9.0pt; mso-fareast-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-language: RU;"&gt;Поток данных, содержащий
&lt;/span&gt;&lt;span lang="EN-US" style="color: #2a2a2a; font-family: &amp;quot;Segoe UI&amp;quot;,&amp;quot;sans-serif&amp;quot;; font-size: 9.0pt; mso-ansi-language: EN-US; mso-fareast-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-language: RU;"&gt;RTF&lt;/span&gt;&lt;span style="color: #2a2a2a; font-family: &amp;quot;Segoe UI&amp;quot;,&amp;quot;sans-serif&amp;quot;; font-size: 9.0pt; mso-fareast-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-language: RU;"&gt;-текст из редактора программы. Поток всегда
спозиционирован на начало.&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div class="MsoNormal" style="margin-bottom: 0.0001pt;"&gt;
&lt;i&gt;&lt;span lang="EN-US" style="font-family: 'Segoe UI', sans-serif; font-size: 9pt;"&gt;AFileName&lt;/span&gt;&lt;/i&gt;&lt;span lang="EN-US" style="font-family: 'Segoe UI', sans-serif; font-size: 9pt;"&gt;&amp;nbsp;[&lt;/span&gt;&lt;span style="font-family: 'Segoe UI', sans-serif; font-size: 9pt;"&gt;выходной&lt;/span&gt;&lt;span lang="EN-US" style="font-family: 'Segoe UI', sans-serif; font-size: 9pt;"&gt;]&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div class="MsoNormal" style="line-height: 13.5pt; margin-bottom: .0001pt; margin-bottom: 0cm; margin-left: 36.0pt; margin-right: 0cm; margin-top: 0cm;"&gt;
&lt;span style="color: #2a2a2a; font-family: &amp;quot;Segoe UI&amp;quot;,&amp;quot;sans-serif&amp;quot;; font-size: 9.0pt; mso-fareast-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-language: RU;"&gt;Тип&lt;/span&gt;&lt;span lang="EN-US" style="color: #2a2a2a; font-family: &amp;quot;Segoe UI&amp;quot;,&amp;quot;sans-serif&amp;quot;; font-size: 9.0pt; mso-ansi-language: EN-US; mso-fareast-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-language: RU;"&gt;:&amp;nbsp;&lt;b&gt;BSTR&lt;/b&gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div class="MsoNormal" style="line-height: 13.5pt; margin-bottom: .0001pt; margin-bottom: 0cm; margin-left: 36.0pt; margin-right: 0cm; margin-top: 0cm;"&gt;
&lt;span style="color: #2a2a2a; font-family: &amp;quot;Segoe UI&amp;quot;,&amp;quot;sans-serif&amp;quot;; font-size: 9.0pt; mso-fareast-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-language: RU;"&gt;Абсолютное&lt;/span&gt;&lt;span style="color: #2a2a2a; font-family: &amp;quot;Segoe UI&amp;quot;,&amp;quot;sans-serif&amp;quot;; font-size: 9.0pt; mso-ansi-language: EN-US; mso-fareast-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-language: RU;"&gt; &lt;/span&gt;&lt;span style="color: #2a2a2a; font-family: &amp;quot;Segoe UI&amp;quot;,&amp;quot;sans-serif&amp;quot;; font-size: 9.0pt; mso-fareast-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-language: RU;"&gt;имя&lt;/span&gt;&lt;span style="color: #2a2a2a; font-family: &amp;quot;Segoe UI&amp;quot;,&amp;quot;sans-serif&amp;quot;; font-size: 9.0pt; mso-ansi-language: EN-US; mso-fareast-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-language: RU;"&gt; &lt;/span&gt;&lt;span style="color: #2a2a2a; font-family: &amp;quot;Segoe UI&amp;quot;,&amp;quot;sans-serif&amp;quot;; font-size: 9.0pt; mso-fareast-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-language: RU;"&gt;файла&lt;/span&gt;&lt;span lang="EN-US" style="color: #2a2a2a; font-family: &amp;quot;Segoe UI&amp;quot;,&amp;quot;sans-serif&amp;quot;; font-size: 9.0pt; mso-ansi-language: EN-US; mso-fareast-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-language: RU;"&gt;-&lt;/span&gt;&lt;span style="color: #2a2a2a; font-family: &amp;quot;Segoe UI&amp;quot;,&amp;quot;sans-serif&amp;quot;; font-size: 9.0pt; mso-fareast-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-language: RU;"&gt;назначения&lt;/span&gt;&lt;span lang="EN-US" style="color: #2a2a2a; font-family: &amp;quot;Segoe UI&amp;quot;,&amp;quot;sans-serif&amp;quot;; font-size: 9.0pt; mso-ansi-language: EN-US; mso-fareast-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-language: RU;"&gt;. &lt;/span&gt;&lt;span style="color: #2a2a2a; font-family: &amp;quot;Segoe UI&amp;quot;,&amp;quot;sans-serif&amp;quot;; font-size: 9.0pt; mso-fareast-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-language: RU;"&gt;Файл может существовать. В этом случае функция должна
его перезаписать.&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div class="MsoNormal" style="line-height: 13.5pt; margin-bottom: .0001pt; margin-bottom: 0cm; margin-left: 36.0pt; margin-right: 0cm; margin-top: 0cm;"&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div class="MsoNormal" style="margin-bottom: 0.0001pt;"&gt;
&lt;span style="color: #db7100; font-family: &amp;quot;Segoe UI Light&amp;quot;,&amp;quot;sans-serif&amp;quot;; font-size: 13.5pt; mso-bidi-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-language: RU;"&gt;Возвращаемое&lt;/span&gt;&lt;span style="color: #db7100; font-family: &amp;quot;Segoe UI Light&amp;quot;,&amp;quot;sans-serif&amp;quot;; font-size: 13.5pt; mso-ansi-language: EN-US; mso-bidi-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-language: RU;"&gt; &lt;/span&gt;&lt;span style="color: #db7100; font-family: &amp;quot;Segoe UI Light&amp;quot;,&amp;quot;sans-serif&amp;quot;; font-size: 13.5pt; mso-bidi-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-language: RU;"&gt;значение&lt;/span&gt;&lt;span lang="EN-US" style="color: #db7100; font-family: &amp;quot;Segoe UI Light&amp;quot;,&amp;quot;sans-serif&amp;quot;; font-size: 13.5pt; mso-ansi-language: EN-US; mso-bidi-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-language: RU;"&gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div class="MsoNormal" style="line-height: 13.5pt; margin-bottom: .0001pt; margin-bottom: 0cm;"&gt;
&lt;span style="color: #2a2a2a; font-family: &amp;quot;Segoe UI&amp;quot;,&amp;quot;sans-serif&amp;quot;; font-size: 9.0pt; mso-fareast-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-language: RU;"&gt;Нет. Ошибки возвращаются стандартным способом.&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div class="MsoNormal" style="line-height: 13.5pt; margin-bottom: .0001pt; margin-bottom: 0cm;"&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div class="MsoNormal" style="margin-bottom: 0.0001pt;"&gt;
&lt;span style="color: #db7100; font-family: &amp;quot;Segoe UI Light&amp;quot;,&amp;quot;sans-serif&amp;quot;; font-size: 13.5pt; mso-bidi-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-language: RU;"&gt;Примечания&lt;/span&gt;&lt;span lang="EN-US" style="color: #db7100; font-family: &amp;quot;Segoe UI Light&amp;quot;,&amp;quot;sans-serif&amp;quot;; font-size: 13.5pt; mso-ansi-language: EN-US; mso-bidi-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-language: RU;"&gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div class="MsoNormal" style="line-height: 13.5pt; margin-bottom: .0001pt; margin-bottom: 0cm;"&gt;
&lt;span style="color: #2a2a2a; font-family: &amp;quot;Segoe UI&amp;quot;,&amp;quot;sans-serif&amp;quot;; font-size: 9.0pt; mso-fareast-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-language: RU;"&gt;Эту функцию реализует плагин экспорта, чтобы производить экспорт текста.&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div class="MsoNormal" style="line-height: 13.5pt; margin-bottom: .0001pt; margin-bottom: 0cm;"&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div class="MsoNormal" style="line-height: 13.5pt; margin-bottom: .0001pt; margin-bottom: 0cm;"&gt;
&lt;span style="color: #2a2a2a; font-family: &amp;quot;Segoe UI&amp;quot;,&amp;quot;sans-serif&amp;quot;; font-size: 9.0pt; mso-fareast-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-language: RU;"&gt;Ядро вызывает эту функцию, когда пользователь выбирает плагин в диалоге выбора
файлов и нажимает &lt;/span&gt;&lt;span lang="EN-US" style="color: #2a2a2a; font-family: &amp;quot;Segoe UI&amp;quot;,&amp;quot;sans-serif&amp;quot;; font-size: 9.0pt; mso-ansi-language: EN-US; mso-fareast-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-language: RU;"&gt;OK&lt;/span&gt;&lt;span style="color: #2a2a2a; font-family: &amp;quot;Segoe UI&amp;quot;,&amp;quot;sans-serif&amp;quot;; font-size: 9.0pt; mso-fareast-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-language: RU;"&gt;. Ядро&lt;/span&gt;&lt;span style="color: #2a2a2a; font-family: &amp;quot;Segoe UI&amp;quot;,&amp;quot;sans-serif&amp;quot;; font-size: 9.0pt; mso-ansi-language: EN-US; mso-fareast-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-language: RU;"&gt; &lt;/span&gt;&lt;span style="color: #2a2a2a; font-family: &amp;quot;Segoe UI&amp;quot;,&amp;quot;sans-serif&amp;quot;; font-size: 9.0pt; mso-fareast-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-language: RU;"&gt;передаёт&lt;/span&gt;&lt;span style="color: #2a2a2a; font-family: &amp;quot;Segoe UI&amp;quot;,&amp;quot;sans-serif&amp;quot;; font-size: 9.0pt; mso-ansi-language: EN-US; mso-fareast-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-language: RU;"&gt; &lt;/span&gt;&lt;span style="color: #2a2a2a; font-family: &amp;quot;Segoe UI&amp;quot;,&amp;quot;sans-serif&amp;quot;; font-size: 9.0pt; mso-fareast-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-language: RU;"&gt;функции&lt;/span&gt;&lt;span style="color: #2a2a2a; font-family: &amp;quot;Segoe UI&amp;quot;,&amp;quot;sans-serif&amp;quot;; font-size: 9.0pt; mso-ansi-language: EN-US; mso-fareast-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-language: RU;"&gt; &lt;/span&gt;&lt;span style="color: #2a2a2a; font-family: &amp;quot;Segoe UI&amp;quot;,&amp;quot;sans-serif&amp;quot;; font-size: 9.0pt; mso-fareast-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-language: RU;"&gt;текущий&lt;/span&gt;&lt;span style="color: #2a2a2a; font-family: &amp;quot;Segoe UI&amp;quot;,&amp;quot;sans-serif&amp;quot;; font-size: 9.0pt; mso-ansi-language: EN-US; mso-fareast-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-language: RU;"&gt; &lt;span lang="EN-US"&gt;RTF-&lt;/span&gt;&lt;/span&gt;&lt;span style="color: #2a2a2a; font-family: &amp;quot;Segoe UI&amp;quot;,&amp;quot;sans-serif&amp;quot;; font-size: 9.0pt; mso-fareast-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-language: RU;"&gt;текст&lt;/span&gt;&lt;span style="color: #2a2a2a; font-family: &amp;quot;Segoe UI&amp;quot;,&amp;quot;sans-serif&amp;quot;; font-size: 9.0pt; mso-ansi-language: EN-US; mso-fareast-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-language: RU;"&gt; &lt;/span&gt;&lt;span style="color: #2a2a2a; font-family: &amp;quot;Segoe UI&amp;quot;,&amp;quot;sans-serif&amp;quot;; font-size: 9.0pt; mso-fareast-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-language: RU;"&gt;и&lt;/span&gt;&lt;span style="color: #2a2a2a; font-family: &amp;quot;Segoe UI&amp;quot;,&amp;quot;sans-serif&amp;quot;; font-size: 9.0pt; mso-ansi-language: EN-US; mso-fareast-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-language: RU;"&gt; &lt;/span&gt;&lt;span style="color: #2a2a2a; font-family: &amp;quot;Segoe UI&amp;quot;,&amp;quot;sans-serif&amp;quot;; font-size: 9.0pt; mso-fareast-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-language: RU;"&gt;имя&lt;/span&gt;&lt;span style="color: #2a2a2a; font-family: &amp;quot;Segoe UI&amp;quot;,&amp;quot;sans-serif&amp;quot;; font-size: 9.0pt; mso-ansi-language: EN-US; mso-fareast-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-language: RU;"&gt; &lt;/span&gt;&lt;span style="color: #2a2a2a; font-family: &amp;quot;Segoe UI&amp;quot;,&amp;quot;sans-serif&amp;quot;; font-size: 9.0pt; mso-fareast-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-language: RU;"&gt;файла&lt;/span&gt;&lt;span lang="EN-US" style="color: #2a2a2a; font-family: &amp;quot;Segoe UI&amp;quot;,&amp;quot;sans-serif&amp;quot;; font-size: 9.0pt; mso-ansi-language: EN-US; mso-fareast-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-language: RU;"&gt;, &lt;/span&gt;&lt;span style="color: #2a2a2a; font-family: &amp;quot;Segoe UI&amp;quot;,&amp;quot;sans-serif&amp;quot;; font-size: 9.0pt; mso-fareast-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-language: RU;"&gt;указанное&lt;/span&gt;&lt;span style="color: #2a2a2a; font-family: &amp;quot;Segoe UI&amp;quot;,&amp;quot;sans-serif&amp;quot;; font-size: 9.0pt; mso-ansi-language: EN-US; mso-fareast-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-language: RU;"&gt; &lt;/span&gt;&lt;span style="color: #2a2a2a; font-family: &amp;quot;Segoe UI&amp;quot;,&amp;quot;sans-serif&amp;quot;; font-size: 9.0pt; mso-fareast-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-language: RU;"&gt;пользователем&lt;/span&gt;&lt;span lang="EN-US" style="color: #2a2a2a; font-family: &amp;quot;Segoe UI&amp;quot;,&amp;quot;sans-serif&amp;quot;; font-size: 9.0pt; mso-ansi-language: EN-US; mso-fareast-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-language: RU;"&gt; (&lt;/span&gt;&lt;span style="color: #2a2a2a; font-family: &amp;quot;Segoe UI&amp;quot;,&amp;quot;sans-serif&amp;quot;; font-size: 9.0pt; mso-fareast-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-language: RU;"&gt;имя&lt;/span&gt;&lt;span style="color: #2a2a2a; font-family: &amp;quot;Segoe UI&amp;quot;,&amp;quot;sans-serif&amp;quot;; font-size: 9.0pt; mso-ansi-language: EN-US; mso-fareast-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-language: RU;"&gt; &lt;/span&gt;&lt;span style="color: #2a2a2a; font-family: &amp;quot;Segoe UI&amp;quot;,&amp;quot;sans-serif&amp;quot;; font-size: 9.0pt; mso-fareast-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-language: RU;"&gt;файла&lt;/span&gt;&lt;span style="color: #2a2a2a; font-family: &amp;quot;Segoe UI&amp;quot;,&amp;quot;sans-serif&amp;quot;; font-size: 9.0pt; mso-ansi-language: EN-US; mso-fareast-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-language: RU;"&gt; &lt;/span&gt;&lt;span style="color: #2a2a2a; font-family: &amp;quot;Segoe UI&amp;quot;,&amp;quot;sans-serif&amp;quot;; font-size: 9.0pt; mso-fareast-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-language: RU;"&gt;преобразуется&lt;/span&gt;&lt;span style="color: #2a2a2a; font-family: &amp;quot;Segoe UI&amp;quot;,&amp;quot;sans-serif&amp;quot;; font-size: 9.0pt; mso-ansi-language: EN-US; mso-fareast-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-language: RU;"&gt; &lt;/span&gt;&lt;span style="color: #2a2a2a; font-family: &amp;quot;Segoe UI&amp;quot;,&amp;quot;sans-serif&amp;quot;; font-size: 9.0pt; mso-fareast-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-language: RU;"&gt;в&lt;/span&gt;&lt;span style="color: #2a2a2a; font-family: &amp;quot;Segoe UI&amp;quot;,&amp;quot;sans-serif&amp;quot;; font-size: 9.0pt; mso-ansi-language: EN-US; mso-fareast-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-language: RU;"&gt; &lt;/span&gt;&lt;span style="color: #2a2a2a; font-family: &amp;quot;Segoe UI&amp;quot;,&amp;quot;sans-serif&amp;quot;; font-size: 9.0pt; mso-fareast-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-language: RU;"&gt;абсолютный&lt;/span&gt;&lt;span style="color: #2a2a2a; font-family: &amp;quot;Segoe UI&amp;quot;,&amp;quot;sans-serif&amp;quot;; font-size: 9.0pt; mso-ansi-language: EN-US; mso-fareast-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-language: RU;"&gt; &lt;/span&gt;&lt;span style="color: #2a2a2a; font-family: &amp;quot;Segoe UI&amp;quot;,&amp;quot;sans-serif&amp;quot;; font-size: 9.0pt; mso-fareast-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-language: RU;"&gt;формат&lt;/span&gt;&lt;span lang="EN-US" style="color: #2a2a2a; font-family: &amp;quot;Segoe UI&amp;quot;,&amp;quot;sans-serif&amp;quot;; font-size: 9.0pt; mso-ansi-language: EN-US; mso-fareast-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-language: RU;"&gt;). &lt;/span&gt;&lt;span style="color: #2a2a2a; font-family: &amp;quot;Segoe UI&amp;quot;,&amp;quot;sans-serif&amp;quot;; font-size: 9.0pt; mso-fareast-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-language: RU;"&gt;Функция должна создать указанный файл (а при его наличии
– перезаписать) и сохранить в него переданный первым параметром &lt;/span&gt;&lt;span lang="EN-US" style="color: #2a2a2a; font-family: &amp;quot;Segoe UI&amp;quot;,&amp;quot;sans-serif&amp;quot;; font-size: 9.0pt; mso-ansi-language: EN-US; mso-fareast-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-language: RU;"&gt;RTF&lt;/span&gt;&lt;span style="color: #2a2a2a; font-family: &amp;quot;Segoe UI&amp;quot;,&amp;quot;sans-serif&amp;quot;; font-size: 9.0pt; mso-fareast-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-language: RU;"&gt;-текст, преобразовав его в нужный формат.&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div class="MsoNormal" style="line-height: 13.5pt; margin-bottom: .0001pt; margin-bottom: 0cm;"&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div class="MsoNormal" style="margin-bottom: 0.0001pt;"&gt;
&lt;span style="color: #db7100; font-family: &amp;quot;Segoe UI Light&amp;quot;,&amp;quot;sans-serif&amp;quot;; font-size: 13.5pt; mso-bidi-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-language: RU;"&gt;Примеры&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div class="MsoNormal" style="line-height: 13.5pt; margin-bottom: .0001pt; margin-bottom: 0cm;"&gt;
&lt;span style="color: #2a2a2a; font-family: &amp;quot;Segoe UI&amp;quot;,&amp;quot;sans-serif&amp;quot;; font-size: 9.0pt; mso-fareast-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-language: RU;"&gt;Пример использования можно увидеть в статье &lt;a href="http://www.example.com/"&gt;Пример реализации плагина экспорта&lt;/a&gt;.&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div class="MsoNormal" style="line-height: 13.5pt; margin-bottom: .0001pt; margin-bottom: 0cm;"&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div class="MsoNormal" style="margin-bottom: 0.0001pt;"&gt;
&lt;span style="color: #db7100; font-family: &amp;quot;Segoe UI Light&amp;quot;,&amp;quot;sans-serif&amp;quot;; font-size: 13.5pt; mso-bidi-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-language: RU;"&gt;Требования&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;table border="0" cellpadding="0" cellspacing="0" class="MsoNormalTable" style="border-collapse: collapse; mso-padding-bottom-alt: 15.0pt; mso-padding-top-alt: 15.0pt; mso-yfti-tbllook: 1184;"&gt;
 &lt;tbody&gt;
&lt;tr&gt;
  &lt;td style="background: #EDEDED; border-bottom: solid #DBDBDB 1.0pt; border: none; mso-border-bottom-alt: solid #DBDBDB .75pt; padding: 7.5pt 6.0pt 7.5pt 6.0pt;"&gt;
  &lt;div class="MsoNormal" style="line-height: 13.5pt; margin-bottom: .0001pt; margin-bottom: 0cm; margin-left: 7.5pt; margin-right: 7.5pt; margin-top: 0cm;"&gt;
&lt;b&gt;&lt;span style="color: #2a2a2a; font-family: &amp;quot;Segoe UI&amp;quot;,&amp;quot;sans-serif&amp;quot;; font-size: 9.0pt; mso-fareast-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-language: RU;"&gt;Версия ядра&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;
&lt;/td&gt;
  &lt;td style="border-bottom: solid #DBDBDB 1.0pt; border: none; mso-border-bottom-alt: solid #DBDBDB .75pt; padding: 7.5pt 6.0pt 7.5pt 6.0pt;" valign="top"&gt;
  &lt;div class="MsoNormal" style="margin-bottom: 7.5pt; margin-left: 7.5pt; margin-right: 7.5pt; margin-top: 7.5pt;"&gt;
&lt;span style="color: #2a2a2a; font-family: &amp;quot;Segoe UI&amp;quot;,&amp;quot;sans-serif&amp;quot;; font-size: 9.0pt; mso-fareast-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-language: RU;"&gt;1&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/td&gt;
 &lt;/tr&gt;
&lt;tr&gt;
  &lt;td style="background: #EDEDED; border-bottom: solid #DBDBDB 1.0pt; border: none; mso-border-bottom-alt: solid #DBDBDB .75pt; padding: 7.5pt 6.0pt 7.5pt 6.0pt;"&gt;
  &lt;div class="MsoNormal" style="line-height: 13.5pt; margin-bottom: .0001pt; margin-bottom: 0cm; margin-left: 7.5pt; margin-right: 7.5pt; margin-top: 0cm;"&gt;
&lt;b&gt;&lt;span style="color: #2a2a2a; font-family: &amp;quot;Segoe UI&amp;quot;,&amp;quot;sans-serif&amp;quot;; font-size: 9.0pt; mso-fareast-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-language: RU;"&gt;Заголовоный файл&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;
&lt;/td&gt;
  &lt;td style="border-bottom: solid #DBDBDB 1.0pt; border: none; mso-border-bottom-alt: solid #DBDBDB .75pt; padding: 7.5pt 6.0pt 7.5pt 6.0pt;" valign="top"&gt;
  &lt;div class="MsoNormal" style="margin-bottom: 0.0001pt; margin-left: 7.5pt; margin-right: 7.5pt; margin-top: 0cm;"&gt;
&lt;span lang="EN-US" style="color: #2a2a2a; font-family: &amp;quot;Segoe UI&amp;quot;,&amp;quot;sans-serif&amp;quot;; font-size: 9.0pt; mso-ansi-language: EN-US; mso-fareast-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-language: RU;"&gt;PluginAPI.tlb&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/td&gt;
 &lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;div class="MsoNormal" style="margin-bottom: 0.0001pt;"&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div class="MsoNormal" style="margin-bottom: 0.0001pt;"&gt;
&lt;span style="color: #db7100; font-family: &amp;quot;Segoe UI Light&amp;quot;,&amp;quot;sans-serif&amp;quot;; font-size: 13.5pt; mso-bidi-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-language: RU;"&gt;См. также&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div class="MsoNormal"&gt;
&lt;span lang="EN-US" style="color: #2a2a2a; font-family: &amp;quot;Segoe UI&amp;quot;,&amp;quot;sans-serif&amp;quot;; font-size: 9.0pt; line-height: 115%; mso-ansi-language: EN-US; mso-fareast-font-family: &amp;quot;Times New Roman&amp;quot;; mso-fareast-language: RU;"&gt;&lt;a href="http://www.example.com/"&gt;IImportPlugin.ImportRTF&lt;/a&gt;&lt;/span&gt;&lt;span lang="EN-US"&gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/blockquote&gt;
&lt;br /&gt;
(Да, это документация с закосом под MSDN)&lt;br /&gt;
&lt;br /&gt;
Конечно же, вы можете придерживаться любого другого стиля оформления документации, в том числе вы можете готовить её не в виде CHM/HTML файлов, а писать в комментариях прямо в заголовочниках. Всё это - вторичные детали. Самое главное тут - что она (эта документация) вообще должна быть. Потому что в ней написаны такие вещи ("семантика"), которые не выражены в коде.&lt;br /&gt;
&lt;br /&gt;
Так что, как бы вы ни оформляли свою документацию, проследите чтобы в ней было:
&lt;ul&gt;
&lt;li&gt;(Опционально) Краткое описание метода, что он делает. Эта часть нужна, чтобы быстро ухватить суть метода. В принципе, поскольку у вас есть подробное описание (см. ниже), то её можно не писать. Но её наличие позволяет более эффективно работать с документацией.&lt;/li&gt;
&lt;li&gt;(Опционально) Синтаксис метода (копия из заголовочника). Не обязательно, ведь код можно посмотреть в заголовочниках, но лучше приводить - исключительно для удобства (не надо ещё куда-то лазить, чтобы увидеть синтаксис). Всегда отсутствует в документации, написанной прямо в заголовочниках (по очевидным причинам).&lt;/li&gt;
&lt;li&gt;(Обязательно) Описание каждого параметра и возвращаемого значения функции. Опишите, какой параметр входной, какой выходной, какие ограничения вы задаёте для параметров, какие значения он может принимать, а какие - не может. Как метод должен реагировать на специальные случаи (если они есть).&lt;/li&gt;
&lt;li&gt;(Обязательно) Обработка ошибок. Как метод сообщает об ошибках. Какие есть специальные случаи, которые вызывающий  может хотеть явно обработать.&lt;/li&gt;
&lt;li&gt;(Обязательно) Подробное описание метода и его работы. Нужно сказать, когда он вызывается, что он делает, что должен выдавать. Описать типичное использование, краевые случаи и возможные подводные камни.&lt;/li&gt;
&lt;li&gt;(Опционально) Пример использования или ссылка на него.&lt;/li&gt;
&lt;li&gt;(Обязательно) Требования - т.е. в какой версии сервера (ядра) появился этот метод и где искать его описание. Понятно, что в первом варианте вашей системы плагинов у вас каждый метод будет присутствовать, начиная с первой версии ядра (просто потому, что другой версии у вас нет). И в этом случае этот раздел можно не писать из-за его очевидности. Но в дальнейшем, когда вы  будете расширять свою систему и у вас появятся новые методы, этот раздел всё равно нужно будет добавить.&lt;/li&gt;
&lt;li&gt;(Опционально) Ссылки на тесно связанные или аналогичные методы, плюс ссылки на обобщающий материал (если есть) или корневой раздел.&lt;/li&gt;
&lt;/ul&gt;
Небольшое замечание по поводу входных и выходных параметров. Дело в том, что этот термин может использоваться в двух разных смыслах. Во-первых, есть очевидный смысл: &lt;code&gt;P: T&lt;/code&gt;, &lt;code&gt;const P: T&lt;/code&gt; - входные параметры, &lt;code&gt;out P: T&lt;/code&gt; - выходной параметр, а &lt;code&gt;var P: T&lt;/code&gt; - входной-выходной. Это - синтаксический смысл. Он очевидно следует из кода метода. Поэтому лично я считаю, что этот смысл не нужно дублировать ещё и словами в документации (вы ведь привели синтаксис метода, дааа?).&lt;br /&gt;
&lt;br /&gt;
Но есть и другой смысл, скорее семантический. К примеру, в нашем методе первый параметр у нас формирует ядро, плагин из него должен извлечь информацию. Т.е. это как бы "входной" параметр. С другой стороны, второй параметр, хотя по синтаксису он чисто "входной" - но подразумевается, что плагин должен записать в этот файл информацию, "вывести её". И вот с этой точки зрения параметр может рассматриваться как "выходной". Так вот, этот, второй смысл, я и предлагаю указывать в документации. Потому что первый смысл и так у нас есть - он выражен в синтаксисе метода. А вот второй смысл там не содержится - он лишь следует из описания параметра. Т.е. в каком-то смысле, это является аналогом "краткого заголовка" для параметра - аналогично тому, как краткое пояснение метода является обобщением его подробного описания.&lt;br /&gt;
&lt;br /&gt;
Итого, ваш контракт плагинов будет состоять из двух частей: синтаксиса (заголовочники) и семантики (документация).&lt;br /&gt;
&lt;br /&gt;
&lt;a name="n4"&gt;&lt;/a&gt;&lt;h1&gt;Реализация&lt;/h1&gt;
К этому моменту у нас на руках есть полностью формализированный комплект правил для плагинов, так что написать код сейчас будет очень просто. Но на практике вы можете приступить к этому пункту раньше, чем завершите подготовку контракта. Поэтому это может быть сложнее.&lt;br /&gt;
&lt;br /&gt;
В качестве примера сейчас мы напишем несколько плагинов экспорта-импорта, а также научим ядро работать с ними. Поскольку эта статья - про плагины, а не про вопросы работы с RTF-текстом, то я ограничусь лишь простыми, тривиальными примерами. Чтобы было хоть чуточку интересно, я предлагаю уже существующие возможности (импорт/экспорт в RTF и TXT) тоже оформить в виде плагинов. Это позволит мне продемонстрировать работу с плагинами, не отвлекаясь на вопросы преобразования RTF - это сама по себе непростая задача. По идее, если вы захотите развить этот пример и сделать импорт/экспорт в другие форматы, вам придётся выполнить кучу работы. Во-первых, вам нужно прочитать входной документ и распарсить его, создав модель документа в памяти. Модель - это значит что документ будет представлен объектами "документ", "абзац", "строка", "параметры шрифта" и т.п. Это делается, чтобы с документом можно было удобно работать. И при этом вам нужно следить, что вы покрываете все случаи, описанные в спецификации формата, вас ожидают волнительные вопросы работы с кодировками и т.д. Когда преобразование выполнено, то остаётся пройтись по модели документа и выполнить её "сборку" в документ другого формата. В общем, если вас заинтересует развитие этого примера, то в сети можно найти готовый код для парсинга RTF и других форматов. Альтернативно можно попробовать делать преобразование RTF как замену строк - этот подход основывается на том факте, что RTF - это текстовый формат, вроде HTML. Т.е. вы можете получить содержимое в виде строки, а затем серией замен сменить атрибуты RTF на, скажем, тэги HTML. Или вы можете просто использовать уже написанный и готовый код. В этом случае ваша задача по написанию плагина будет заключаться в инкапсуляции готовой библиотеки кода в виде плагина к программе. В любом случае, как видите, всё это достаточно трудоёмкие задачи - и именно поэтому здесь они не рассматриваются.&lt;br /&gt;
&lt;br /&gt;
Итак, начнём с ядра:
&lt;pre class="brush:delphi"&gt;procedure TMainForm.FormCreate(Sender: TObject);
begin
  SetErrorMode(SetErrorMode(0) or SEM_NOOPENFILEERRORBOX or SEM_FAILCRITICALERRORS);
  // Загрузка всех плагинов. Подразумевается, что они лежат в под-папке Plugins
  Plugins.LoadPlugins(ExtractFilePath(ParamStr(0)) + 'Plugins');

  ...
end;&lt;/pre&gt;
Это - загрузка плагинов. Тут ничего особенного нет, все плагины грузятся автоматически и скопом, не различаясь по типу.&lt;br /&gt;
&lt;br /&gt;
В дальнейшем нам потребуется показывать загруженные плагины в диалогах сохранения/открытия, поэтому предлагаю сразу же после загрузки плагинов подготовить диалоги. Мы можем это сделать, потому что список плагинов у нас не меняется. Если бы мы добавили в программу возможность динамически загружать плагины по указке пользователя (замечу, что наша архитектура это позволяет, просто мы не выставляем эту возможность в данном примере), то нам нужно было бы это делать каждый раз перед открытием диалога.
&lt;pre class="brush:delphi"&gt;procedure TMainForm.FormCreate(Sender: TObject);
begin
  SetErrorMode(SetErrorMode(0) or SEM_NOOPENFILEERRORBOX or SEM_FAILCRITICALERRORS);
  // Загрузка всех плагинов. Подразумевается, что они лежат в под-папке Plugins
  Plugins.LoadPlugins(ExtractFilePath(ParamStr(0)) + 'Plugins');

  BuildFilterList;

  ...
end;

procedure TMainForm.BuildFilterList;

  function BuildFilter(const AInterface: TGUID): String;
  var
    X, Y: Integer;
    Intf: IInterface;
  begin
    Result := '';
    Y := 0;
    // Отбираем только нужные плагины и заносим их в список
    for X := 0 to Plugins.Count - 1 do
      if Supports(Plugins[X], AInterface, Intf) then
      begin
        Result := Result + Plugins[X].Description + '|' + Plugins[X].Mask + '|';
        Plugins[X].FilterIndex := Y;
        Inc(Y);
        Intf := nil;
      end;
    // Удалили лишний | в конце строки
    SetLength(Result, Length(Result) - 1);
  end;

begin
  OpenDialog.Filter := BuildFilter(IImportPlugin);
  SaveDialog.Filter := BuildFilter(IExportPlugin);

  OpenDialog.FilterIndex := 1;
  SaveDialog.FilterIndex := 1;
end;&lt;/pre&gt;
Тут мы два раза проходимся по общему списку плагинов, отбирая плагины по типам и занося их в список для диалогов. Я воспользовался той же техникой, что и в прошлый раз - спроецировав плагин на локальный тип данных, так что работа с плагинами ничем не отличается от работы с локальными объектами. Я не буду приводить изменения в коде менеджера плагинов - все изменения делаются по аналогии, как и в прошлый раз. Вы можете посмотреть полный исходный код в примере к статье (см. ниже).&lt;br /&gt;
&lt;br /&gt;
Единственное, на что я хочу обратить момент прямо сейчас - нам нужно как-то связать плагин со строкой в фильтре диалога. Т.е. вот сейчас мы добавили плагин в диалог - это хорошо. А что нам делать потом, когда пользователь прикажет программе сохранить файл именно в этот формат? Как нам найти нужный плагин? По какому признаку их отличать?&lt;br /&gt;
&lt;br /&gt;
Тут есть несколько разных подходов. Давайте я кратенько их опишу.&lt;br /&gt;
&lt;br /&gt;
Подход первый: опознавать плагин по его характеристике. Например, по маске. Или по описанию. Или по тому и другому. Это будет работать, если эта характеристика - уникальна. Плюсы: дополнительно ничего делать не надо, минусы: не всегда такая характеристика есть. В данном случае эта характеристика не уникальна, потому что теоретически никто не мешает нам установить два плагина экспорта в PDF: плагин от Васи и плагин от Пети.&lt;br /&gt;
&lt;br /&gt;
Подход второй: создать уникальную характеристику и отслеживать по ней. К примеру, это ID плагина, который имеет уникальный GUID. Такой ID уже есть в нашем предыдущем примере с плагинами, так что мы могли бы использовать его. И вообще-то обычно так вам и нужно поступать. Но в данном конкретном случае это не очень удобно - потому что нам нужно хранить этот ID плагина в фильтре диалога. Т.е. прежде чем использовать этот подход, надо сначала изменить диалог - мы же практикуем правильные подходы, а не "поставь распорку сбоку - ничего не развалится" (т.е. ведение списка ID отдельно от диалога). Плюсы: подходит всегда, гарантированная уникальность. Минусы: целевой объект должен поддерживать возможность сохранять ID плагина, т.е. обычно требуется доработка существующих классов.&lt;br /&gt;
&lt;br /&gt;
Третий, не очень правильный, подход: ввести "обратный идентфикатор" и заставить плагин помнить нужную информацию. Именно этот подход я применил в примере выше. Плюсы: не нужно модифицировать сторонний класс, минусы: зависимость менеджера плагинов от использующего его кода, кроме того, этот метод не всегда применим. Тут надо заметить, что сам плагин (разумеется!) не должен ничего знать про то, как ядро его использует. Какой там у него индекс в диалоге сохранения - не его ума дело, а ядра. Поэтому, конечно же, эту информацию передавать самому плагину нельзя. Но поскольку у нас есть менеджер плагинов и он отслеживает информацию о плагинах (вроде его индекса, описателя библиотеки и имени файла - всех этих вещей на руках у плагина нет), то почему бы не дать ему отслеживать ещё один дополнительный кусочек информации? Именно таким кусочком в моём примере является (новое) свойство &lt;code&gt;FilterIndex&lt;/code&gt;.&lt;br /&gt;
&lt;br /&gt;
Дополнительно отмечу четвёртый (тоже не совсем правильный) подход: идентификация плагинов по индексу в менеджере плагинов. Плюсы: идентификатор можно записать в свойства типа &lt;code&gt;Tag&lt;/code&gt;, &lt;code&gt;Objects&lt;/code&gt;, &lt;code&gt;lpParam&lt;/code&gt; и т.п. (т.е. не нужна доработка классов), минусы: эта схема не пригодна при динамической загрузке/выгрузке, т.к. индексы при этом могут меняться.&lt;br /&gt;
&lt;br /&gt;
Ладно, разобрались. Далее, сам экспорт:
&lt;pre class="brush:delphi"&gt;procedure TMainForm.FileSaveAs(Sender: TObject);
var
  Plugin: IExportPlugin;
  RTFStream: IStream;
  Stream: TStream;
begin
  if SaveDialog.Execute then
  begin
    if FileExists(SaveDialog.FileName) then
      if MessageDlg(Format(sOverWrite, [SaveDialog.FileName]),
        mtConfirmation, mbYesNoCancel, 0) &lt;&gt; idYes then Exit;

    // Ищем плагин
    if not FindPlugin(SaveDialog.FilterIndex - 1 { у FilterIndex отсчёт с 1, корректируем},
                      IExportPlugin, Plugin) then
      Exit;

    // Готовим данные для экспорта
    Stream := TMemoryStream.Create;
    RTFStream := TStreamAdapter.Create(Stream, soOwned);
    Editor.Lines.SaveToStream(Stream);
    Stream.Position := 0;

    // Вызываем экспорт
    Plugin.ExportRTF(RTFStream, SaveDialog.FileName);

    // Учёт (без изменений)
    SetFileName(SaveDialog.FileName);
    Editor.Modified := False;
    SetModified(False);
  end;
end;&lt;/pre&gt;
...и импорт:
&lt;pre class="brush:delphi"&gt;procedure TMainForm.FileOpen(Sender: TObject);
var
  Stream: TStream;
  RTFStream: IStream;
  Plugin: IImportPlugin;
begin
  CheckFileSave;
  SetFileName(sUntitled);  // &lt;- указали, что это - новый документ
  if OpenDialog.Execute then
  begin
    // Импорт
    Stream := TMemoryStream.Create;
    RTFStream := TStreamAdapter.Create(Stream, soOwned);
    if not FindPlugin(OpenDialog.FilterIndex - 1, IImportPlugin, Plugin) then
      Exit;
    Plugin.ImportRTF(OpenDialog.FileName, RTFStream);

    // Обработка данных импорта
    Stream.Position := 0;
    Editor.Lines.LoadFromStream(Stream);

    // Учёт
    Editor.SetFocus;
    Editor.Modified := False;
    SetModified(False);
    Editor.ReadOnly := ofReadOnly in OpenDialog.Options;
  end;
end;&lt;/pre&gt;
А также команда "просто Save":
&lt;pre class="brush:delphi"&gt;procedure TMainForm.FileSave(Sender: TObject);
var
  Plugin: IExportPlugin;
  RTFStream: IStream;
  Stream: TStream;
begin
  if FFileName = sUntitled then
    FileSaveAs(Sender)
  else
  begin
    // Ищем плагин
    if not FindPlugin(SaveDialog.FilterIndex - 1, IExportPlugin, Plugin) then
      Exit; 

    // Вызываем экспорт
    Stream := TMemoryStream.Create;
    RTFStream := TStreamAdapter.Create(Stream, soOwned);
    Editor.Lines.SaveToStream(Stream);
    Stream.Position := 0;
    Plugin.ExportRTF(RTFStream, FFileName);

    Editor.Modified := False;
    SetModified(False);
  end;
end;&lt;/pre&gt;
Функция &lt;code&gt;FindPlugin&lt;/code&gt; тривиальна (она возвращает запрошенный интерфейс у плагина с заданным индексом фильтра):
&lt;pre class="brush:delphi"&gt;function TMainForm.FindPlugin(const AIndex: Integer; const AGUID: TGUID; out Obj): Boolean;
var
  X: Integer;
begin
  Result := False;
  for X := 0 to Plugins.Count - 1 do
    if Plugins[X].FilterIndex = AIndex then
    begin
      Result := Supports(Plugins[X], AGUID, Obj);
      if Result then
        Break;
    end;
end;&lt;/pre&gt;
Здесь нужно обратить внимание на фундаментальное изменение в поведении программы. Раньше, когда плагинов в программе у нас не было, мы могли загрузить файл и нажать на кнопку "Save". И это действие сохранило бы файл в оригинальном формате. Но когда мы перевели загрузку/сохранение на плагины - мы этого сделать не можем. Потому что у нас сейчас нет информации о связи плагинов импорта и экспорта. И если у нас установлен плагин импорта, скажем, из HTML, то это не значит, что установлен и плагин экспорта в HTML. Иными словами, операция может быть односторонней. Поэтому в данном примере при открытии файла мы помечаем его как ещё не сохранённый - и тогда при сохранении мы покажем диалог выбора имени файла и плагина для экспорта.&lt;br /&gt;
&lt;br /&gt;
А чтобы реализовать исходное поведение, нам нужно приложить больше усилий - нужно придумать способ ассоциации плагинов импорта с плагинами экспорта. Делать это можно разными способами, но сейчас я не буду рассматривать этот вопрос. Пока нам хватит текущего материала.&lt;br /&gt;
&lt;br /&gt;
Я приведу &lt;i&gt;некоторые&lt;/i&gt; изменения в менеджере плагинов, которые я сделал для этого примера:
&lt;pre class="brush:delphi"&gt;type
  IPlugin = interface
  // protected
    ...
    function GetMask: String;
    function GetDescription: String;
    function GetFilterIndex: Integer;
    procedure SetFilterIndex(const AValue: Integer);
  // public
    ...
    property Mask: String read GetMask;
    property Description: String read GetDescription;

    property FilterIndex: Integer read GetFilterIndex write SetFilterIndex;
  end;

...

  TPlugin = class(TInterfacedObject, IUnknown, IPlugin)
  private
    ...
    FFilterIndex: Integer;
    FPlugin: PluginAPI.IPlugin;
    FInfoRetrieved: Boolean;
    FMask: String;
    FDescription: String;
    procedure GetInfo;
  protected
    ...
    function GetMask: String;
    function GetDescription: String;
    function GetFilterIndex: Integer;
    procedure SetFilterIndex(const AValue: Integer);
    function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
  public
    ...
  end;

  ...

procedure TPlugin.GetInfo;
begin
  if FInfoRetrieved then
    Exit;
  FMask := FPlugin.Mask;
  FDescription := FPlugin.Description;
  FInfoRetrieved := True;
end;

function TPlugin.GetFilterIndex: Integer;
begin
  Result := FFilterIndex;
end;

procedure TPlugin.SetFilterIndex(const AValue: Integer);
begin
  FFilterIndex := AValue;
end;

function TPlugin.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
  Result := inherited QueryInterface(IID, Obj);
  if Failed(Result) then
    Result := FPlugin.QueryInterface(IID, Obj);
end;&lt;/pre&gt;
Почти все изменения я уже обсудил - это и дублирование &lt;code&gt;Mask&lt;/code&gt;/&lt;code&gt;Description&lt;/code&gt; и новое поле &lt;code&gt;FilterIndex&lt;/code&gt;, не передаваемое плагину. Осталось пояснить только код с &lt;code&gt;QueryInterface&lt;/code&gt;. Нужен он вот зачем: нашей программе нужен от плагина не только &lt;code&gt;IPlugin&lt;/code&gt; (функциональность которого и так выставляется наружу менеджером плагинов), но и &lt;code&gt;IExportPlugin&lt;/code&gt;/&lt;code&gt;IImportPlugin&lt;/code&gt;. Как программа может их получить? По идее, их нужно запрашивать у интерфейса плагина, который вернула функция инициализации плагина. Этот интерфейс хранится в менеджере плагинов внутри класса &lt;code&gt;TPlugin&lt;/code&gt; - в поле &lt;code&gt;FPlugin&lt;/code&gt;. Чтобы выставить его наружу есть два варианта: простой и красивый.&lt;br /&gt;
&lt;br /&gt;
Простой вариант выглядит так:
&lt;pre class="brush:delphi"&gt;type
  IPlugin = interface
  ...
    function GetIntf: PluginAPI.IPlugin;
  ...
    property Intf: PluginAPI.IPlugin read GetIntf;
  end;

...

  TPlugin = class(TInterfacedObject, IUnknown, IPlugin) 
  private
    FPlugin: PluginAPI.IPlugin;
  ...
    function GetIntf: PluginAPI.IPlugin;
  ...  
  end;

...

function TPlugin.GetIntf: PluginAPI.IPlugin;
begin
  Result := FPlugin;
end;&lt;/pre&gt;
(да, я знаю: у меня два разных интерфейса называются одним именем; наверное, это не очень удачно, но другого подходящего имени мне в голову не пришло, а они не пересекаются, так что я оставил так, но вы можете использовать разные имена, чтобы не запутаться)&lt;br /&gt;
&lt;br /&gt;
И тогда работа с плагином выглядела бы так:
&lt;pre class="brush:delphi"&gt;for X := 0 to Plugins.Count - 1 do
  if Supports(Plugins[X].Intf, IExportPlugin, ExportPlugin) then
    ...&lt;/pre&gt;
Т.е. получается, что мы ввели лишний уровень косвенности - &lt;code&gt;Intf&lt;/code&gt;. Этот способ прост, потому что очевиден: мы просто открыли доступ к внутреннему полю.&lt;br /&gt;
&lt;br /&gt;
Но есть и другой способ, более красивый - именно его я и использовал. Смысл его заключается в том, чтобы разрешить получать интерфейсы плагина не через плагин, а через его обёртку в менеджере плагинов. Именно этим занимается переопределённый метод &lt;code&gt;QueryInterface&lt;/code&gt;. Логика у него чрезвычайно простая: сперва вызовем унаследованную реализацию. Нашла она запрашиваемый интерфейс? Если да - то выходим. В этой ситуации кто-то запросил интерфейс оболочки плагина. Если же унаследованная реализация ничего не нашла, то это означает, что оболочка плагина этот интерфейс не реализует. Но тогда, быть может, нас спрашивают не о нашем интерфейсе, а интерфейсе плагина? Тогда мы вызовем запрос интерфейса у плагина.&lt;br /&gt;
&lt;br /&gt;
Иными словами, новая реализация &lt;code&gt;QueryInterface&lt;/code&gt; делегирует запросы всех неподдерживаемых интерфейсов плагину. Это не значит, что любой вызов будет успешен - в конце концов, плагин не всё же подряд поддерживает. Но эта логика гарантирует, что все поддерживаемые плагином интерфейсы могут быть получены через его обёртку в менеджере плагинов. &lt;br /&gt;
&lt;br /&gt;
С таким подходом мы можем писать код вида:
&lt;pre class="brush:delphi"&gt;for X := 0 to Plugins.Count - 1 do
  if Supports(Plugins[X], IExportPlugin, ExportPlugin) then
    ...&lt;/pre&gt;
Обратите внимание, что к этому моменту обёртка плагина для нас вообще ничем не отличается от самого плагина, с точки зрения вызывающего нет никакой разницы. Со стороны это выглядит так, словно у плагина каким-то "волшебным" образом появились свойства, которых у него на самом деле нет (&lt;code&gt;Index&lt;/code&gt;, &lt;code&gt;FileName&lt;/code&gt;, &lt;code&gt;FilterIndex&lt;/code&gt; и т.п.).&lt;br /&gt;
&lt;br /&gt;
Кстати говоря, в таком простом примере можно было бы вообще объединить &lt;code&gt;IPlugin&lt;/code&gt; с &lt;code&gt;IImportPlugin&lt;/code&gt; и &lt;code&gt;IExportPlugin&lt;/code&gt;, но я специально сделал два интерфейса, чтобы показать как можно запрашивать у плагина разную функциональность, а также чтобы лучше подчеркнуть логику разработки: "нам что-то от плагина надо? - создай интерфейс для этого!"&lt;br /&gt;
&lt;br /&gt;
Давайте теперь перейдём к плагинам. Начнём с плагинов импорта/экспорта для RTF. Они тривиальны, потому что нам не нужно выполнять преобразования потоков данных, а нужно просто тупо скопировать их.&lt;br /&gt;
&lt;br /&gt;
Плагин экспорта:
&lt;pre class="brush:delphi"&gt;library ExportRTF;

uses
  SysUtils,
  Classes,
  ActiveX,
  AxCtrls,
  PluginAPI in 'PluginAPI\Headers\PluginAPI.pas';

{$R *.res}
{$E .rep}

type
  TPlugin = class(TInterfacedObject, IUnknown, IPlugin, IExportPlugin)
  private
    FCore: ICore;
  protected
    // IPlugin
    function GetMask: WideString; safecall;
    function GetDescription: WideString; safecall;

    // IExportPlugin
    procedure ExportRTF(const ARTF: IStream; const AFileName: WideString); safecall;
  public
    constructor Create(const ACore: ICore);
  end;

{ TPlugin }

constructor TPlugin.Create(const ACore: ICore);
begin
  inherited Create;
  FCore := ACore;
  Assert(FCore.Version &gt;= 1);
end;

function TPlugin.GetMask: WideString;
begin
  Result := '*.rtf';
end;

function TPlugin.GetDescription: WideString;
begin
  Result := 'Rich Text (RTF)';
end;

procedure TPlugin.ExportRTF(const ARTF: IStream; const AFileName: WideString);
var
  FS: TFileStream;
  RTFStream: TOleStream;
begin
  FS := TFileStream.Create(AFileName, fmCreate or fmShareExclusive);
  try
    RTFStream := TOleStream.Create(ARTF);
    try
      FS.CopyFrom(RTFStream, 0);
    finally
      FreeAndNil(RTFStream);
    end;
  finally
    FreeAndNil(FS);
  end;
end;

// _________________________________________________________________

function Init(const ACore: ICore): IPlugin; safecall;
begin
  Result := TPlugin.Create(ACore);
end;

procedure Done; safecall;
begin

end;

exports
  Init name SPluginInitFuncName,
  Done name SPluginDoneFuncName;

end.&lt;/pre&gt; 
И плагин импорта:
&lt;pre class="brush:delphi"&gt;library ImportRTF;

uses
  SysUtils,
  Classes,
  ActiveX,
  AxCtrls,
  PluginAPI in 'PluginAPI\Headers\PluginAPI.pas';

{$R *.res}
{$E .rep}

type
  TPlugin = class(TInterfacedObject, IUnknown, IPlugin, IImportPlugin)
  private
    FCore: ICore;
  protected
    // IPlugin
    function GetMask: WideString; safecall;
    function GetDescription: WideString; safecall;

    // IImportPlugin
    procedure ImportRTF(const AFileName: WideString; const ARTF: IStream); safecall;
  public
    constructor Create(const ACore: ICore);
  end;

{ TPlugin }

constructor TPlugin.Create(const ACore: ICore);
begin
  inherited Create;
  FCore := ACore;
  Assert(FCore.Version &gt;= 1);
end;

function TPlugin.GetMask: WideString;
begin
  Result := '*.rtf';
end;

function TPlugin.GetDescription: WideString;
begin
  Result := 'Rich Text (RTF)';
end;

procedure TPlugin.ImportRTF(const AFileName: WideString; const ARTF: IStream); safecall;
var
  RTFStream: TOleStream;
  FS: TFileStream;
begin
  RTFStream := TOleStream.Create(ARTF);
  try
    FS := TFileStream.Create(AFileName, fmOpenRead or fmShareDenyWrite);
    try
      RTFStream.CopyFrom(FS, 0);
    finally
      FreeAndNil(FS);
    end;
  finally
    FreeAndNil(RTFStream);
  end;
end;

// _________________________________________________________________

function Init(const ACore: ICore): IPlugin; safecall;
begin
  Result := TPlugin.Create(ACore);
end;

procedure Done; safecall;
begin

end;

exports
  Init name SPluginInitFuncName,
  Done name SPluginDoneFuncName;

end.&lt;/pre&gt;
Как видите, они тривиальны - но лишь потому, что нам не нужно выполнять преобразование данных. Зато на этом простом примере видно, как нужно делать плагины. А если у вас возникнет желание сделать экспорт в HTML, PDF и т.п. - что ж, словами я описал этот процесс выше.&lt;br /&gt;
&lt;br /&gt;
Кстати говоря, обратите внимание, что при выбранной схеме один и тот же плагин может быть одновременно и плагином импорта и плагином экспорта - для этого ему всего лишь нужно реализовывать два интерфейса одновременно (и &lt;code&gt;IImportPlugin&lt;/code&gt; и &lt;code&gt;IExportPlugin&lt;/code&gt;). А ещё надо заметить, что если разрешить такую ситуацию, то наш подход с &lt;code&gt;FilterIndex&lt;/code&gt; работать не будет - т.к. один плагин будет зарегистрирован в обоих диалогах (и поэтому нам нужно будет иметь два свойства-индекса, а не одно). Так что этот момент - ещё один вопрос для фиксации в документации. Сделать можно и так и этак - это как вы захотите. Но что бы вы ни выбрали - этот выбор, главное, надо закрепить в документации.&lt;br /&gt; 
&lt;br /&gt;
&lt;a href="http://dl.dropbox.com/u/201788/Projects/Demos/PluginAPI_3.zip" title="Скачать PluginAPI_3.zip"&gt;Скачать пример к статье&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
В &lt;a title="Разработка системы плагинов, часть 3: активные плагины" href="http://www.gunsmoker.ru/2012/01/3.html"&gt;следующий раз&lt;/a&gt; - более сложные методы взаимодействия с плагинами, обработка ошибок.&lt;br /&gt;
&lt;br /&gt;
В следующих статьях я уже не буду так подробно расписывать процесс разработки API. Подразумевается, что вы вами уже умеете это делать. Т.е. вы сначала всё продумаете, потом создадите заголовочники, потом напишете документацию.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1702873441071265539-5777257077220706959?l=www.gunsmoker.ru' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/GunSmokersBlog?a=a35T_w1cseE:U70n6mA8TJ0:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GunSmokersBlog?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GunSmokersBlog?a=a35T_w1cseE:U70n6mA8TJ0:4cEx4HpKnUU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GunSmokersBlog?i=a35T_w1cseE:U70n6mA8TJ0:4cEx4HpKnUU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GunSmokersBlog?a=a35T_w1cseE:U70n6mA8TJ0:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GunSmokersBlog?i=a35T_w1cseE:U70n6mA8TJ0:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GunSmokersBlog?a=a35T_w1cseE:U70n6mA8TJ0:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GunSmokersBlog?i=a35T_w1cseE:U70n6mA8TJ0:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GunSmokersBlog?a=a35T_w1cseE:U70n6mA8TJ0:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GunSmokersBlog?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GunSmokersBlog?a=a35T_w1cseE:U70n6mA8TJ0:-BTjWOF_DHI"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GunSmokersBlog?i=a35T_w1cseE:U70n6mA8TJ0:-BTjWOF_DHI" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/GunSmokersBlog/~4/a35T_w1cseE" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.gunsmoker.ru/feeds/5777257077220706959/comments/default" title="Комментарии к сообщению" /><link rel="replies" type="text/html" href="http://www.gunsmoker.ru/2012/01/delphi-2-api.html#comment-form" title="Комментарии: 10" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1702873441071265539/posts/default/5777257077220706959?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1702873441071265539/posts/default/5777257077220706959?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/GunSmokersBlog/~3/a35T_w1cseE/delphi-2-api.html" title="Разработка системы плагинов в Delphi, часть 2: разработка API" /><author><name>Александр Алексеев</name><uri>https://profiles.google.com/113168002104297556003</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-aUMttZQBsuI/AAAAAAAAAAI/AAAAAAAAC3Y/QuZ7K9t_WzE/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://2.bp.blogspot.com/-SRj9LD0b59o/Twh0qwmc2kI/AAAAAAAADTo/J0yGcErVjo4/s72-c/RichEditDemo.png" height="72" width="72" /><thr:total>10</thr:total><feedburner:origLink>http://www.gunsmoker.ru/2012/01/delphi-2-api.html</feedburner:origLink></entry><entry gd:etag="W/&quot;A08FQ3c-cCp7ImA9WhRVEkQ.&quot;"><id>tag:blogger.com,1999:blog-1702873441071265539.post-7625352937635378534</id><published>2011-12-25T11:06:00.000+04:00</published><updated>2012-01-11T20:36:52.958+04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-01-11T20:36:52.958+04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Delphi" /><category scheme="http://www.blogger.com/atom/ns#" term="Статья" /><title>Разработка системы плагинов в Delphi</title><content type="html">Эта статья - продолжение &lt;a href="http://www.gunsmoker.ru/2008/12/1.html" title="Создаём систему плагинов, часть 1"&gt;старой серии&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
Напомню, что те статьи, несмотря на некоторый практический выхлоп в конце, всё же не очень-то освещали тему плагинов, а представляли собой копание во внутренностях работы DLL и пакетов Delphi. Было решено эту серию закончить - именно как серию о плагинах.&lt;br /&gt;
&lt;br /&gt;
Ну, во-первых, причиной для новой статьи стали просьбы её продолжить/закончить. За три года они поступали регулярно. Во-вторых, я заметил, что многие люди ссылаются на эту статью как на руководство по разработке плагинов, хотя, как я уже сказал, оно таким не является. Сам я всегда ссылался на него со словами "вот, это не читай, но в первой части тут неплохая подборка ссылок по интересующей тебя теме".&lt;br /&gt;
&lt;br /&gt;
В общем, все эти факторы в итоге перевесили мою лень и я решил написать нормальную статью про плагины.&lt;br /&gt;
&lt;br /&gt;
Примечание: я был бы очень благодарен, если кто-нибудь со знанием Visual Studio (C++) просмотрел бы раздел 8 (и особенно - касательно генерации заголовочников для Visual Studio C++) на предмет моих ошибок.&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;
&lt;h1&gt;
Оглавление&lt;/h1&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="#n1" title="Что у нас есть"&gt;Что у нас есть&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#n2" title="Что хотим получить"&gt;Что хотим получить&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#n3" title="Как пойдём"&gt;Как пойдём&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#n4" title="Основные понятия"&gt;Основные понятия&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#n5" title="Базовый набор правил и соглашений"&gt;Базовый набор правил и соглашений&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#n6" title="Структура файлов и папок"&gt;Структура файлов и папок&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#n7" title="Менеджер плагинов"&gt;Менеджер плагинов&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#n7_1" title="Загрузка одного плагина"&gt;Загрузка одного плагина&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#n7_2" title="Загрузка папки с плагинами"&gt;Загрузка папки с плагинами&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#n7_3" title="Отключение плагинов"&gt;Отключение плагинов&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#n8" title="API"&gt;API&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#n8_1" title="Управление заголовочниками"&gt;Управление заголовочниками&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#n8_2" title="Библиотеки типов"&gt;Библиотеки типов&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#n8_3" title="Генерация заголовочников для Delphi и C++ Builder"&gt;Генерация заголовочников для Delphi и C++ Builder&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#n8_4" title="Генерация заголовочников для Visual Studio C++"&gt;Генерация заголовочников для Visual Studio C++&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#n8_5" title="Выводы по ведению заголовочных файлов"&gt;Выводы по ведению заголовочных файлов&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#n8_6" title="Начала реализации функциональности"&gt;Начала реализации функциональности&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;br /&gt;
&lt;a name="n1"&gt;&lt;/a&gt;&lt;h1&gt;Что у нас есть&lt;/h1&gt;
Напомню основные способы реализации систем плагинов:
&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://www.delphikingdom.ru/asp/viewitem.asp?catalogid=677" title="COM. Автоматизация - от простого к сложному (Часть I)"&gt;COM-автоматизация&lt;/a&gt;. &lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.delphikingdom.ru/asp/viewitem.asp?catalogid=1338" title="Программа из кирпичиков, или плагины, плагины и еще раз плагины"&gt;Плагины в виде COM-объектов&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.delphikingdom.ru/asp/viewitem.asp?catalogid=468" title="Еще раз о Plugin's…"&gt;Плагины в виде пакетов + интерфейсы&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.delphikingdom.ru/asp/viewitem.asp?catalogid=274" title="Подгружаемые модули (plugins) в Delphi"&gt;Плагины в виде пакетов (bpl - packages)&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.gunsmoker.ru/2008/12/1.html" title="Создаём систему плагинов, часть 1"&gt;Плагины в виде DLL + интерфейсы&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.delphikingdom.ru/asp/viewitem.asp?catalogid=512" title="Использование DLL в качестве PlugIn-ов"&gt;Плагины в виде DLL&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.blaisepascal.ru/index.php?actie=blaisepascal/blaise6" title="TMS Scripter Studio Pro в 6-м выпуске журнала Blaise Pascal Magazine"&gt;Скрипты&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;a name="n2"&gt;&lt;/a&gt;&lt;h1&gt;Что хотим получить&lt;/h1&gt;
Мы хотим разработать систему плагинов - т.е. набор правил, по которым сторонние разработчики (т.е. не авторы программы) могли бы писать функциональность, встраивающуюся в основную программу и как-бы являющуюся её частью. При этом хочется, чтобы плагины можно было бы писать на любом (нативном) языке программирования, а не только Delphi и C++ Builder.&lt;br /&gt;
&lt;br /&gt;
При этом хочется, чтобы и работать было удобно, и чтобы у системы были бы мощные возможности. Как дополнительное пожелание - строить более-менее современную систему, а не по техникам 1995-го года.&lt;br /&gt;
&lt;br /&gt;
Система должна по возможности допускать развитие со временем (добавление новых возможностей в будущем) с сохранением обратной совместимости (старые плагины работают в новой системе).&lt;br /&gt;
&lt;br /&gt;
При этом предполагается, что плагины будут "тяжеловесные". Т.е. предполагающие существенный объём кода для реализации их функциональности. Ну, скажем, вроде как плагины звукового вывода в WinAmp, архиваторные, Lister-ые или FS-плагины в Total Commander или протокольные плагины в QIP.&lt;br /&gt;
&lt;br /&gt;
Некоторые задачи более удачно ложатся на "легковесные плагины" - скрипты. К примеру, как макросы в MS Word. Вам нужно выбирать систему на скриптах, если ваши плагины должны много обращаться к интерфейсу (и, в частном случае, - внутренним объектам) программы, но при этом сами они относительно просты. Так, что их можно выразить на скриптовом языке. И в этом случае эта статья - не для вас (в этом случае могу ткнуть в направлении статьи про TMS Scripter Studio Pro в &lt;a href="http://www.blaisepascal.ru/index.php?actie=blaisepascal/blaise6" title="Выпуск журнала №6 - Blaise Pascal Magazine"&gt;6-м номере журнала Blaise Pascal Magazine&lt;/a&gt;).&lt;br /&gt;
&lt;br /&gt;
В целом, в одном приложении вполне может быть реализовано две системы плагинов - для разных потребностей. К примеру, тот же MS Word (помимо макросов и VBA) поддерживает плагины в виде COM-объектов.&lt;br /&gt;
&lt;br /&gt;
&lt;a name="n3"&gt;&lt;/a&gt;&lt;h1&gt;Как пойдём&lt;/h1&gt;
Сразу замечу, что в этой статье я не буду говорить "почему". Я просто озвучу вариант, который выбрал я. Ответы на вопросы "почему" можно почерпнуть в &lt;a href="http://www.gunsmoker.ru/2008/12/1.html" title="Создаём систему плагинов, часть 1"&gt;старой серии статей&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
Что это значит? Реализации одной и той же задачи может быть несколько. Все из них имеют плюсы и минусы. Я не могу описать все возможные варианты - у меня не хватит ни времени, ни терпения это сделать. Поэтому я буду описывать лишь один вариант. Этот вариант я выбрал, исходя из требований к системе плагинов, изложенных в предыдущем пункте. &lt;br /&gt;
&lt;br /&gt;
По тексту я кратко могу пояснять причины выбора того или иного решения, но подробное объяснение и альтернативы будут за кадром.&lt;br /&gt;
&lt;br /&gt;
Если вы не начинающий (а зачем вы это читаете?) - вы можете сделать такой выбор сами. Если же вы начинающий и озабочены тем, что не можете сделать выбор, вот мой совет - просто следуйте этой статье. Когда у вас накопится опыт, вы уже более чётко будете представлять себе, что вы хотите получить, какие есть варианты достижения этого, какие у них плюсы и минусы. Так что вы сможете сделать свой выбор. Потом, не сейчас.&lt;br /&gt;
&lt;br /&gt;
&lt;a name="n4"&gt;&lt;/a&gt;&lt;h1&gt;Основные понятия&lt;/h1&gt;
Плаги́н (от англ. plug-in) — независимо компилируемый программный модуль, динамически подключаемый к основной программе, предназначенный для расширения и/или использования её возможностей. Плагин - это маленькая программка, которая встраивается в основную (большую) программу и расширяет её возможности.&lt;br /&gt;
&lt;br /&gt;
Основная программа при этом называется "ядро" (core).&lt;br /&gt;
&lt;br /&gt;
Чаще всего основной программой является .exe файл, а плагины - это .dll файлы.&lt;br /&gt;
&lt;br /&gt;
Ядро предоставляет сервисы, которые плагин может использовать. К ним относится предоставляемая плагину возможность зарегистрировать себя в ядре, а также протокол обмена данными с другими плагинами. Плагины являются зависимыми от сервисов, предоставляемых ядром и отдельно (сами по себе) не используются.&lt;br /&gt;
&lt;br /&gt;
Протокол (также называемый API - &lt;a href="http://ru.wikipedia.org/wiki/%D0%98%D0%BD%D1%82%D0%B5%D1%80%D1%84%D0%B5%D0%B9%D1%81_%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F_%D0%BF%D1%80%D0%B8%D0%BB%D0%BE%D0%B6%D0%B5%D0%BD%D0%B8%D0%B9" title="Википедия: Интерфейс программирования приложений"&gt;Application Programming Interface&lt;/a&gt;) - набор правил, контракт, которому соглашаются следовать ядро и плагины, чтобы понять друг друга и успешно взаимодействовать. Все сервисы ядра могут предоставляться только в рамках этого соглашения.&lt;br /&gt;
&lt;br /&gt;
Заголовочники, заголовочные файлы (headers) - набор исходных файлов, которые содержат объявления структур, использующихся в протоколе плагинов. Как правило, не содержат реализации. Заголовочные файлы предоставляются на нескольких языках - как правило, это язык, на котором написана программа (в нашем случае - Delphi), C++ (как стандарт) и некоторыми дополнительными (Basic и т.п.). Все эти файлы эквивалентны и просто представляют собой перевод из одного языка программирования на другой. Чем больше языков будет в комплекте - тем лучше. Если вы не предоставите заголовочные файлы для какого-то языка, то программисты на этом языке не смогут писать плагины для вашей программы, пока они сами не переведут файлы с предоставляемого языка (Delphi или C++) на их язык. Т.е. отсутствие заголовочников на каком-то языке - это не красный "стоп", но достаточное препятствие. В этом смысле очень удачно выглядит COM (где описание хранится в универсальном формате библиотеки типов - TLB). Вам не нужно ничего делать, кроме как распространять .tlb файл (который также может быть встроен в .dll). Если язык умеет работать с COM - он может импортировать информацию из TLB и создать заголовочник самостоятельно. TLB файл - это двоичный файл. Его создают и редактируют в каком-нибудь редакторе, либо он генерируется средой разработки. Его также можно "скомпилировать" из текстового описания - IDL файла (.idl или .ridl).&lt;br /&gt;
&lt;br /&gt;
Документация - представляет собой словесное описание протокола плагинов. Она пишется разработчиком программы для разработчиков плагинов (т.е. она односторонняя). Конечно, вы можете вести документацию и для себя лично, но сейчас речь не про неё. Итак, в этой документации как минимум должно быть формальное описание API  - перечисление всех функций, методов, интерфейсов и типов данных с объяснениями "как" и "зачем" (т.н. Reference). Дополнительно, документация может содержать неформальное описание процесса разработки плагинов (guide, how-to и т.п.). В простейших случаях документация пишется прямо в заголовочниках (комментариях), но чаще всего это файл (или файлы) в формате chm, html или pdf.&lt;br /&gt;
&lt;br /&gt;
SDK (Software Development Kit) - набор из заголовочников и документации. SDK - это то, что необходимо стороннему разработчику для написания плагинов к вашей программе. SDK - это то, что вы должны создать и публично распространять для всех желающих писать плагины к вашей программе.&lt;br /&gt;
&lt;br /&gt;
&lt;a name="n5"&gt;&lt;/a&gt;&lt;h1&gt;Базовый набор правил и соглашений&lt;/h1&gt;
Итак, для системы плагинов я выбрал схему DLL + &lt;a title="Глава 6. Интерфейсы" href="http://www.delphikingdom.ru/asp/viewitem.asp?catalogid=1202"&gt;интерфейсы&lt;/a&gt;. Замечу, что мы не можем использовать пакеты по соображениям межязыковой совместимости, а COM мне не нравится отложенной выгрузкой :) Также замечу, что предлагаемая схема вполне будет способна работать с плагинами в виде пакетов, если вы захотите это сделать, но при этом не будет никаких плюшек пакетов (управление памятью, разделение классов), окромя бонуса с DllMain (как описано в &lt;a href="http://www.gunsmoker.ru/2008/12/1.html" title="Создаём систему плагинов, часть 1"&gt;оригинальной серии статей&lt;/a&gt;).&lt;br /&gt;
&lt;br /&gt;
Правило номер два - в системе плагинов обязательно должны быть явные функции инициализации и финализации. Это значит, что каждая DLL должна экспортировать минимум 2 функции, которые будут вызываться непосредственно (первой) после загрузки плагина ядром и перед самой выгрузкой (последней).&lt;br /&gt;
&lt;br /&gt;
Правило номер три - модель вызова любых функций и методов в системе плагинов должна быть &lt;code&gt;safecall&lt;/code&gt;. Напомню, что &lt;code&gt;safecall&lt;/code&gt; - это, на самом деле, &lt;code&gt;stdcall&lt;/code&gt; с неявным HRESULT. Например, вот три примера эквивалентных объявлений (т.е. они написаны по-разному, но представляют собой одно и то же):
&lt;br /&gt;
&lt;pre class="brush:delphi"&gt;procedure DoSomething; safecall;
function  DoSomething: HRESULT; stdcall;

procedure DoSomethingElse(Param: Integer); safecall;
function  DoSomethingElse(Param: Integer): HRESULT; stdcall;

function DoSomethingMore: Integer; safecall;
function DoSomethingMore(out AResult: Integer): HRESULT; stdcall;&lt;/pre&gt;
&lt;br /&gt;
Правило номер четыре - обработка ошибок в стиле COM (safecall/HRESULT). Т.е. в Delphi это прозрачно будут исключения.&lt;br /&gt;
&lt;br /&gt;
Правило номер пять - все строки должны иметь тип &lt;code&gt;WideString&lt;/code&gt;.&lt;br /&gt;
&lt;br /&gt;
Правило номер шесть - вы не должны использовать типы данных Delphi, потому что они не имеют аналога в других языках. Например, &lt;code&gt;string&lt;/code&gt;, &lt;code&gt;array of&lt;/code&gt;, &lt;code&gt;TObject&lt;/code&gt;, &lt;code&gt;TForm&lt;/code&gt; (и вообще любые объекты и уж тем более компоненты) и т.п. Что можно использовать - целочисленные типы (&lt;code&gt;Integer&lt;/code&gt;, &lt;code&gt;Cardinal&lt;/code&gt;, &lt;code&gt;Int64&lt;/code&gt;, &lt;code&gt;UInt64&lt;/code&gt;, &lt;code&gt;NativeInt&lt;/code&gt;, &lt;code&gt;NativeUInt&lt;/code&gt;, &lt;code&gt;Byte&lt;/code&gt;, &lt;code&gt;Word&lt;/code&gt; и т.п.; я бы не рекомендовал использовать &lt;code&gt;Currency&lt;/code&gt;, если только он вам действительно нужен), вещественные (&lt;code&gt;Single&lt;/code&gt; и &lt;code&gt;Double&lt;/code&gt;; я бы рекомендовал избегать типов &lt;code&gt;Extended&lt;/code&gt; и &lt;code&gt;Comp&lt;/code&gt;, если только они действительно вам нужны и иначе никак), перечислимые и subrange-типы (с некоторыми оговорками), символьные типы (&lt;code&gt;AnsiChar&lt;/code&gt; и &lt;code&gt;WideChar&lt;/code&gt;, но не &lt;code&gt;Char&lt;/code&gt;), строки (только в виде &lt;code&gt;WideString&lt;/code&gt;), логический тип (&lt;code&gt;BOOL&lt;/code&gt;, но не &lt;code&gt;Boolean&lt;/code&gt;), интерфейсы (interface), в методах которых используются допустимые типы, записи (record) из вышеуказанных типов, а также указатели на них (в том числе указатели на &lt;i&gt;массивы&lt;/i&gt; из вышеуказанных типов, но не динамические массивы).&lt;br /&gt;
&lt;br /&gt;
Как узнать, какой тип можно использовать, а какой - нет? Относительно простое правило - если вы не видите тип в этом списке, и типа нет в модуле &lt;code&gt;Windows&lt;/code&gt; (модуле &lt;code&gt;Winapi.Windows&lt;/code&gt;, начиная с Delphi XE2), то этот тип использовать нельзя. Если же тип перечислен мною выше или находится в модуле &lt;code&gt;Windows&lt;/code&gt;/&lt;code&gt;Winapi.Windows&lt;/code&gt; - используйте его. Это достаточно грубое правило, но для начала - сойдёт.&lt;br /&gt;
&lt;br /&gt;
Правило номер семь - как только вы опубликовали какой-то тип (интерфейс), вы не должны его изменять. Если вам нужно его расширить или изменить - вы вводите новый интерфейс (новую версию интерфейса), но не меняете старый.&lt;br /&gt;
&lt;br /&gt;
Правило номер восемь - вы не используете разделяемый менеджер памяти (не подключаете модуля вроде &lt;code&gt;ShareMem&lt;/code&gt;, &lt;code&gt;SimpleShareMem&lt;/code&gt; и т.п.).&lt;br /&gt;
&lt;br /&gt;
Правило номер девять - все интерфейсы API должны иметь GUID. Все интерфейсы вне API (используемые только ядром) могут не иметь GUID.&lt;br /&gt;
&lt;br /&gt;
&lt;a name="n6"&gt;&lt;/a&gt;&lt;h1&gt;Структура папок и файлов&lt;/h1&gt;
Следующий вопрос - какие файлы нам понадобятся, что нам нужно создать. Для начала заметим, что когда мы говорим о системе плагинов, то мы можем говорить с двух сторон: со стороны ядра и со стороны плагина. И ядро и плагин должны следовать контракту, который мы для них определим, но делать это они будут с разных сторон. Это означает, что нам потребуются:
&lt;br /&gt;
&lt;ol&gt;
&lt;li&gt;Общие файлы - используются и ядром и плагинами&lt;/li&gt;
&lt;li&gt;Файлы ядра (то, к чему не имеют доступа плагины)&lt;/li&gt;
&lt;li&gt;Файлы плагинов (то, к чему не имеет доступа ядро)&lt;/li&gt;
&lt;li&gt;Файлы вне системы плагинов (функциональность ядра и плагинов)&lt;/li&gt;
&lt;/ol&gt;
&lt;br /&gt;
Поэтому, для начала, чтобы отделить п4 от пп1-3, давайте создадим новую папку (в любом месте), скажем - PluginAPI. В эту папку мы будем помещать всё то, что имеет отношение к системе плагинов, вне этой папки будем хранить всё остальное.&lt;br /&gt;
&lt;br /&gt;
Далее, в этой папке создадим три подпапки: Headers (для общих файлов), Core (для файлов ядра) и Plugins (для файлов плагинов). Headers - это то, что вы должны распространять публично для всех желающих писать плагины для вашей программы (часть SDK, заголовочники). Core будет использоваться только вами (как разработчиком программы), а Plugins часто может оказаться пустой, но если это не так, то там лежат файлы, которые вы должны будете прикладывать к Headers, когда вы распространяете SDK. Это - дополнительная функциональность, обёртка вокруг заголовочников.&lt;br /&gt;
&lt;br /&gt;
Далее, вы создаёте основную программу. Поскольку у меня программы нет, я возьму уже готовую - это демка RichEdit из комплекта поставки Delphi. Программа-пример представляет собой простой текстовый редактор. Файлы этой демки (remain.pas, remain.dfm и т.п.) относятся к пункту 4: файлы вне системы плагинов. Поэтому вы сохраняете основную программу в любом месте, а затем добавляете нужные файлы через Project/Add to project (либо прописываете пути поиска в проекте или IDE, но я не буду это показывать).&lt;br /&gt;
&lt;br /&gt;
Какие файлы? Вроде у нас ещё ничего нет? Вот-вот. Так что давайте (наконец-то!) что-то попишем...&lt;br /&gt;
&lt;br /&gt;
&lt;a name="n7"&gt;&lt;/a&gt;&lt;h1&gt;Менеджер плагинов&lt;/h1&gt;
Откройте свою программу (в примере - скопированную демку RichEdit) и сделайте File/New unit. Сохраните этот модуль в папке PluginAPI\Core под именем PluginManager.pas и введите в него такую заготовку:
&lt;br /&gt;
&lt;pre class="brush:delphi"&gt;unit PluginManager;

interface

type
  IPluginManager = interface
  end;

function Plugins: IPluginManager;

implementation

uses
  SysUtils,
  Classes;

type
  TPluginManager = class(TInterfacedObject, IPluginManager)

  end;

//________________________________________________________________

var
  FPluginManager: IPluginManager;

function Plugins: IPluginManager;
begin
  Result := FPluginManager;
end;

initialization
  FPluginManager := TPluginManager.Create;
finalization
  FPluginManager := nil;
end.&lt;/pre&gt;
Этот код объявляет и создаёт менеджер плагинов. Менеджер плагинов - это вспомогательный код в вашей программе, который служит для управления плагинами. Он выполняет всю черновую работу с плагинами, так что вам &lt;a href="http://www.gunsmoker.ru/2010/08/9897.html" title="98/97: Отделяйте интерфейс пользователя от кода"&gt;не нужно засорять код своей основной программы&lt;/a&gt;. Менеджер создаётся автоматически при старте программы и автоматически удаляется при выходе из программы. &lt;br /&gt;
&lt;br /&gt;
Вы видите тут три части. Секция &lt;code&gt;interface&lt;/code&gt; перечисляет то, с чем будет работать основная программа. Пока это глобальная функция &lt;code&gt;Plugins&lt;/code&gt; для доступа к менеджеру и сам менеджер - интерфейс &lt;code&gt;IPluginManager&lt;/code&gt;: пока пустой.&lt;br /&gt;
&lt;br /&gt;
Вторая часть (до подчёркивания) содержит код менеджера плагинов - тоже пока пустой. Это черновая работа, которую мы скрываем "под капотом" модуля, чтобы она не засоряла код основной программы.&lt;br /&gt;
&lt;br /&gt;
Часть три (после черты) представляет собой код инициализации и удаления менеджера плагинов. Поскольку мы работаем с ним через интерфейс, то для его удаления мы просто очищаем ссылку. Обратите внимание, что поскольку менеджер плагинов в данном случае является глобальным объектом, то мы используем для его хранения глобальную переменную. Однако заметьте, что при этом глобальная переменная объявлена последней и лежит практически в самом низу текста (новый код будет добавляться выше черты, но не ниже). Плюс, она закрыта от внешнего доступа секцией &lt;code&gt;implementation&lt;/code&gt;. Т.е. она &lt;a href="http://www.gunsmoker.ru/2011/04/blog-post.html" title="Что плохого в глобальных переменных?"&gt;максимально изолирована от внешних воздействий&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
Итак, что будет делать менеджер плагинов? Ну, наверное для начала плагины надо бы найти и загрузить. Как это обычно делают? Есть два способа:
&lt;br /&gt;
&lt;ol&gt;
&lt;li&gt;Пользователь указывает в настройках программы, какие плагины нужно включать.&lt;br /&gt;
Плюсы:
&lt;ul&gt;
&lt;li&gt;Пользователь может использовать плагин в любом месте.&lt;/li&gt;
&lt;li&gt;Пользователь может отключать плагин из интерфейса программы, не удаляя его.&lt;/li&gt;
&lt;/ul&gt;
Минусы:
&lt;ul&gt;
&lt;li&gt;Пользователю нужно настраивать плагины вручную.&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;/li&gt;
&lt;li&gt;Программа загружает как плагины все файлы из предопределённой папки.&lt;br /&gt;
Плюсы:
&lt;ul&gt;
&lt;li&gt;Пользователю ничего не надо делать.&lt;/li&gt;
&lt;/ul&gt;
Минусы:
&lt;ul&gt;
&lt;li&gt;Чтобы удалить или добавить плагин, его нужно скопировать в папку или удалить из неё.&lt;/li&gt;
&lt;li&gt;Плагин нельзя "отключить", не удалив его.&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;/li&gt;
&lt;/ol&gt;
В принципе, эти подходы можно комбинировать. Скажем, автозагружать плагины из папки, но при этом не загружать отключенные (исключения) и допускать загрузку по команде из другого места.&lt;br /&gt;
&lt;br /&gt;
Итак, что должен уметь делать менеджер плагинов, чтобы реализовать какую-то из схем выше?&lt;br /&gt;
&lt;br /&gt;
&lt;a name="n7_1"&gt;&lt;/a&gt;&lt;h4&gt;Загрузка одного плагина&lt;/h4&gt;
Какую-бы схему мы ни реализовали бы - нам обязательно в любом случае потребуется функция загрузки одного плагина. Вот давайте с неё и начнём:
&lt;br /&gt;
&lt;pre class="brush:delphi"&gt;unit PluginManager;

interface

uses
  Windows;

type
  IPlugin = interface
  // protected
    function GetIndex: Integer;
    function GetHandle: HMODULE;
    function GetFileName: String;
  // public
    property Index: Integer read GetIndex;
    property Handle: HMODULE read GetHandle;
    property FileName: String read GetFileName;
  end;

  IPluginManager = interface
  // protected
    function GetItem(const AIndex: Integer): IPlugin;
    function GetCount: Integer;
  // public
    function LoadPlugin(const AFileName: String): IPlugin;
    procedure UnloadPlugin(const AIndex: Integer);

    property Items[const AIndex: Integer]: IPlugin read GetItem; default;
    property Count: Integer read GetCount;
  end;

function Plugins: IPluginManager;

implementation

uses
  SysUtils,
  Classes;

type
  TPluginManager = class(TInterfacedObject, IPluginManager)
  private
    FItems: array of IPlugin;
    FCount: Integer;
  protected
    function GetItem(const AIndex: Integer): IPlugin;
    function GetCount: Integer;
  public
    function LoadPlugin(const AFileName: String): IPlugin;
    procedure UnloadPlugin(const AIndex: Integer);
    function IndexOf(const APlugin: IPlugin): Integer;
  end;

  TPlugin = class(TInterfacedObject, IPlugin)
  private
    FManager: TPluginManager;
    FFileName: String;
    FHandle: HMODULE;
  protected
    function GetIndex: Integer;
    function GetHandle: HMODULE;
    function GetFileName: String;
  public
    constructor Create(const APluginManger: TPluginManager; const AFileName: String); virtual;
    destructor Destroy; override;
  end;

{ TPluginManager }

function TPluginManager.LoadPlugin(const AFileName: String): IPlugin;
begin
  // Загружаем плагин
  Result := TPlugin.Create(FManager, AFileName);

  // Заносим в список
  if Length(FItems) &amp;gt;= FCount then // "Capacity"
    SetLength(FItems, Length(FItems) + 64);
  FItems[FCount] := Result;
  Inc(FCount);
end;

procedure TPluginManager.UnloadPlugin(const AIndex: Integer);
var
  X: Integer;
begin
  // Выгрузить плагин
  FItems[AIndex] := nil;
  // Сдвинуть плагины в списке, чтобы закрыть "дырку"
  for X := AIndex to FCount - 1 do
    FItems[X] := FItems[X + 1];
  // Не забыть учесть последний
  FItems[FCount - 1] := nil;
  Dec(FCount);
end;

function TPluginManager.IndexOf(const APlugin: IPlugin): Integer;
var
  X: Integer;
begin
  Result := -1;
  for X := 0 to FCount - 1 do
    if FItems[X] = APlugin then
    begin
      Result := X;
      Break;
    end;
end;

function TPluginManager.GetCount: Integer;
begin
  Result := FCount;
end;

function TPluginManager.GetItem(const AIndex: Integer): IPlugin;
begin
  Result := FItems[AIndex];
end;

{ TPlugin }

constructor TPlugin.Create(const APluginManger: TPluginManager;
  const AFileName: String);
begin
  inherited Create;
  FManager := APluginManger;
  FFileName := AFileName;
  FHandle := SafeLoadLibrary(AFileName);
  Win32Check(FHandle &amp;lt;&amp;gt; 0);
end;

destructor TPlugin.Destroy;
begin
  if FHandle &amp;lt;&amp;gt; 0 then
  begin
    FreeLibrary(FHandle);
    FHandle := 0;
  end;
  inherited;
end;

function TPlugin.GetFileName: String;
begin
  Result := FFileName;
end;

function TPlugin.GetHandle: HMODULE;
begin
  Result := FHandle;
end;

function TPlugin.GetIndex: Integer;
begin
  Result := FManager.IndexOf(Self);
end;

//________________________________________________________________

...

end.&lt;/pre&gt;
Ух, что-то тут много всего появилось. Давайте по порядку. Начнём с изменений в &lt;code&gt;IPluginManager&lt;/code&gt;.&lt;br /&gt;
&lt;br /&gt;
Собственно функция загрузки плагина - это &lt;code&gt;LoadPlugin&lt;/code&gt;:
&lt;br /&gt;
&lt;pre class="brush:delphi"&gt;function LoadPlugin(const AFileName: String): IPlugin;&lt;/pre&gt;
Как видите, она принимает имя файла плагина для загрузки. Очевидно, что она должна возвращать загруженный плагин. Нам надо его как-то представить - вот тут появляется новая сущность: "плагин". Он у нас представлен новым интерфейсом &lt;code&gt;IPlugin&lt;/code&gt;.&lt;br /&gt;
&lt;br /&gt;
Лирическое отступление. Сразу заметим такую вещь: сейчас мы говорим про внутреннюю кухню ядра, мы ещё не начали формировать API (напомню: исходный файл менеджера плагинов лежит в PluginAPI\Core, а не в PluginAPI\Headers). Именно поэтому тут совершенно нормально использовать &lt;code&gt;string&lt;/code&gt;, соглашение вызова &lt;code&gt;register&lt;/code&gt; и другие вещи, специфичные для Delphi: потому что их будет использовать только наша программа и никто иной. &lt;br /&gt;
&lt;br /&gt;
Окей, возвращаясь к коду. Функция &lt;code&gt;LoadPlugin&lt;/code&gt; устроена просто (если вы посмотрите на её реализацию в классе &lt;code&gt;TPluginManager&lt;/code&gt;):
&lt;br /&gt;
&lt;pre class="brush:delphi"&gt;function TPluginManager.LoadPlugin(const AFileName: String): IPlugin;
begin
  // Загружаем плагин
  Result := TPlugin.Create(FManager, AFileName);

  // Заносим в список
  if Length(FItems) &amp;gt;= FCount then // "Capacity"
    SetLength(FItems, Length(FItems) + 64);
  FItems[FCount] := Result;
  Inc(FCount);
end;&lt;/pre&gt;
Она создаёт наш плагин (в виде объекта &lt;code&gt;TPlugin&lt;/code&gt;, реализующего интерфейс &lt;code&gt;IPlugin&lt;/code&gt;) и регистрирует его в своём "списке всех плагинов" - &lt;code&gt;FItems&lt;/code&gt;. Как вы видите, функция &lt;code&gt;LoadPlugin&lt;/code&gt; на самом деле не выполняет непосредственно загрузку плагина, а делегирует (передаёт) эту работу классу-оболочке для плагина &lt;code&gt;TPlugin&lt;/code&gt;. Это - правильно. Загрузка плагина - забота плагина и его класса. Управление плагинами - забота менеджера плагинов. Правильное разделение обязанностей, короче говоря.&lt;br /&gt;
&lt;br /&gt;
Раз уж у нас появился "список плагинов", то неплохо бы его выставить наружу - так в &lt;code&gt;IPluginManager&lt;/code&gt; появляются свойства &lt;code&gt;Items&lt;/code&gt; и &lt;code&gt;Count&lt;/code&gt;: 
&lt;br /&gt;
&lt;pre class="brush:delphi"&gt;  IPluginManager = interface
  // protected
    function GetItem(const AIndex: Integer): IPlugin;
    function GetCount: Integer;
  // public
    ...
    property Items[const AIndex: Integer]: IPlugin read GetItem; default;
    property Count: Integer read GetCount;
  end;&lt;/pre&gt;
Они дают ядру доступ к списку плагинов, позволяя их перебрать.&lt;br /&gt;
&lt;br /&gt;
Ну и если плагин загружен, то надо ведь и обратное действие уметь выполнять: выгрузку плагинов. Вот у нас появляется ещё один метод: &lt;code&gt;UnloadPlugin&lt;/code&gt;. 
&lt;br /&gt;
&lt;pre class="brush:delphi"&gt;procedure UnloadPlugin(const AIndex: Integer);&lt;/pre&gt;
Метод принимает номер плагина для его выгрузки. Из его реализации (в классе &lt;code&gt;TPluginManager&lt;/code&gt;):
&lt;br /&gt;
&lt;pre class="brush:delphi"&gt;procedure TPluginManager.UnloadPlugin(const AIndex: Integer);
var
  X: Integer;
begin
  // Выгрузить плагин
  FItems[AIndex] := nil;
  // Сдвинуть плагины в списке, чтобы закрыть "дырку"
  for X := AIndex to FCount - 1 do
    FItems[X] := FItems[X + 1];
  // Не забыть учесть последний
  FItems[FCount - 1] := nil;
  Dec(FCount);
end;&lt;/pre&gt;
видно, что он просто удаляет плагин из списка, уничтожая его.&lt;br /&gt;
&lt;br /&gt;
Пока мы ещё говорим про класс менеджера плагинов, заметим такую вещь: мы работаем с интерфейсами и для &lt;code&gt;FItems&lt;/code&gt; у нас используется динамический массив. Это значит, что все типы данных являются управляемыми и нам не нужно их освобождать руками. Т.е. если ядро вызовет три раза &lt;code&gt;LoadPlugin&lt;/code&gt; для загрузки трёх плагинов, а потом просто выйдет, то у нас не будет никакой утечки памяти. Менеджер плагинов начнёт удаляться в &lt;code&gt;finalization&lt;/code&gt; (при условии, что ссылку на него никто больше не держит), при этом автоматически очистится &lt;code&gt;FItems&lt;/code&gt; (как авто-управляемый динамический массив), а все его элементы будут автоматически освобождены (как автоуправляемые интерфейсы) - опять же при условии, что на них больше нет ссылок. В этот момент и произойдёт выгрузка каждого плагина.&lt;br /&gt;
&lt;br /&gt;
Итак, с &lt;code&gt;IPluginManager&lt;/code&gt; и &lt;code&gt;TPluginManager&lt;/code&gt; мы закончили, давайте посмотрим теперь на &lt;code&gt;IPlugin&lt;/code&gt;/&lt;code&gt;TPlugin&lt;/code&gt;.&lt;br /&gt;
&lt;br /&gt;
Тут пока совсем всё просто: наружу интерфейс выставляет несколько свойств, которые могут быть интересны ядру: индекс плагина, имя файла и описатель загруженного файла. 
&lt;br /&gt;
&lt;pre class="brush:delphi"&gt;  IPlugin = interface
  // protected
    function GetIndex: Integer;
    function GetHandle: HMODULE;
    function GetFileName: String;
  // public
    property Index: Integer read GetIndex;
    property Handle: HMODULE read GetHandle;
    property FileName: String read GetFileName;
  end;&lt;/pre&gt;
Реализация методов в классе &lt;code&gt;TPlugin&lt;/code&gt; тривиальна. Единственный момент - обратите внимание на реализацию свойства &lt;code&gt;Index&lt;/code&gt;. 
&lt;br /&gt;
&lt;pre class="brush:delphi"&gt;function TPlugin.GetIndex: Integer;
begin
  Result := FManager.IndexOf(Self);
end;&lt;/pre&gt;
В данном случае оно реализовано через поиск (см. &lt;code&gt;TPluginManager.IndexOf&lt;/code&gt;).
&lt;br /&gt;
&lt;pre class="brush:delphi"&gt;function TPluginManager.IndexOf(const APlugin: IPlugin): Integer;
var
  X: Integer;
begin
  Result := -1;
  for X := 0 to FCount - 1 do
    if FItems[X] = APlugin then
    begin
      Result := X;
      Break;
    end;
end;&lt;/pre&gt;
Можно было бы сделать иначе: хранить индекс плагина в поле класса &lt;code&gt;TPlugin&lt;/code&gt;. Оба решения имеют как плюсы, так и минусы, но в целом разницы нет никакой (у вас будет загружено более 1000 плагинов? Навряд ли). &lt;br /&gt;
&lt;br /&gt;
Более интересно выглядят (скрытые) конструктор и деструктор плагина. Помимо тривиальностей по инициализации и заполнению свойств плагина, они выполняют собственно работу, ради которой всё и затевалось: загрузку и выгрузку DLL.
&lt;br /&gt;
&lt;pre class="brush:delphi"&gt;constructor TPlugin.Create(const APluginManger: TPluginManager;
  const AFileName: String);
begin
  inherited Create;
  FManager := APluginManger;
  FFileName := AFileName;
  FHandle := SafeLoadLibrary(AFileName); // работа: загрузка
  Win32Check(FHandle &amp;lt;&amp;gt; 0);
end;

destructor TPlugin.Destroy;
begin
  if FHandle &amp;lt;&amp;gt; 0 then // работа: выгрузка
  begin
    FreeLibrary(FHandle);
    FHandle := 0;
  end;
  inherited;
end;&lt;/pre&gt;
&lt;br /&gt;
Уже в этот момент этот код можно потестировать. К примеру, если вы бросите на форму основной программы &lt;code&gt;TButton&lt;/code&gt;, &lt;code&gt;TEdit&lt;/code&gt; и &lt;code&gt;TListbox&lt;/code&gt;, то вы можете написать такой тестовый код:
&lt;br /&gt;
&lt;pre class="brush:delphi"&gt;type
  TMainForm = class(TForm)
    ...
  private
    ...
    procedure UpdatePluginsList;
  end;

...

implementation

uses
  REAbout, RichEdit, ShellAPI, ReInit,
  PluginManager; // &amp;lt;- новый модуль

...

procedure TMainForm.Button1Click(Sender: TObject);
begin
  Plugins.LoadPlugin(Edit1.Text);
  UpdatePluginsList;
end;

procedure TMainForm.UpdatePluginsList;
var
  X: Integer;
begin
  ListBox1.Items.BeginUpdate;
  try
    ListBox1.Items.Clear;
    for X := 0 to Plugins.Count - 1 do
      ListBox1.Items.Add(IntToStr(Plugins[X].Index) + ': ' + Plugins[X].FileName);
  finally
    ListBox1.Items.EndUpdate;
  end;
end;

...

end.&lt;/pre&gt;
Запустите программу, вы можете ввести в Edit имя файла любой DLL и нажать на кнопку для загрузки её как плагина. При этом она тут же появится в списке загруженных плагинов.&lt;br /&gt;
&lt;br /&gt;
Конечно, сейчас это может показаться несколько странным: как это так получается, что любая DLL может быть загружена как плагин? И что вообще делает этот плагин? Собственно, ничего странного нет: ведь мы пока не написали ни одной строчки API, у нас нет никакого контракта для плагинов, пока мы писали лишь код поддержки. Именно поэтому для плагина (пока!) подходит любая DLL и она просто ничего не делает.&lt;br /&gt;
&lt;br /&gt;
По этой же причине, уже написанный код - универсален и может быть использован как база для любой новой системы плагинов.&lt;br /&gt;
&lt;br /&gt;
&lt;a name="n7_2"&gt;&lt;/a&gt;&lt;h4&gt;Загрузка папки с плагинами&lt;/h4&gt;
Следующая задача - загрузить целую папку с плагинами. Раз у нас уже есть функция загрузки одного плагина, то теперь мы можем вызвать её в цикле по всем плагинам в папке (пусть даже пока функция загрузки просто грузит DLL, а сами плагины пока что ничего не делают - не беда).&lt;br /&gt;
&lt;br /&gt;
Я не буду приводить весь код целиком, а приведу лишь добавленный и изменённый код. Код в многоточиях оставлен без изменений.
&lt;br /&gt;
&lt;pre class="brush:delphi"&gt;interface

uses
  Windows,
  SysUtils,
  Classes;

type
  EPluginManagerError = class(Exception);
    EPluginLoadError = class(EPluginManagerError);
      EPluginsLoadError = class(EPluginLoadError)
      private
        FItems: TStrings;
      public
        constructor Create(const AText: String; const AFailedPlugins: TStrings);
        destructor Destroy; override;
        property FailedPluginFileNames: TStrings read FItems;
      end;

...

  IPluginManager = interface
    ...  
    procedure LoadPlugins(const AFolder: String; const AFileExt: String = '');
    ...
  end;

...

implementation

resourcestring
  rsPluginsLoadError = 'One or more plugins has failed to load:' + sLineBreak + '%s';

type
  TPluginManager = class(TInterfacedObject, IPluginManager)
    ...  
    procedure LoadPlugins(const AFolder, AFileExt: String);
    ...
  end;

...

procedure TPluginManager.LoadPlugins(const AFolder, AFileExt: String);

  function PluginOK(const APluginName, AFileExt: String): Boolean;
  begin
    Result := (AFileExt = '');
    if Result then
      Exit;
    Result := SameFileName(ExtractFileExt(APluginName), AFileExt);
  end;

var
  Path: String;
  SR: TSearchRec;
  Failures: TStringList;
  FailedPlugins: TStringList;
begin
  Path := IncludeTrailingPathDelimiter(AFolder);

  Failures := TStringList.Create;
  FailedPlugins := TStringList.Create;
  try
    if FindFirst(Path + '*.*', faNormal, SR) = 0 then
    try
      repeat
        if ((SR.Attr and faDirectory) = 0) and
           PluginOK(SR.Name, AFileExt) then
        try
          LoadPlugin(Path + SR.Name);
        except
          on E: Exception do
          begin
            FailedPlugins.Add(SR.Name);
            Failures.Add(Format('%s: %s', [SR.Name, E.Message]));
          end;
        end;
      until FindNext(SR) &amp;lt;&amp;gt; 0;
    finally
      FindClose(SR);
    end;

    if Failures.Count &amp;gt; 0 then
      raise EPluginsLoadError.Create(Format(rsPluginsLoadError, [Failures.Text]), FailedPlugins);
  finally
    FreeAndNil(FailedPlugins);
    FreeAndNil(Failures);
  end;
end;

...

function TPluginManager.LoadPlugin(const AFileName: String): IPlugin;
begin
  // Загружаем плагин
  try
    Result := TPlugin.Create(FManager, AFileName);
  except
    on E: Exception do
      raise EPluginLoadError.Create(Format('[%s] %s', [E.ClassName, E.Message]));
  end;

  ...
end;

...

{ EPluginsLoadError }

constructor EPluginsLoadError.Create(const AText: String;
  const AFailedPlugins: TStrings);
begin
  inherited Create(AText);
  FItems := TStringList.Create;
  FItems.Assign(AFailedPlugins);
end;

destructor EPluginsLoadError.Destroy;
begin
  FreeAndNil(FItems);
  inherited;
end;

//________________________________________________________________&lt;/pre&gt;
Опять у нас получилось больше кода, чем изначально планировалось. И вот почему.&lt;br /&gt;
&lt;br /&gt;
Как видите, основной новый метод тут - &lt;code&gt;LoadPlugins&lt;/code&gt;. Он принимает имя папки и необязательное расширение файлов. Реализация метода ищет в указанной папке все файлы и загружает те из них, которые подходят под указанное расширение. Если расширение не указывать, то будут загружены вообще все файлы.&lt;br /&gt;
&lt;br /&gt;
К примеру:
&lt;br /&gt;
&lt;pre class="brush:delphi"&gt;procedure TMainForm.Button2Click(Sender: TObject);
begin
  try
    Plugins.LoadPlugins('C:\Windows', '.dll'); // загрузить все DLL из папки Windows
  finally
    UpdatePluginsList;
  end;
end;&lt;/pre&gt;
или:
&lt;br /&gt;
&lt;pre class="brush:delphi"&gt;procedure TMainForm.Button2Click(Sender: TObject);
begin
  try
    Plugins.LoadPlugins(Edit1.Text); // загрузить вообще все файлы из указанной папки
  finally
    UpdatePluginsList;
  end;
end;&lt;/pre&gt;
В реализации метода &lt;code&gt;LoadPlugins&lt;/code&gt; нет ничего сложного, за исключением одного момента: обработка ошибок. Предположим, один из плагинов не смог загрузится. Что тогда делать? Есть два варианта: первый - остановиться и сообщить об ошибке. Это самый простой вариант и он мне не нравится. Зато можно было бы ничего больше не писать. Вариант два - продолжить загрузку плагинов. Именно этот вариант я и показал. Тут возникает вопрос, что делать с ошибкой загрузки плагина и как о ней сообщить. Ведь если мы грузим целую папку с плагинами, то отказать в загрузке может не один плагин, а много.&lt;br /&gt;
&lt;br /&gt;
Ответ заключается в том, что мы запоминаем плагины, которые не удалось загрузить. Но при этом продолжаем их загружать. В самом конце операции загрузки мы возбуждаем одну-единственную ошибку (одну - даже если &lt;i&gt;несколько&lt;/i&gt; плагинов отказало), в которой суммируем информацию.&lt;br /&gt;
&lt;br /&gt;
Тут возникает два момента: во-первых, раз уж мы возбуждаем свою ошибку, то нам надобен для этого класс. Использовать &lt;code&gt;Exception&lt;/code&gt; я вам категорически запрещаю. Поэтому мы вводим свои классы ошибок:
&lt;br /&gt;
&lt;pre class="brush:delphi"&gt;type
  EPluginManagerError = class(Exception); // для всех ошибок менеджера плагинов
    EPluginLoadError = class(EPluginManagerError); // для ошибок загрузки плагинов
      EPluginsLoadError = class(EPluginLoadError); // для ошибок загрузки плагинов в папке&lt;/pre&gt;
Кроме того, для последней ситуации кроме сообщения нам требуется хранить дополнительную информацию: список плагинов, которые отказались грузится. Зачем это нужно? Ну, скажем, ядро может отключить сбойнувшие плагины, чтобы не грузить их во второй раз в будущем при следующей загрузке. Плагин сбойнул? Отключили. Если пользователь исправит ошибку, препятствующую загрузке плагина - он включит плагин обратно.&lt;br /&gt;
&lt;br /&gt;
Итак, для этого к исключению мы пристыковываем список файловых имён:
&lt;br /&gt;
&lt;pre class="brush:delphi"&gt;EPluginsLoadError = class(EPluginLoadError)
private
  FItems: TStrings;
public
  constructor Create(const AText: String; const AFailedPlugins: TStrings);
  destructor Destroy; override;
  property FailedPluginFileNames: TStrings read FItems;
end;&lt;/pre&gt;
А метод &lt;code&gt;LoadPlugins&lt;/code&gt; теперь может собирать информацию о сбойнувших плагинах:
&lt;br /&gt;
&lt;pre class="brush:delphi"&gt;try
  LoadPlugin(Path + SR.Name);
except
  on E: Exception do
  begin
    // Запомнили имя файла сбойнувшего плагина
    FailedPlugins.Add(SR.Name); 
    // И вписали возникшую проблему в общий список ошибок
    Failures.Add(Format('%s: %s', [SR.Name, E.Message])); 
  end;
end;&lt;/pre&gt;
и возбуждать единую ошибку в конце работы:
&lt;br /&gt;
&lt;pre class="brush:delphi"&gt;if Failures.Count &amp;gt; 0 then // есть хотя бы один сбойнувший плагин?
  raise EPluginsLoadError.Create(Format(rsPluginsLoadError, [Failures.Text]), FailedPlugins);&lt;/pre&gt;
Ну и раз уж мы определили свои типы ошибок, неплохо бы их соблюдать. Поэтому изменяем метод &lt;code&gt;LoadPlugin&lt;/code&gt;, чтобы при исключении он возбуждал бы именно наш тип ошибки:
&lt;br /&gt;
&lt;pre class="brush:delphi"&gt;function TPluginManager.LoadPlugin(const AFileName: String): IPlugin;
begin
  // Загружаем плагин
  try
    Result := TPlugin.Create(FManager, AFileName);
  except
    on E: Exception do
      raise EPluginLoadError.Create(Format('[%s] %s', [E.ClassName, E.Message]));
  end;

  ...
end;&lt;/pre&gt;
Ну, вроде и всё с этим. Как обычно, вы можете проверить код в своём приложении - пусть оно грузит содержимое подпапки Plugins. Пусть даже сейчас это ничего не будет делать.&lt;br /&gt;
&lt;br /&gt;
&lt;a name="n7_3"&gt;&lt;/a&gt;&lt;h4&gt;Отключение плагинов&lt;/h4&gt;
Итак, у нас есть функция загрузки одного плагина по команде пользователя, есть автозагрузка плагинов из папки, а теперь осталось лишь сделать отключение загрузки плагина без его удаления.&lt;br /&gt;
&lt;br /&gt;
К этой задаче можно подступиться по разному. Я предлагаю такое решение: завести "чёрный список" плагинов. Любой плагин можно добавить в этот список. Любой плагин можно удалить из списка. Функция &lt;code&gt;LoadPlugin&lt;/code&gt; (а следовательно и функция &lt;code&gt;LoadPlugins&lt;/code&gt;) не станет грузить плагин, если он находится в чёрном списке. Соотвественно, чтобы отключить плагин - его надо занести в чёрный список (рассматривайте его как список отключенных плагинов), а чтобы включить - удалить из этого списка.&lt;br /&gt;
&lt;br /&gt;
Плагины можно идентифицировать по разному, но поскольку речь идёт о стадии загрузки, то наиболее естественно будет идентификация по полному имени файла - то, что передаётся в функцию &lt;code&gt;LoadPlugin&lt;/code&gt;.&lt;br /&gt;
&lt;br /&gt;
Итого получаем (и снова, я привожу лишь изменения, а не весь код):
&lt;br /&gt;
&lt;pre class="brush:delphi"&gt;type
  ...
  IPluginManager = interface
    ...
    procedure Ban(const AFileName: String);
    procedure Unban(const AFileName: String);

    procedure SaveSettings(const ARegPath: String);
    procedure LoadSettings(const ARegPath: String);
    ...
  end;

...

implementation

...

type
  TPluginManager = class(TInterfacedObject, IPluginManager)
  private
    ...
    FBanned: TStringList;
  protected
    ...
    function CanLoad(const AFileName: String): Boolean;
  public
    constructor Create;
    destructor Destroy; override;
    ...
    procedure Ban(const AFileName: String);
    procedure Unban(const AFileName: String);
    procedure SaveSettings(const ARegPath: String);
    procedure LoadSettings(const ARegPath: String);
  end;

  ...

{ TPluginManager }

constructor TPluginManager.Create;
begin
  inherited Create;
  FBanned := TStringList.Create;
end;

destructor TPluginManager.Destroy;
begin
  FreeAndNil(FBanned);
  inherited;
end;

function TPluginManager.LoadPlugin(const AFileName: String): IPlugin;
begin
  if not CanLoad(AFileName) then
  begin
    Result := nil;
    Exit;
  end;

  // Загружаем плагин
  ...
end;

...

procedure TPluginManager.Ban(const AFileName: String);
begin
  Unban(AFileName);
  FBanned.Add(AFileName);
end;

procedure TPluginManager.Unban(const AFileName: String);
var
  X: Integer;
begin
  for X := 0 to FBanned.Count - 1 do
    if SameFileName(FBanned[X], AFileName) then
    begin
      FBanned.Delete(X);
      Break;
    end;
end;

function TPluginManager.CanLoad(const AFileName: String): Boolean;
var
  X: Integer;
begin
  // Не грузить отключенные
  for X := 0 to FBanned.Count - 1 do
    if SameFileName(FBanned[X], AFileName) then
    begin
      Result := False;
      Exit;
    end;

  // Не грузить уже загруженные
  for X := 0 to FCount - 1 do
    if SameFileName(FItems[X].FileName, AFileName) then
    begin
      Result := False;
      Exit;
    end;

  Result := True;
end;

const
  SRegDisabledPlugins = 'Disabled plugins';
  SRegPluginX         = 'Plugin%d';

procedure TPluginManager.SaveSettings(const ARegPath: String);
var
  Reg: TRegIniFile;
  Path: String;
  X: Integer;
begin
  Reg := TRegIniFile.Create(ARegPath, KEY_ALL_ACCESS);
  try
    // Удаляем старые
    Reg.EraseSection(SRegDisabledPlugins);
    Path := ARegPath + '\' + SRegDisabledPlugins;
    if not Reg.OpenKey(Path, True) then
      Exit;

    // Сохраняем новые
    for X := 0 to FBanned.Count - 1 do
      Reg.WriteString(Path, Format(SRegPluginX, [X]), FBanned[X]);
  finally
    FreeAndNil(Reg);
  end;
end;

procedure TPluginManager.LoadSettings(const ARegPath: String);
var
  Reg: TRegIniFile;
  Path: String;
  X: Integer;
begin
  Reg := TRegIniFile.Create(ARegPath, KEY_READ);
  try
    FBanned.BeginUpdate;
    try
      FBanned.Clear;

      // Читаем
      Path := ARegPath + '\' + SRegDisabledPlugins;
      if not Reg.OpenKey(Path, True) then
        Exit;
      Reg.ReadSectionValues(Path, FBanned);

      // Убираем "Plugin5=" из строк
      for X := 0 to FBanned.Count - 1 do
        FBanned[X] := FBanned.ValueFromIndex[X];
    finally
      FBanned.EndUpdate;
    end;
  finally
    FreeAndNil(Reg);
  end;
end;&lt;/pre&gt;
Тут тоже ничего сложного: чёрный список плагинов хранится в &lt;code&gt;FBanned&lt;/code&gt;, куда плагин можно добавить или из которого удалить через функции &lt;code&gt;Ban&lt;/code&gt; и &lt;code&gt;Unban&lt;/code&gt; соответственно. Функция &lt;code&gt;CanLoad&lt;/code&gt; используется для определения статуса плагина: нужно его грузить или нет. Помимо собственно чёрного списка, функция дополнительно проверяет уже загруженные плагины, блокируя их повторную загрузку.&lt;br /&gt;
&lt;br /&gt;
Вспомогательные функции &lt;code&gt;Save-&lt;/code&gt; и &lt;code&gt;LoadSettings&lt;/code&gt; используются для сохранения и загрузки списка заблокированных плагинов в реестр, следуя &lt;a href="http://www.gunsmoker.ru/2011/09/blog-post_11.html" title="Сериализация - налоги"&gt;правилам хорошего тона&lt;/a&gt;. Таким образом, тестовый код может выглядеть так:
&lt;br /&gt;
&lt;pre class="brush:delphi"&gt;procedure TMainForm.Button3Click(Sender: TObject);
begin
  Plugins.Ban(Edit1.Text);
end;

procedure TMainForm.Button4Click(Sender: TObject);
begin
  Plugins.Unban(Edit1.Text);
end;

procedure TMainForm.FormCreate(Sender: TObject);
begin
  Plugins.LoadSettings('\SOFTWARE\MyCompany\MyProduct');
  ...
end;

procedure TMainForm.FormDestroy(Sender: TObject);
begin
  Plugins.SaveSettings('\SOFTWARE\MyCompany\MyProduct');
end;&lt;/pre&gt;
Результат работы функции &lt;code&gt;SaveSettings&lt;/code&gt; в реестре выглядит так:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://3.bp.blogspot.com/-nmnyjIwdsDk/TvaRePS2mbI/AAAAAAAADDM/UbP5v5PNfQ0/s1600/BannedPluginsInRegistry.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="133" src="http://3.bp.blogspot.com/-nmnyjIwdsDk/TvaRePS2mbI/AAAAAAAADDM/UbP5v5PNfQ0/s400/BannedPluginsInRegistry.png" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
Разумеется, ничто не мешает вам реализовать функции сохранения/загрузки как-то иначе, или вовсе вынести их за пределы менеджера плагинов: пусть конфигурацию сохраняет и восстанавливает код основной программы.&lt;br /&gt;
&lt;br /&gt;
Итак, мы написали менеджер плагинов в минимальном варианте. Вы можете расширять его функциями-обёртками для вашего удобства (например, ввести функцию, возвращающую загруженный плагин по имени файла), но это уже будет дополнительные удобства. Сейчас же самое время перейти к следующему шагу - собственно плагинам.&lt;br /&gt;
&lt;br /&gt;
Скачать исходный код к этому моменту можно &lt;a href="http://dl.dropbox.com/u/201788/Projects/Demos/PluginAPI_1.zip" title="Скачать PluginAPI_1.zip"&gt;тут&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;a name="n8"&gt;&lt;/a&gt;&lt;h1&gt;API&lt;/h1&gt;
Теперь, когда у нас готова основа архитектуры плагинов со стороны ядра, можно начать прорабатывать контракт плагинов: API. Начнём с их инициализации. Как уже говорилось, у DLL должно быть минимум две функции: инициализации и финализации, которые будут вызываться первыми и последними. Вот давайте их сейчас и напишем. Откройте вашу программу и сделайте File/New unit. Сохраните новый модуль в папке PluginAPI\Headers под именем PluginAPI.pas:
&lt;br /&gt;
&lt;pre class="brush:delphi"&gt;unit PluginAPI;

interface

type
  TInitPluginFunc = function(const ACore: IUnknown): IUnknown; safecall;
  TDonePluginFunc = procedure; safecall;

const
  SPluginInitFuncName = '87D4EDFB420343F2976EB3CF4DB7C224';
  SPluginDoneFuncName = SPluginInitFuncName + '_done';
  SPluginExt          = '.MyAppPlugin';

implementation

end.&lt;/pre&gt;
Здесь мы определяем прототипы двух функций (Init и Done). Обе они имеют соглашение вызова safecall, а функция инициализации к тому же принимает параметры от ядра и возвращает плагин (т.е. самого себя). Пока у нас нет функциональности, поэтому я вписал самый базовый интерфейс, который только может быть. Заметьте, что мы сейчас уже говорим про API, поэтому должны придерживаться упомянутых в начале статье правил - в частности, касаемо соглашения вызова и типов данных.&lt;br /&gt;
&lt;br /&gt;
Также в модуле определены имена, которые должны иметь функции инициализации и финализации. Имена выбраны как случайные - это сделано специально, чтобы в произвольной DLL таких имён точно бы не оказалось. Таким образом, если загружать произвольную DLL как плагин, то эта операция будет неудачной, потому что в произвольно взятой DLL (а не специально разработанном плагине) уж точно нет функции с именем '87D4EDFB420343F2976EB3CF4DB7C224'. Для получения такого имени я нажал в редакторе кода Delphi комбинацию Ctrl + Shift + G и получил:
&lt;br /&gt;
&lt;pre class="brush:delphi"&gt;['{87D4EDFB-4203-43F2-976E-B3CF4DB7C224}']&lt;/pre&gt;
После чего я удалил [], {} и -, оставив только
&lt;br /&gt;
&lt;pre class="brush:delphi"&gt;'87D4EDFB420343F2976EB3CF4DB7C224'&lt;/pre&gt;
И таким образом я получил имя, которое гарантировано уникально.&lt;br /&gt;
&lt;br /&gt;
Если вы будете использовать мой код в своих проектах, то вы должны для каждого своего проекта сгенерировать свою константу так же, как это сделал я. Это необходимо, чтобы плагины от одной программы нельзя было бы загрузить в другой.&lt;br /&gt;
&lt;br /&gt;
Наконец, тут определена константа для расширений плагинов. К примеру, загрузка плагинов (возвращаясь к примеру с менеджером плагинов) тогда будет выглядеть так:
&lt;br /&gt;
&lt;pre class="brush:delphi"&gt;Plugins.LoadFolder(ExtractFilePath(ParamStr(0))) + 'Plugins', SPluginExt);&lt;/pre&gt;
&lt;br /&gt;
Т.е. мы говорим, что плагин для нашей программы представляет собой переименованную в .MyAppPlugin-файл обыкновенную DLL, которая экспортирует функции 87D4EDFB420343F2976EB3CF4DB7C224 и 87D4EDFB420343F2976EB3CF4DB7C224_done с прототипами, указанными выше, и расположенная в подпапке Plugins основной программы.&lt;br /&gt;
&lt;br /&gt;
Возьмите на заметку - это слова для &lt;a title="Разработка системы плагинов в Delphi, часть 2: разработка API" href="http://www.gunsmoker.ru/2012/01/delphi-2-api.html#n3"&gt;вашей (будущей) документации к плагинам&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
В любом случае, давайте во-первых научим работать с этим наш менеджер плагинов, а потом напишем пустой плагин (ибо после внесения изменений в программу уже нельзя будет загрузить произвольную DLL):
&lt;br /&gt;
&lt;pre class="brush:delphi"&gt;unit PluginManager;

...

implementation

uses
  Registry,
  PluginAPI;

...

type
  TPlugin = class(TInterfacedObject, IPlugin)
  private
    ...
    FInit: TInitPluginFunc;
    FDone: TDonePluginFunc;
    FPlugin: IInterface;
  protected
    ...  
  end;
  
...

{ TPlugin }

constructor TPlugin.Create(const APluginManger: TPluginManager;
  const AFileName: String);
begin
  ...
  FDone := GetProcAddress(FHandle, SPluginDoneFuncName);
  FInit := GetProcAddress(FHandle, SPluginInitFuncName);
  Win32Check(Assigned(FInit));
  FPlugin := FInit(FManager);
end;

destructor TPlugin.Destroy;
begin
  FPlugin := nil;
  if Assigned(FDone) then
    FDone;
  ...
end;

...

end.&lt;/pre&gt;
Мне кажется, тут всё очевидно. Хочу только обратить внимание, что Init-функцию мы подразумеваем обязательной, а Done функцию - опциональной (т.е. она может отсутствовать, если она не нужна плагину).&lt;br /&gt;
&lt;br /&gt;
Теперь давайте создадим плагин. Для этого сделайте File/New/Delphi Projects/Dynamic-Link Library и сохраните куда-нибудь проект (вне папки PluginAPI). Затем сделайте File/Add to project и укажите файл PluginAPI.pas. После чего напишите минимальный код:
&lt;br /&gt;
&lt;pre class="brush:delphi"&gt;library Plugin1;

uses
  PluginAPI in 'PluginAPI\Headers\PluginAPI.pas';

{$R *.res}

function Init(const ACore: IInterface): IInterface; safecall;
begin
  Result := nil;
end;

procedure Done; safecall;
begin
  // ничего не делает
end;

exports
  Init name SPluginInitFuncName,
  Done name SPluginDoneFuncName;

end.&lt;/pre&gt;
Мы просто вставили пустые функции инициализации и финализации и экспортировали их.&lt;br /&gt;
&lt;br /&gt;
Пока мы ничего сделать не можем, потому что ещё не определили функциональность. Но в этот момент мы можем проверить, как плагин и приложение работают вместе. Попробуйте как и ранее загрузить произвольную DLL - это не сработает. А теперь попробуйте загрузить наш плагин - операция пройдёт успешно.&lt;br /&gt;
&lt;br /&gt;
&lt;a name="n8_1"&gt;&lt;/a&gt;&lt;h4&gt;Управление заголовочниками&lt;/h4&gt;
Начнём понемногу строить функциональность. Давайте начнём с самого простого - добавим возможность ядру узнать версию плагина, а плагину - версию ядра.&lt;br /&gt;
&lt;br /&gt;
Очевидно, что для этого нам нужно объявить два интерфейса: один для ядра и один для плагина. Или один интерфейс для обоих сразу. В любом случае, интерфейс будет иметь метод (и свойство) для получения версии.&lt;br /&gt;
&lt;br /&gt;
Тут сразу же я хотел бы рассмотреть вот какой момент. Предположим, сейчас мы напишем (слово "предположим" означает что это писать не надо):
&lt;br /&gt;
&lt;pre class="brush:delphi"&gt;unit PluginAPI;

interface

...

type
  IPluginInfo = interface
  ['{F96EFCD5-17F6-4C17-956C-219F23C51AF9}']
  // private
    function GetID: TGUID; safecall;
    function GetName: WideString; safecall;
    function GetVersion: WideString; safecall;
  // public
    property ID: TGUID read GetID;
    property Name: WideString read GetName;
    property Version: WideString read GetVersion;
  end;

  ICoreInfo = interface
  ['{525797CF-89EB-4226-8BFE-4E3DD2123E13}']
  // private
    function GetVersion: Integer; safecall;
  // public
    property Version: Integer read GetVersion;
  end;

implementation

end.&lt;/pre&gt;
Мы можем использовать эти объявления и включить заголовочный файл PluginAPI.pas в SDK (вместе с его описанием в документации). Хорошо, но что будут делать программисты, работающие на других языках? Один из вариантов - перевести все .pas файлы на C++. К примеру, наш файл PluginAPI.pas, будучи переведённым на C++, станет выглядеть как-то так:
&lt;br /&gt;
&lt;pre class="brush:cpp"&gt;namespace Pluginapi
{
//-- type declarations -------------------------------------------------------
typedef HRESULT __stdcall (*TInitPluginFunc)(IUnknown *ACore, IUnknown **Result);
typedef HRESULT __stdcall (*TDonePluginFunc)(void);

MIDL_INTERFACE("F96EFCD5-17F6-4C17-956C-219F23C51AF9")
IPluginInfo : public IUnknown
{
public:
 virtual HRESULT __stdcall GetID(GUID *Result) = 0;
 virtual HRESULT __stdcall GetName(BSTR *Result) = 0;
 virtual HRESULT __stdcall GetVersion(BSTR *Result) = 0;
};

MIDL_INTERFACE("525797CF-89EB-4226-8BFE-4E3DD2123E13")
ICoreInfo : public IUnknown
{
public:
 virtual HRESULT __stdcall GetVersion(int *Result) = 0;
};

//-- var, const, procedure ---------------------------------------------------
#define SPluginInitFuncName L"87D4EDFB420343F2976EB3CF4DB7C224"
#define SPluginDoneFuncName L"87D4EDFB420343F2976EB3CF4DB7C224_done"
#define SPluginExt L".MyAppPlugin"

} /* namespace Pluginapi */&lt;/pre&gt;
Примечание: чёрт, я мало что понимаю в C++, так что с переводом я мог наврать. Буду благодарен, если мне ткнут носом в правильный перевод.&lt;br /&gt;
&lt;br /&gt;
Как видите, текст дословно дублирует паскалевский код, но на другом языке (C++). Т.е. это просто копия.&lt;br /&gt;
&lt;br /&gt;
Суть такого телодвижения в том, что C++ - это де-факто стандарт. Если кто-то не программирует на Delphi, то он программирует либо на C++ (и тогда он может воспользоваться указанным заголовочником), либо программирует на другом языке. В последнем случае ему придётся переводить заголовочники (с Delphi или C++ на свой язык) самостоятельно.&lt;br /&gt;
&lt;br /&gt;
В любом случае, если вы в основном работаете на Delphi и слабо знакомы с другими языками, либо же вы хотите как-то автоматизировать работу (перевод заголовочников - не самая приятная работа, к тому же тут легко ошибиться: забыть перевести внесённые изменения), то вам захочется подыскать другой способ. Хотя, повторюсь, вы можете просто писать .pas файлы и переводить их на другие языки - это отлично будет работать.&lt;br /&gt;
&lt;br /&gt;
Но сейчас я предлагаю воспользоваться ещё одним заимствованием из COM: библиотекой типов.&lt;br /&gt;
&lt;br /&gt;
&lt;a name="n8_2"&gt;&lt;/a&gt;&lt;h4&gt;Библиотеки типов&lt;/h4&gt;
Я кратко уже описал смысл выше: библиотека типов - это универсальное хранилище информации о сборке. Она хранится в двоичном формате (TLB) либо как отдельный файл (.tlb), либо как ресурс внутри .dll. С библиотеками типов умеют работать почти все современные компиляторы под Windows. Если кто-то умеет работать с COM, то он умеет работать и с библиотекой типов.&lt;br /&gt;
&lt;br /&gt;
Самое важное для нас тут то, что библиотека типов может быть импортирована сторонней средой разработки, и при этом среда разработки сама (и автоматически) сгенерирует все необходимые исходные файлы. К примеру, если импортировать TLB в Delphi - Delphi сама создаст .pas заголовочник. Если импортировать TLB в Visual Studio C++ - она сама создаст .hpp файлы. Аналогичное справедливо и для других сред разработки (импортировать TLB можно даже в PHP!).&lt;br /&gt;
&lt;br /&gt;
Короче говоря, с точки зрения хранимых сведений, библиотека является более продвинутым аналогом заголовочных файлов - поскольку хранит в себе гораздо больше полезной информации, компактнее и быстрее (не нужно делать парсинг заголовочных файлов) и, главное, может использоваться в любой среде разработки и любом языке программирования, которые поддерживает COM, а не только в Delphi.&lt;br /&gt;
&lt;br /&gt;
Давайте создадим описание наших интерфейсов в виде библиотеки типов. Я буду описывать процесс, используя Delphi XE2, но это же должно быть верно и для более старых версий Delphi, включая Delphi 7 - правда, с некоторыми оговорками. Итак, выберите File/New/ActiveX/Type Library. Появится редактор библиотеки типов:&lt;br /&gt;
&lt;br /&gt;
&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-XwY0TYpb-wI/Tvcjh1HUKhI/AAAAAAAADDY/wefJcjX_bZ8/s1600/TLEditor7.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="311" src="http://1.bp.blogspot.com/-XwY0TYpb-wI/Tvcjh1HUKhI/AAAAAAAADDY/wefJcjX_bZ8/s400/TLEditor7.png" width="400" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Редактор в Delphi 7&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;br /&gt;
&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-e0swuU0SPfU/TvcjoWabifI/AAAAAAAADDk/Wt7CeelYhbY/s1600/TLEditorXE2.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="261" src="http://2.bp.blogspot.com/-e0swuU0SPfU/TvcjoWabifI/AAAAAAAADDk/Wt7CeelYhbY/s400/TLEditorXE2.png" width="400" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Редактор в Delphi XE2&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;br /&gt;
Сохраните этот файл с именем PluginAPI в папке PluginAPI\Headers. В Delphi XE2 это будет файл PluginAPI.ridl, а в Delphi 7 - PluginAPI.tlb. .ridl-файл - это текстовый формат, .tlb - двоичный.&lt;br /&gt;
&lt;br /&gt;
Тут есть один не совсем очевидный момент: в Delphi XE2 на экране вы видите 2 кнопки сохранения: во-первых, это стандартная кнопка в панели инструментов, а, во-вторых, это кнопка сохранения в редакторе библиотеки типов (она видна на снимке экрана выше - это самая правая кнопка в панели инструментов редактора библиотеки типов). Так вот, чтобы сохранить изменения в библиотеки типов, нужно нажать на стандартную кнопку сохранения, а не на кнопку сохранения в редакторе библиотеки типов. Это ещё более неочевидно, потому что изменения в библиотеки типов не приводят к немедленному включению стандартной кнопки сохранения Save all. Чтобы она включилась, нужно переключить вкладки - уйти с редактора библиотеки типов на, скажем, главный модуль программы или страницу Welcome и вернуться обратно. После этого кнопка сохранения будет активной.&lt;br /&gt;
&lt;br /&gt;
Если же вы щёлкните на кнопке сохранения в редакторе библиотеки типов, то это будет не сохранение, а экспорт в .tlb-файл (напомню, что Delphi XE2 хранит библиотеку типов в текстовом формате в .ridl файле). В Delphi 7 это немного попроще, потому что кнопка экспорта так и называется - экспорт. И выглядит она по другому. Соответственно, в Delphi 7 эта кнопка экспортирует двоичный файл в текстовый.&lt;br /&gt;
&lt;br /&gt;
В общем, Delphi 7 и Delphi XE2 ведут себя с точностью до наоборот. Нам это, впрочем никак не мешает, поскольку одно в другое преобразовать не проблема.&lt;br /&gt;
&lt;br /&gt;
В любом случае, при создании библиотеки типов ей автоматически был присвоен уникальный GUID и установлена версия 1.0. А после сохранения её в файл она стала иметь имя "PluginAPI". На первой вкладке вам ничего больше менять не нужно. Теперь переключитесь на вкладку "Uses" и сбросьте галочку с "Borland standard VCL type library", оставив только "OLE Automation". Это отвяжет библиотеку типов от Delphi (при этом среда покажет предупреждение, что теперь с этой библиотекой типов не будут работать некоторые Delphi-вые фишки - соглашайтесь).&lt;br /&gt;
&lt;br /&gt;
Теперь создадим в библиотеке типов наши интерфейсы &lt;code&gt;IPluginInfo&lt;/code&gt; и &lt;code&gt;ICoreInfo&lt;/code&gt;. Для этого щёлкните по первой (красной) кнопке в панели инструментов редактора библиотеки типов, либо же щёлкните по ней правой кнопкой мыши и выберите New\Interface. Это создаст новый интерфейс с автоматически сгенерированным GUID. Установите ему имя в "IPluginInfo", версию - в 1.0, а предка - в &lt;code&gt;IUnknown&lt;/code&gt;:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://3.bp.blogspot.com/-oSVdJBJdDic/Tvcol1kODoI/AAAAAAAADDw/LK6jcatdcgw/s1600/TLWithInterface.png" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="208" width="400" src="http://3.bp.blogspot.com/-oSVdJBJdDic/Tvcol1kODoI/AAAAAAAADDw/LK6jcatdcgw/s400/TLWithInterface.png" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
На вкладке "Flags" сбросьте опции "Dual" и "OLE Automation". Это отключит дополнительные возможности COM, объявив интерфейс в чистом виде.&lt;br /&gt;
&lt;br /&gt;
Далее щёлкните правой кнопкой по интерфейсу в дереве и выберите New\Property - это создаст два метода-акцесора Get и Set. Поскольку свойства у нас только для чтения, то удалите метод Set, оставив только Get (Get - первый, Set - второй; ещё их можно опознать по свойству Invoke kind: Put - это Set, ну а Get - это Get). Установите имя в ID, а тип данных - в GUID. Больше ничего менять не нужно.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://1.bp.blogspot.com/-bwgr493A5OM/TvcqJ1V4gYI/AAAAAAAADD8/oiC4ext7aOU/s1600/TLWithProperty.png" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="208" width="400" src="http://1.bp.blogspot.com/-bwgr493A5OM/TvcqJ1V4gYI/AAAAAAAADD8/oiC4ext7aOU/s400/TLWithProperty.png" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
Аналогично создайте свойства &lt;code&gt;Name&lt;/code&gt; и &lt;code&gt;Version&lt;/code&gt;, имеющие тип &lt;code&gt;BSTR&lt;/code&gt;, а также интерфейс &lt;code&gt;ICoreInfo&lt;/code&gt; со свойством Version, имеющим тип &lt;code&gt;long&lt;/code&gt;:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://2.bp.blogspot.com/-Y2gmkG9v7Pg/Tvcq_IUAAgI/AAAAAAAADEI/4bscyGmjRCY/s1600/EditRIDL.png" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="189" width="400" src="http://2.bp.blogspot.com/-Y2gmkG9v7Pg/Tvcq_IUAAgI/AAAAAAAADEI/4bscyGmjRCY/s400/EditRIDL.png" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
Готово.&lt;br /&gt;
&lt;br /&gt;
Тут надо сделать замечание по поводу типов данных. Типы данных указываются в "стиле C++", а не Delphi. К примеру, &lt;code&gt;GUID&lt;/code&gt; вместо &lt;code&gt;TGUID&lt;/code&gt;, &lt;code&gt;BSTR&lt;/code&gt; вместо &lt;code&gt;WideString&lt;/code&gt;, &lt;code&gt;int&lt;/code&gt; вместо &lt;code&gt;Integer&lt;/code&gt;, &lt;code&gt;long&lt;/code&gt; вместо &lt;code&gt;LongInt&lt;/code&gt;, * вместо ^ (указатель) и так далее. Список соответствия можно посмотреть в &lt;a href="http://www.delphikingdom.ru/asp/viewitem.asp?catalogid=169#02" title="Как получить справку по функциям Win API"&gt;этой статье&lt;/a&gt;. Кроме того, поскольку интерфейсы в C++ являются просто абстрактными классами, а экземпляры классов в C++ не являются по умолчанию указателями (как в Delphi), то с именами интерфейсов необходимо явно указывать указатель, например: 
&lt;pre class="brush:cpp"&gt;IUnknown*&lt;/pre&gt;
Когда вы закончите формировать библиотеку типов, сохраните её. Также сделайте экспорт в альтернативный формат (в Delphi 7 - в текстовый, в Delphi XE2 - в двоичный). В текстовом виде наша библиотека сейчас выглядит примерно так:
&lt;pre class="brush:cpp"&gt;[
  uuid(F156F71A-758A-40E2-A34E-50187B8ED7B9),
  version(1.0)

]
library PluginAPI
{

  importlib("stdole2.tlb");

  interface IPluginInfo;
  interface ICoreInfo;


  [
    uuid(631B96BB-1E7E-407D-83F1-5C673D2B5A15),
    version(1.0)
  ]
  interface IPluginInfo: IUnknown
  {
    [propget, id(0x00000065)]
    HRESULT _stdcall ID([out, retval] struct GUID* Value);
    [propget, id(0x00000066)]
    HRESULT _stdcall Name([out, retval] BSTR* Value);
    [propget, id(0x00000067)]
    HRESULT _stdcall Version([out, retval] BSTR* Value);
  };

  [
    uuid(3BAA3534-5422-42B9-BDEA-1CE1037295B3),
    version(1.0)
  ]
  interface ICoreInfo: IUnknown
  {
    [propget, id(0x00000065)]
    HRESULT _stdcall Version([out, retval] long* Value);
  };

};&lt;/pre&gt;
Файлы PluginAPI.tlb и PluginAPI.ridl (или .idl) - это то, что необходимо распространять в составе SDK. Имея на руках эти файлы любой сможет их импортировать в свой любимый язык программирования и получить заголовочные файлы. Вам не нужно ничего переводить самому.&lt;br /&gt;
&lt;br /&gt;
&lt;a name="n8_3"&gt;&lt;/a&gt;&lt;h4&gt;Генерация заголовочников для Delphi и C++ Builder&lt;/h4&gt;
К примеру, если говорить про Delphi и C++ Builder, то получение нужных файлов (да, это немного бессмысленно, т.к. эти файлы у нас в любом случае есть, но чисто ради примера) удобно делать через утилиту GenTLB.exe в папке bin - вы можете указать ей .ridl файл и на выходе получить комплект из .pas, .hpp и .cpp - все необходимые заголовочники для Delphi и C++. Есть утилита tlibimp.exe, которая работает по .tlb файлу. К примеру, если у вас на руках есть .tlb файл, то получить комплект файлов вы можете такой командой:
&lt;pre&gt;tlibimp.exe Source.tlb -C -P -I "-DD:\Output\" -Pt+&lt;/pre&gt;
Здесь Source.tlb - это исходный файл, а D:\Output\ - папка, куда нужно поместить результаты. Опции -C, -P и -I отвечают за генерацию .hpp/.cpp, .pas и .ridl файлов соответственно, а опция -Pt+ включает красивое "схлопывание" методов-акцессоров в свойства для Delphi.&lt;br /&gt;
&lt;br /&gt;
Кроме того, если вы создадите библиотеку типов в рамках проекта (т.е. вы выберите File\New\ActiveX\Type Library в то время, когда у вас открыт проект основной программы), то необходимые .pas файлы будут сгенерированы (и будут обновляться в дальнейшем) автоматически.&lt;br /&gt;
&lt;br /&gt;
Автогенерируемые файлы получают суффикс "_TLB". Например, библиотека "PluginAPI.tlb" создаст файл "PluginAPI_TLB.pas". Сам файл при этом выглядит примерно так:
&lt;pre class="brush:delphi"&gt;unit PluginAPI_TLB;

// ************************************************************************ //
// WARNING                                                                    
// -------                                                                    
// The types declared in this file were generated from data read from a       
// Type Library. If this type library is explicitly or indirectly (via        
// another type library referring to this type library) re-imported, or the   
// 'Refresh' command of the Type Library Editor activated while editing the   
// Type Library, the contents of this file will be regenerated and all        
// manual modifications will be lost.                                         
// ************************************************************************ //

// $Rev: 41960 $
// File generated on 2011.12.25 18:49:13 from Type Library described below.

// ************************************************************************  //
// Type Lib: c:\Users\Александр\Documents\RAD Studio\Projects\Plugins\Example2\PluginAPI\Headers\PluginAPI.tlb (1)
// LIBID: {F156F71A-758A-40E2-A34E-50187B8ED7B9}
// LCID: 0
// Helpfile: 
// HelpString: 
// DepndLst: 
//   (1) v2.0 stdole, (C:\Windows\SysWOW64\stdole2.tlb)
// Cmdline:
//   tlibimp  "c:\Users\Александр\Documents\RAD Studio\Projects\Plugins\Example2\PluginAPI\Headers\PluginAPI.tlb" -C -P -I "-Dc:\Users\Александр\Documents\RAD Studio\Projects\Plugins\Example2\PluginAPI\Headers\Test\" -Pt+
// ************************************************************************ //
{$TYPEDADDRESS OFF} // Unit must be compiled without type-checked pointers. 
{$WARN SYMBOL_PLATFORM OFF}
{$WRITEABLECONST ON}
{$VARPROPSETTER ON}
{$ALIGN 4}

interface

uses Windows, ActiveX, Classes, Graphics, StdVCL, Variants;
  

// *********************************************************************//
// GUIDS declared in the TypeLibrary. Following prefixes are used:        
//   Type Libraries     : LIBID_xxxx                                      
//   CoClasses          : CLASS_xxxx                                      
//   DISPInterfaces     : DIID_xxxx                                       
//   Non-DISP interfaces: IID_xxxx                                        
// *********************************************************************//
const
  // TypeLibrary Major and minor versions
  PluginAPIMajorVersion = 1;
  PluginAPIMinorVersion = 0;

  LIBID_PluginAPI: TGUID = '{F156F71A-758A-40E2-A34E-50187B8ED7B9}';

  IID_IPluginInfo: TGUID = '{631B96BB-1E7E-407D-83F1-5C673D2B5A15}';
  IID_ICoreInfo: TGUID = '{3BAA3534-5422-42B9-BDEA-1CE1037295B3}';
type

// *********************************************************************//
// Forward declaration of types defined in TypeLibrary                    
// *********************************************************************//
  IPluginInfo = interface;
  ICoreInfo = interface;

// *********************************************************************//
// Interface: IPluginInfo
// Flags:     (0)
// GUID:      {631B96BB-1E7E-407D-83F1-5C673D2B5A15}
// *********************************************************************//
  IPluginInfo = interface(IUnknown)
    ['{631B96BB-1E7E-407D-83F1-5C673D2B5A15}']
    function Get_ID: TGUID; safecall;
    function Get_Name: WideString; safecall;
    function Get_Version: WideString; safecall;
    property ID: TGUID read Get_ID;
    property Name: WideString read Get_Name;
    property Version: WideString read Get_Version;
  end;

// *********************************************************************//
// Interface: ICoreInfo 
// Flags:     (0)
// GUID:      {3BAA3534-5422-42B9-BDEA-1CE1037295B3}
// *********************************************************************//
  ICoreInfo = interface(IUnknown)
    ['{3BAA3534-5422-42B9-BDEA-1CE1037295B3}']
    function Get_Version: Integer; safecall;
    property Version: Integer read Get_Version;
  end;

implementation

uses ComObj;

end.&lt;/pre&gt;
Тут довольно много воды (к примеру, можно подчистить списки uses и удалить к чертям комментарии), но сам код практически дословно повторяет наш исходный вариант. Что и требовалось получить.&lt;br /&gt;
&lt;br /&gt;
&lt;a name="n8_4"&gt;&lt;/a&gt;&lt;h4&gt;Генерация заголовочников для Visual Studio C++&lt;/h4&gt;
Итак, это был вариант для Delphi и C++ Builder. Чуть более интересно - Visual Studio C++. Во-первых, вам понадобится сама студия. У Microsoft есть обрезанный бесплатный вариант Visual Studio Express - его будет достаточно. Взять его можно &lt;a href="http://www.microsoft.com/express" title="Microsoft Express"&gt;тут&lt;/a&gt;. Только убедитесь, что берёте именно C++, а не Phone, Basic или C#. Обратите внимание, что по умолчанию при загрузке сайт предлагает установить Trial версии Professional - убедитесь, что вы щёлкните по второму варианту (установка версии Express). Скачается небольшой web-установщик - запускайте его и ставьте. Далее ничего необычного нет. &lt;br /&gt;
&lt;br /&gt;
Я думаю, что импортировать TLB можно и в самой студии, но т.к. я в ней не силён, то мне было проще работать через утилиты командной строки. Если вы знаете другой способ генерации заголовочников по TBL в Visual Studio - ради бога, используйте его. Я же установил Platform SDK (сейчас он называется Windows SDK), который взял &lt;a href="http://www.microsoft.com/download/en/details.aspx?id=8279" title="Microsoft Windows SDK for Windows 7 and .NET Framework 4"&gt;тут&lt;/a&gt;. Это тоже веб-установщик и устанавливается он как обычно. После этого в SDK нас интересуют утилиты. Во-первых, там есть утилитка OleView.exe (C:\Program Files\Microsoft SDKs\Windows\v7.1\Bin\OleView.Exe). Она может открыть .tlb-файл и показать, что там внутри - т.е. это просмотрщик (примечание: если вдруг он почему-то не работает, то скорее всего надо зарегистрировать библиотеку IViewers.Dll в той же папке). Плюс, он умеет экспортировать библиотеку типов в .idl, .h и .c. Вот этим и нужно воспользоваться. Открываете свою библиотеку типов и экспортируете её в заголовочники.&lt;br /&gt;
&lt;br /&gt;
По правде сказать, в .idl он у меня сконвертировал, а вот в .h и .c - нет. Мелькнувшее окошко консоли ругнулось на то, что он не может найти cl.exe - это препроцессор C, который идёт в комплекте с Visual Studio. Я не уверен, почему это происходит у меня, заставить его работать я так и не смог (ладно, особо упорно я и не старался). Поэтому я пошёл другим путём: я создал .bat файл с таким содержанием:
&lt;pre&gt;@echo off
call "c:\Program Files\Microsoft Visual Studio 10.0\VC\bin\vcvars32.bat"
"c:\Program Files\Microsoft SDKs\Windows\v7.1\Bin\Midl.Exe" "c:\Test\PluginAPI.IDL" /out "C:\Test\Out" /h "PluginAPI.hpp" &lt;/pre&gt;
(где IDL файл - это текстовое представление библиотеки типов; я не пробовал с файлами, генерируемыми Delphi; этот IDL файл - тот, что мне выдал OLEView).&lt;br /&gt;
&lt;br /&gt;
MIDL компилятор по IDL файлу сгенерировал мне PluginAPI.hpp и PluginAPI_i.c - это и есть нужные нам файлы.&lt;br /&gt;
&lt;br /&gt;
&lt;a name="n8_5"&gt;&lt;/a&gt;&lt;h4&gt;Выводы по ведению заголовочных файлов&lt;/h4&gt;
Итак, подводя черту, у вас есть три способа ведения заголовочников:
&lt;ol&gt;
&lt;li&gt;Просто писать .pas файл и при необходимости &lt;b&gt;вручную&lt;/b&gt; перевести его на другие языки.&lt;/li&gt;
&lt;li&gt;Создать библиотеку типов и редактировать её в редакторе Delphi (вообще говоря, для этого можно использовать любой редактор TLB, а не только Delphi). В конце &lt;b&gt;автоматически&lt;/b&gt; получить .tlb и комплект файлов для Delphi, C++ Builder, Visual Studio C++, а также текстовое описание (idl/ridl).&lt;/li&gt;
&lt;li&gt;Создать idl или ridl файл. Писать текст - это быстрее и удобнее, чем использовать редактор, но нужно знать &lt;a title="Microsoft Interface Definition Language" href="http://msdn.microsoft.com/en-us/library/windows/desktop/aa367091(v=vs.85).aspx"&gt;язык описания интерфейсов&lt;/a&gt; (IDL). В конце скомпилировать файл в .tlb и получить комплект сопроводительных файлов.&lt;/li&gt;
&lt;/ol&gt;
Как делать - на ваш выбор. В принципе, начать можно с п1, а затем перейти к п2 или п3, но надо понимать, что при этом вы будете дублировать свою работу (сначала вы пишете .pas, а потом &lt;b&gt;ровно это же&lt;/b&gt; воспроизводите в библиотеке типов). Так что изначально начав работу с библиотекой типов вы избежите повторения. Тем не менее, именно по этой причине (поскольку одно тождественно второму), здесь и далее я буду говорить только про Паскаль и приводить код только Delphi модулей - подразумевая, что для других языков вы либо сделаете перевод, либо составите .tlb-файл. Но я больше не буду заострять на этом внимание. Итак, всюду ниже вы делаете как написано, но в итоге собираете файлы для других языков. Надеюсь, это понятно.&lt;br /&gt;
&lt;br /&gt;
Давайте я ещё просуммирую действия, которые вам необходимо делать при работе с TLB:
&lt;ul&gt;
&lt;li&gt;Помещайте .tlb файлы в PluginsAPI\Headers. .tlb файл - это главный и основной файл. Его нужно распространять обязательно. Все остальные пункты ниже - необязательные. Их можно не делать. Но если вы их сделаете, то это будет дополнительное удобство.&lt;/li&gt;
&lt;li&gt;В папку PluginsAPI\Headers положите файлы .idl или .ridl.&lt;/li&gt;
&lt;li&gt;Получите комплект заголовочников для Delphi, C++ Builder и Visual Studio C++, как указано выше (tlibimp для Delphi и C++ Builder и oleview/midl для Visual Studio).&lt;/li&gt;
&lt;li&gt;В папке PluginAPI\Headers создайте подпапки Delphi, Builder и VC.&lt;/li&gt;
&lt;li&gt;В папку Delphi положите XYZ_TLB.pas&lt;/li&gt;
&lt;li&gt;В папку Builder положите XYZ_TLB.h и XYZ_TLB.cpp&lt;/li&gt;
&lt;li&gt;В папку VC положите XYZ.hpp и XYZ_i.c&lt;/li&gt;
&lt;/ul&gt;
Папка PluginAPI\Headers целиком (с подпапками) - это то, что вам нужно распространять среди разработчиков ваших плагинов.&lt;br /&gt;
&lt;br /&gt;
Далее мы к этому возвращаться не будем.&lt;br /&gt;
&lt;br /&gt;
Что касается файла PluginAPI.pas с двумя типами данных и двумя константами - вы можете описать его словами в документации, плюс приложить вот это определение на C:
&lt;pre class="brush:cpp"&gt;//-- type declarations -------------------------------------------------------
typedef HRESULT __stdcall (*TInitPluginFunc)(IUnknown *ACore, IUnknown **Result);
typedef HRESULT __stdcall (*TDonePluginFunc)(void);

//-- var, const, procedure ---------------------------------------------------
#define SPluginInitFuncName L"87D4EDFB420343F2976EB3CF4DB7C224"
#define SPluginDoneFuncName L"87D4EDFB420343F2976EB3CF4DB7C224_done"
#define SPluginExt L".MyAppPlugin"&lt;/pre&gt;
Этого будет достаточно, чтобы разработчики плагинов поняли бы, о чём идёт речь, и что нужно делать. Конечно, разработчикам на других языках (не C++ и не Delphi) придётся перевести эти строки на свой язык самостоятельно, но перевод пары строк - это полный пустяк. Основную массу заголовочников транслировать не нужно - для этого есть импорт библиотеки типов.&lt;br /&gt;
&lt;br /&gt;
Далее, в документации нужно особо уточнить, что ваши плагины и ядро - это не COM-объекты. Просто самое типичное использование библиотеки типов - это COM. Т.е. если кто-то видит библиотеку типов, он может автоматически потянуться её импортировать и вызывать &lt;code&gt;CoCreateInstance&lt;/code&gt; для создания объектов. Только это не будет работать в вашем случае. Потому что у вас нет никаких COM-объектов, и уж тем более их никто глобально не регистрировал, чтобы их можно было получить через &lt;code&gt;CoCreateInstance&lt;/code&gt;. Поэтому нужно чётко указать: это не COM, библиотека типов служит лишь для авто-генерации заголовочных файлов для вашего любимого языка программирования, а чтобы сделать плагин - нужно создать DLL с двумя вот такими (см. C код выше) функциями. Первая из которых работает с интерфейсами. Какими интерфейсами? А вот они как раз описаны в библиотеке типов. &lt;br /&gt;
&lt;br /&gt;
Как-то так.&lt;br /&gt;
&lt;br /&gt;
&lt;a name="n8_6"&gt;&lt;/a&gt;&lt;h4&gt;Начала реализации функциональности&lt;/h4&gt;
Итак, вернёмся же к нашим баранам. Я напомню, что мы решили дать возможность плагину и ядру узнать информацию друг о друге и для этого мы ввели такие интерфейсы:
&lt;pre class="brush:delphi"&gt;type
  IPluginInfo = interface
  ['{F96EFCD5-17F6-4C17-956C-219F23C51AF9}']
  // private
    function GetID: TGUID; safecall;
    function GetName: WideString; safecall;
    function GetVersion: WideString; safecall;
  // public
    property ID: TGUID read GetID;
    property Name: WideString read GetName;
    property Version: WideString read GetVersion;
  end;

  ICoreInfo = interface
  ['{525797CF-89EB-4226-8BFE-4E3DD2123E13}']
  // private
    function GetVersion: Integer; safecall;
  // public
    property Version: Integer read GetVersion;
  end;&lt;/pre&gt;
Я написал их от балды - вы можете использовать любую другую структуру, даже один интерфейс вместо двух (скажем, &lt;code&gt;IVersionInfo&lt;/code&gt;). Тут ничего жёстко не определено, работает ваша фантазия - как захотите, так и сделаете. Я бы только рекомендовал предусмотреть идентификацию плагинов по уникальному ID. В качестве такого хорошо подходит GUID. А вот имя файла - не достаточно (плагин можно переименовать).&lt;br /&gt;
&lt;br /&gt;
В любом случае, теперь их надо использовать в коде. Начнём с плагина. Возьмём пустой плагин из предыдущего примера и дополним его новым кодом следующим образом:
&lt;pre class="brush:delphi"&gt;library Plugin1;

uses
  SysUtils,
  Classes,
  PluginAPI in 'PluginAPI\Headers\PluginAPI.pas',
  PluginAPI_TLB in 'PluginAPI\Headers\Delphi\PluginAPI_TLB.pas';

{$R *.res}

const
  SPluginID: TGUID = '{C147E26F-3933-4AE2-A4A8-2A55BC9DABD2}';
  SPluginName      = 'Example plugin';
  SPluginVersion   = '1.0.0.0';

type
  TPlugin = class(TInterfacedObject, IUnknown, IPluginInfo)
  private
    FCore: ICoreInfo;
  protected
    function Get_ID: TGUID; safecall;
    function Get_Name: WideString; safecall;
    function Get_Version: WideString; safecall;
  public
    constructor Create(const ACore: IInterface);
  end;

{ TPlugin }

constructor TPlugin.Create(const ACore: IInterface);
begin
  inherited Create;
  if not Supports(ACore, ICoreInfo, FCore) then
    Assert(False);
  Assert(FCore.Version &gt;= 1);
end;

function TPlugin.Get_ID: TGUID;
begin
  Result := SPluginID;
end;

function TPlugin.Get_Name: WideString;
begin
  Result := SPluginName;
end;

function TPlugin.Get_Version: WideString;
begin
  Result := SPluginVersion;
end;

// _________________________________________________________________

function Init(const ACore: IInterface): IInterface; safecall;
begin
  Result := TPlugin.Create(ACore);
end;

procedure Done; safecall;
begin

end;

exports
  Init name SPluginInitFuncName,
  Done name SPluginDoneFuncName;

end.&lt;/pre&gt;
Итак, плагин реализует интерфейс &lt;code&gt;IPluginInfo&lt;/code&gt; - для этого нам понадобился объект. Реализация методов в данном случае тривиальна - мы просто возвращаем константы. Конструктор объекта получает из переданного нам интерфейса интерфейс &lt;code&gt;ICoreInfo&lt;/code&gt; и сохраняет его в поле &lt;code&gt;FCore&lt;/code&gt;. Это не нужно в этом примере (нигде ниже &lt;code&gt;FCore&lt;/code&gt; не используется), но может быть полезно в общем случае. После получения интерфейса, мы проверяем, что версия ядра - 1 или выше. Конечно, это всегда будет так. Глупая проверка :)&lt;br /&gt;
&lt;br /&gt;
Кстати, я выбрал строку для версии плагина, подразумевал, что единственное, что с ней можно сделать - показать в интерфейсе пользователя. Поэтому строка. С другой стороны, версия ядра не показывается (плагином) в UI, но нужна ему для проверки, есть ли у ядра интересующие плагин возможности. Вот почему это число - для простой проверки. К примеру, если в будущем мы расширим наше ядро новыми возможностями, то плагин при загрузке может проверить, что версия ядра не ниже двух - и отказаться грузиться в старой версии программы (потому что там нет новых возможностей, которые нужны плагину). Ладно, в любом случае сейчас это никак не используется, потому что у нас всего одна версия. Но это пример на будущее.&lt;br /&gt;
&lt;br /&gt;
Теперь, что касается ядра.
&lt;pre class="brush:delphi"&gt;unit PluginManager;

interface

...

  IPlugin = interface
  // protected
    ...
    function GetID: TGUID;
    function GetName: String;
    function GetVersion: String;
  // public
    ...
    property ID: TGUID read GetID;
    property Name: String read GetName;
    property Version: String read GetVersion;
  end;

  IPluginManager = interface
  // protected
    ...
  // public
    ...
    procedure SetVersion(const AVersion: Integer);
  end;

...

implementation

uses
  Registry,
  PluginAPI,
  PluginAPI_TLB;

...

type
  TPluginManager = class(TInterfacedObject, IUnknown, IPluginManager, ICoreInfo)
  private
    ...
    FVersion: Integer;
  protected
    ...
    procedure SetVersion(const AVersion: Integer);
    function Get_Version: Integer; safecall;
  public
    ...
  end;

  TPlugin = class(TInterfacedObject, IPlugin)
  private
    ...
    FInfoRetrieved: Boolean;
    FID: TGUID;
    FName: String;
    FVersion: String;
    procedure GetInfo;
  protected
    ...
    function GetID: TGUID;
    function GetName: String;
    function GetVersion: String;
  public
    ...
  end;

{ TPluginManager }

constructor TPluginManager.Create;
begin
  ...
  SetVersion(1);
end;

...

procedure TPluginManager.SetVersion(const AVersion: Integer);
begin
  FVersion := AVersion;
end;

function TPluginManager.Get_Version: Integer;
begin
  Result := FVersion;
end;

...

procedure TPlugin.GetInfo;
var
  Info: IPluginInfo;
begin
  if FInfoRetrieved then
    Exit;
  if Supports(FPlugin, IPluginInfo, Info) then
  begin
    FID := Info.ID;
    FName := Info.Name;
    FVersion := Info.Version;
    FInfoRetrieved := True;
  end;
end;

function TPlugin.GetID: TGUID;
begin
  GetInfo;
  Result := FID;
end;

function TPlugin.GetName: String;
begin
  GetInfo;
  Result := FName;
end;

function TPlugin.GetVersion: String;
begin
  GetInfo;
  Result := FVersion;
end;&lt;/pre&gt;
Изменения в основной программе (показываем имя плагина и его версию вместо имени файла):
&lt;pre class="brush:delphi"&gt;procedure TMainForm.UpdatePluginsList;
var
  X: Integer;
begin
  ListBox1.Items.BeginUpdate;
  try
    ListBox1.Items.Clear;
    for X := 0 to Plugins.Count - 1 do
      ListBox1.Items.Add(IntToStr(Plugins[X].Index) + ': ' + Plugins[X].Name + ' (' + Plugins[X].Version + ')');
  finally
    ListBox1.Items.EndUpdate;
  end;
end;&lt;/pre&gt;
Теперь пояснения по коду. Здесь я практически продублировал интерфейс &lt;code&gt;IPluginInfo&lt;/code&gt; из заголовочника в &lt;code&gt;IPlugin&lt;/code&gt;. Плюсы: вы посмотрите, как красиво выглядит код в основной программе! Можно подумать мы с компонентами работаем, а не с плагинами. Минусы: нам пришлось писать кучу кода-обёртки, который мне сейчас придётся объяснять.&lt;br /&gt;
&lt;br /&gt;
Подобное дублирование делать необязательно и в следующий раз я покажу другой пример.&lt;br /&gt;
&lt;br /&gt;
Сам код для информации о плагине состоит из трёх однотипных методов (&lt;code&gt;GetID&lt;/code&gt;, &lt;code&gt;GetName&lt;/code&gt;, &lt;code&gt;GetVersion&lt;/code&gt;). По сути, здесь показан пример получения информации по запросу: мы не вызываем интерфейс &lt;code&gt;IPluginInfo&lt;/code&gt; до тех пор, пока кто-то не обратится к одному из свойств плагина (&lt;code&gt;ID&lt;/code&gt;, &lt;code&gt;Name&lt;/code&gt;, &lt;code&gt;Version&lt;/code&gt;). Это можно было бы делать и по другому: запросить информацию в конструкторе &lt;code&gt;TPlugin&lt;/code&gt;. Кроме того, этот код показывает пример кэширования информации: когда мы получили информацию, мы сохранили её в полях объекта (кэше). Если она потребуется нам повторно - нам не придётся дёргать плагин снова, чтобы её получить. Этот подход отлично подходит для статической информации (которая не меняется), но мало пригоден для динамической.&lt;br /&gt;
&lt;br /&gt;
Что касается интерфейса &lt;code&gt;ICoreInfo&lt;/code&gt;, то его реализует менеджер плагинов. Тут есть такой вопрос: вообще-то менеджер плагинов не в курсе, что за версия у ядра. Это знает ядро, а не менеджер плагинов. Поэтому мы по умолчанию ставим версию 1. А ядру даёт метод &lt;code&gt;SetVersion&lt;/code&gt;, с помощью которого оно сможет установить (при необходимости) номер версии 2, 3 и так далее. Обратите внимание, что хотя методы &lt;code&gt;SetVersion&lt;/code&gt; и &lt;code&gt;Get_Version&lt;/code&gt; и выглядят парой (и, по сути, это методы-акцессоры Get и Set для свойства "Версия"), они имеют существенное отличие: метод Get предназначен для плагинов, а метод Set - для ядра (это видно по соглашению вызова: &lt;code&gt;safecall&lt;/code&gt; и &lt;code&gt;register&lt;/code&gt;). Метод &lt;code&gt;SetVersion&lt;/code&gt; плагин вызвать не может!&lt;br /&gt;
&lt;br /&gt;
В связи с тем, что у нас появился интерфейс ядра, который мы передаём плагину, у нас возникла небольшая проблема: как вы помните из сказанного выше, плагины не выгружаются программой явно, они выгружаются при удалении менеджера плагинов. А наш менеджер плагинов удаляется тогда, когда все ссылки на него будут уничтожены. Последняя из которых (ссылка) обнуляется в finalization модуля менеджера. А проблема тут вот в чём: мы ведь передали интерфейс менеджера плагинам. Это означает, что обнуление ссылки в секции finalization не будет последним: ведь ссылку на менеджер держат плагины, которые... не выгружаются потому, что менеджер ещё жив. Вот и получается круговая ссылка: менеджер ссылается на плагины, а плагины - на менеджер. И никто из них выгружаться не хочет, потому что есть активные ссылки. Вот поэтому нам нужно внести изменение: в секции finalization нужно явно скомандовать менеджеру плагинов выгрузить все плагины. Сделать это можно просто финализировав массив &lt;code&gt;FItems&lt;/code&gt; - всё остальное за нас сделает механизм учёта ссылок. Финализация массива уничтожит каждый его элемент, каждый элемент - это плагин. Т.е. каждый плагин будет выгружен. Выгрузка плагина отпустит ссылку на менеджер плагинов, после чего обнуление ссылки на менеджер плагинов в секции finalization уничтожит его - и все будут довольны. Код я тут выписывать не буду - там всего несколько строк.&lt;br /&gt;
&lt;br /&gt;
На этом пора бы подвести черту основному введению в разработку системы плагинов на Delphi. Код к этому моменту можно скачать &lt;a title="Скачать PluginAPI_2.zip" href="http://dl.dropbox.com/u/201788/Projects/Demos/PluginAPI_2.zip"&gt;тут&lt;/a&gt;. Нужно отметить, что весь этот код не имеет специфики, он универсален. А потому он может использоваться как база для ваших программ. В следующей статье мы начнём делать уже конкретные вещи и реально полезные плагины. Поэтому код в следующей части не может быть использован в вашем коде, а служит только лишь примером.&lt;br /&gt;
&lt;br /&gt;
Примерный список вопросов для рассмотрения в &lt;a title="Разработка системы плагинов в Delphi, часть 2: разработка API" href="http://www.gunsmoker.ru/2012/01/delphi-2-api.html"&gt;следующий раз&lt;/a&gt;: обработка ошибок, функциональность в плагине, обращение из плагина к внутренностям ядра, общение плагинов между собой, показ плагинами UI, обратные вызовы (инициация действий плагином, а не ядром).&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1702873441071265539-7625352937635378534?l=www.gunsmoker.ru' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/GunSmokersBlog?a=l4tVapc8CnA:zw_hmA-7hm4:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GunSmokersBlog?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GunSmokersBlog?a=l4tVapc8CnA:zw_hmA-7hm4:4cEx4HpKnUU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GunSmokersBlog?i=l4tVapc8CnA:zw_hmA-7hm4:4cEx4HpKnUU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GunSmokersBlog?a=l4tVapc8CnA:zw_hmA-7hm4:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GunSmokersBlog?i=l4tVapc8CnA:zw_hmA-7hm4:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GunSmokersBlog?a=l4tVapc8CnA:zw_hmA-7hm4:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GunSmokersBlog?i=l4tVapc8CnA:zw_hmA-7hm4:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GunSmokersBlog?a=l4tVapc8CnA:zw_hmA-7hm4:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GunSmokersBlog?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GunSmokersBlog?a=l4tVapc8CnA:zw_hmA-7hm4:-BTjWOF_DHI"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GunSmokersBlog?i=l4tVapc8CnA:zw_hmA-7hm4:-BTjWOF_DHI" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/GunSmokersBlog/~4/l4tVapc8CnA" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.gunsmoker.ru/feeds/7625352937635378534/comments/default" title="Комментарии к сообщению" /><link rel="replies" type="text/html" href="http://www.gunsmoker.ru/2011/12/delphi.html#comment-form" title="Комментарии: 27" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1702873441071265539/posts/default/7625352937635378534?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1702873441071265539/posts/default/7625352937635378534?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/GunSmokersBlog/~3/l4tVapc8CnA/delphi.html" title="Разработка системы плагинов в Delphi" /><author><name>Александр Алексеев</name><uri>https://profiles.google.com/113168002104297556003</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-aUMttZQBsuI/AAAAAAAAAAI/AAAAAAAAC3Y/QuZ7K9t_WzE/s512-c/photo.jpg" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://3.bp.blogspot.com/-nmnyjIwdsDk/TvaRePS2mbI/AAAAAAAADDM/UbP5v5PNfQ0/s72-c/BannedPluginsInRegistry.png" height="72" width="72" /><thr:total>27</thr:total><feedburner:origLink>http://www.gunsmoker.ru/2011/12/delphi.html</feedburner:origLink></entry><entry gd:etag="W/&quot;A0UESHo9fCp7ImA9WhRXFEo.&quot;"><id>tag:blogger.com,1999:blog-1702873441071265539.post-3432051332772689820</id><published>2011-12-20T02:47:00.001+04:00</published><updated>2011-12-21T18:53:29.464+04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-12-21T18:53:29.464+04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Delphi" /><category scheme="http://www.blogger.com/atom/ns#" term="EurekaLog" /><title>Публичная бета EurekaLog 7</title><content type="html">Получи бесплатную лицензию на EurekaLog 7! ;)&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;
Компания EurekaLab s.a.s. объявляет о начале публичного бета-тестирования EurekaLog 7.&lt;br /&gt;
&lt;br /&gt;
Вы можете скачать бету из секции &lt;a href="http://www.eurekalog.com/downloads_delphi.php" title="EurekaLog Delphi Downloads"&gt;"Delphi downloads"&lt;/a&gt; на веб-сайте.&lt;br /&gt;
&lt;br /&gt;
Вам также предлагается пройти краткий опрос. Опрос займёт не более 10 минут и здорово поможет EurekaLab в будущем. Вы можете пройти опрос &lt;a href="https://www.kwiksurveys.com/?s=NIMIOF_2f187147" title="EurekaLog Feedback Survey"&gt;тут&lt;/a&gt; (на английском).&lt;br /&gt;
&lt;br /&gt;
&lt;h1&gt;Как попробовать EurekaLog 7&lt;/h1&gt;
EurekaLog 7 может быть установлена вместе с EurekaLog 6. Вы можете переключаться между версиями EurekaLog, используя новый пункт меню "Manage" в меню Пуск/Программы. &lt;br /&gt;
&lt;br /&gt;
Для более подробной информации - см. &lt;a href="http://eurekalog.blogspot.com/2011/12/using-eurekalog-6-and-eurekalog-7-on.html" title="Using EurekaLog 6 and EurekaLog 7 on the same machine and/or in the same IDE version"&gt;эту статью&lt;/a&gt; (на английском).&lt;br /&gt;
&lt;br /&gt;
&lt;h1&gt;Отличия от EurekaLog 6&lt;/h1&gt;
EurekaLog 7 достаточно похожа на EurekaLog 6 и работать с ней нужно по тем же принципам. Единственное важное отличие для быстрого старта - в EurekaLog 7 после её включения для проекта, вам также нужно &lt;a href="http://www.eurekalog.com/help/eurekalog/selecting_application_type.php" title="Selecting application type"&gt;указать тип вашего приложения&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
Пожалуйста, пройдитесь по &lt;a href="http://www.eurekalog.com/help/eurekalog/tutorials.php" title="EurekaLog 7 Quick Start Tutorials"&gt;Quick Start Tutorials&lt;/a&gt; для быстрого ознакомления с изменениями.&lt;br /&gt;
&lt;br /&gt;
&lt;h1&gt;EurekaLog 7 в существующих (старых) проектах под EurekaLog 6&lt;/h1&gt;
EurekaLog 7 меняет некоторое поведение и вносит новые возможности. С целью сохранения обратной совместимости был введён так называемый "режим совместимости с EurekaLog 6" ("EurekaLog 6 Compatibility Mode"). Вы можете включить или выключить его на &lt;a href="http://www.eurekalog.com/help/eurekalog/general_page.php" title="General tab in options"&gt;вкладке "General"&lt;/a&gt; в меню опций EurekaLog для проекта.&lt;br /&gt;
&lt;br /&gt;
Идея режима в том, что если вы его включаете, то EurekaLog будет вести себя так же, как EurekaLog 6. Так что вам не нужно будет менять свой старый код. Если вы его выключаете, то EurekaLog 7 использует новое поведение. Когда вы открываете старый проект - он открывается (импортируется) в режиме "EurekaLog 6 Compatibility Mode". Если же вы создаёте новый проект - он будет создан с выключенным режимом "EurekaLog 6 Compatibility Mode".&lt;br /&gt;
&lt;br /&gt;
&lt;h1&gt;Где искать информацию&lt;/h1&gt;
В EurekaLog 7 была значительно расширена справочная система, но она всё ещё находится в процессе написания. Справку можно открыть локально (через меню Пуск или кнопку F1 в любом окне EurekaLog) или &lt;a href="http://www.eurekalog.com/help/eurekalog/" title="EurekaLog 7 Documentation"&gt;онлайн&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
Документация на код ещё пишется, но вы можете изучить заголовочные файлы в вашей папке &lt;code&gt;\Source&lt;/code&gt; (по умолчанию это &lt;code&gt;C:\Program Files (x86)\EurekaLab\EurekaLog 7\Source\&lt;/code&gt;). Полный исходный код будет доступен только в редакции Enterprise, но частичный исходных код доступен в любой редакции (включая Trial)!&lt;br /&gt;
&lt;br /&gt;
Я также могу писать заметки в блоге, освещающие изменения или новые возможности в EurekaLog 7.&lt;br /&gt;
&lt;br /&gt;
Список изменений в EurekaLog 7 можно посмотреть &lt;a href="http://www.eurekalog.com/help/eurekalog/whats_new.php" title="What's new in EurekaLog 7"&gt;тут&lt;/a&gt; (на английском; список ещё пишется).&lt;br /&gt;
&lt;br /&gt;
&lt;h1&gt;Участвуй в бета-тесте - выиграй лицензию EurekaLog!&lt;/h1&gt;
Для тех, кто примет участие в бета-тестировании EurekaLog 7 приготовлен подарок. Самые активные тестеры получат &lt;b&gt;бесплатную&lt;/b&gt; лицензию EurekaLog 7 Enterprise! (и, возможно, лицензии других редакций - в качестве поощрительного приза) Это предложение доступно всем - как существующим клиентам EurekaLab s.a.s., так и новым пользователям EurekaLog.&lt;br /&gt;
&lt;br /&gt;
Так что же вам нужно для этого делать? Просто установите EurekaLog 7 и начните её использовать. Сообщайте о каждом замеченном баге. Или просто поделитесь мнением или внесите предложение. Или пришлите код или иной вклад в проект. Чем больше полезной информации вы пришлёте - тем больше ваши шансы на выигрыш.&lt;br /&gt;
&lt;br /&gt;
Как отправлять информацию? Вы можете либо использовать встроенные средства отправки, либо воспользоваться &lt;a href="http://support.eurekalog.com/index.php?/Tickets/Submit" title="EurekaLog Support"&gt;этой веб-формой&lt;/a&gt;. Убедитесь, что вы отправляете все свои сообщения с одного e-mail адреса. В случае вашей победы ваша лицензия на EurekaLog будет зарегистрирована на этот адрес. Пожалуйста, не используйте для этого форумы и не шлите сообщения мне лично (пожалуйста, не надо мне слать любые сообщения лично, даже если вы не собираетесь участвовать в конкурсе). Короче говоря, если вы получили авто-ответ от нашей системы тех-поддержки о регистрации вашего сообщения - оно будет участвовать в этом конкурсе. Отзывы можно писать на русском языке - с хорошей долей вероятности разбирать их буду я лично. Но даже если нет - остальные члены команды могут использовать Google Translate.&lt;br /&gt;
&lt;br /&gt;
Победители будут объявлены после выхода стабильной (финальной) версии EurekaLog 7. Число победителей не ограничено и будет установлено в зависимости от числа бета-тестеров. Победители будут определены как внёсшие наиболее полезный вклад в развитие проекта. "Полезность" будет определяться разработчиками EurekaLog на их усмотрение. К примеру, один подробный отчёт будет ценится больше нескольких односложных отчётов (однако это не значит, что вам нужно добавлять в отчёты воду). Правила и условия этого предложения могут быть изменены в будущем.&lt;br /&gt;
&lt;br /&gt;
Если вы не выиграете лицензию - вы всё ещё сможете обновить вашу текущую лицензию на EurekaLog с 50% скидкой. Либо даже получить её бесплатно, если вы купили лицензию на EurekaLog 6 за 90 дней до выхода EurekaLog 7.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Примечание:&lt;/b&gt; дата выхода EurekaLog 7 не установлена. Она выйдет тогда, когда будет готова. Вы можете ускорить это событие, потестировав бету.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Примечание:&lt;/b&gt; сейчас доступна только редакция Trial (которую можно &lt;a title="EurekaLog Downloads" href="http://www.eurekalog.com/downloads_delphi.php"&gt;скачать с веб-сайта&lt;/a&gt;). Другие редакции (вроде Professional и Enterprise) станут доступны только после выхода стабильной версии EurekaLog 7.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;&lt;span style="color: red;"&gt;Предупреждение:&lt;/span&gt;&lt;/b&gt; это первая бета EurekaLog. Крайне не рекомендуется использовать её в релиз-версиях ваших реальных проектов.&lt;br /&gt;
&lt;br /&gt;
P.S. Ах, да, российская специфика: вы можете присылать отзывы, даже если вы пират.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1702873441071265539-3432051332772689820?l=www.gunsmoker.ru' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/GunSmokersBlog?a=DYrPtweB5Xw:Vb2d8i16gZc:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GunSmokersBlog?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GunSmokersBlog?a=DYrPtweB5Xw:Vb2d8i16gZc:4cEx4HpKnUU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GunSmokersBlog?i=DYrPtweB5Xw:Vb2d8i16gZc:4cEx4HpKnUU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GunSmokersBlog?a=DYrPtweB5Xw:Vb2d8i16gZc:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GunSmokersBlog?i=DYrPtweB5Xw:Vb2d8i16gZc:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GunSmokersBlog?a=DYrPtweB5Xw:Vb2d8i16gZc:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GunSmokersBlog?i=DYrPtweB5Xw:Vb2d8i16gZc:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GunSmokersBlog?a=DYrPtweB5Xw:Vb2d8i16gZc:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GunSmokersBlog?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GunSmokersBlog?a=DYrPtweB5Xw:Vb2d8i16gZc:-BTjWOF_DHI"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GunSmokersBlog?i=DYrPtweB5Xw:Vb2d8i16gZc:-BTjWOF_DHI" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/GunSmokersBlog/~4/DYrPtweB5Xw" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.gunsmoker.ru/feeds/3432051332772689820/comments/default" title="Комментарии к сообщению" /><link rel="replies" type="text/html" href="http://www.gunsmoker.ru/2011/12/eurekalog-7.html#comment-form" title="Комментарии: 3" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1702873441071265539/posts/default/3432051332772689820?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1702873441071265539/posts/default/3432051332772689820?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/GunSmokersBlog/~3/DYrPtweB5Xw/eurekalog-7.html" title="Публичная бета EurekaLog 7" /><author><name>Александр Алексеев</name><uri>https://profiles.google.com/113168002104297556003</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-aUMttZQBsuI/AAAAAAAAAAI/AAAAAAAAC3Y/QuZ7K9t_WzE/s512-c/photo.jpg" /></author><thr:total>3</thr:total><feedburner:origLink>http://www.gunsmoker.ru/2011/12/eurekalog-7.html</feedburner:origLink></entry><entry gd:etag="W/&quot;AkECSHgzeCp7ImA9WhRXGUU.&quot;"><id>tag:blogger.com,1999:blog-1702873441071265539.post-2632220399538197041</id><published>2011-11-12T23:57:00.001+04:00</published><updated>2011-12-27T16:24:29.680+04:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-12-27T16:24:29.680+04:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Delphi" /><category scheme="http://www.blogger.com/atom/ns#" term="Статья" /><title>Сериализация - потоки данных</title><content type="html">Это второй пост в &lt;a title="Сериализация - оглавление" href="http://www.gunsmoker.ru/2011/08/blog-post.html"&gt;серии постов про сериализацию&lt;/a&gt;. В этой части мы рассмотрим потоки данных.&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;
&lt;h1&gt;Оглавление&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#general" title="Общие сведения"&gt;Общие сведения&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#work" title="Общие принципы работы с потоками данных"&gt;Общие принципы работы&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#errors" title="Обработка ошибок"&gt;Обработка ошибок&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#rules" title="Правила использования"&gt;Правила использования&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#examples" title="Примеры"&gt;Практика&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#misc" title="Особенности"&gt;Особенности&lt;/a&gt;:
&lt;ul&gt;
&lt;li&gt;&lt;a href="#thandlestream" title="THandleStream"&gt;&lt;code&gt;THandleStream&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#tfilestream" title="TFileStream"&gt;&lt;code&gt;TFileStream&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#tmemorystream" title="TMemoryStream"&gt;&lt;code&gt;TMemoryStream&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#tresourcestream" title="TResourceStream"&gt;&lt;code&gt;TResourceStream&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#tbytesstream" title="TBytesStream"&gt;&lt;code&gt;TBytesStream&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#tstringstream" title="TStringStream"&gt;&lt;code&gt;TStringStream&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#tstreamadapter" title="TStreamAdapter"&gt;&lt;code&gt;TStreamAdapter&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#tolestream" title="TOleStream"&gt;&lt;code&gt;TOleStream&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#other" title="Прочие потоки"&gt;Прочие потоки&lt;/a&gt;&lt;/li&gt;
&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#create" title="Создание своих классов-потоков"&gt;Создание своих классов-потоков&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#conclusion" title="Преимущества и недостатки потоков данных"&gt;Преимущества и недостатки потоков данных&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;a name="general"&gt;&lt;/a&gt;&lt;h1&gt;Общие сведения&lt;/h1&gt;
Если вы вспомните &lt;a title="Сериализация - общие сведения о файлах" href="http://www.gunsmoker.ru/2011/09/blog-post_10.html"&gt;определение файла&lt;/a&gt;, то оно звучало так: "файл - это устройство с последовательным доступом, к которому можно обратиться по имени". При этом физические файлы на диске являются лишь частным случаем. Отнимите из этого определения часть "имеющие имя" - и вы получите определение &lt;b&gt;потока данных&lt;/b&gt;. Поток данных представляет собой "что-то" с последовательным доступом. Вы можете читать из него данные или записывать. Некоторые потоки поддерживают позиционирование (вроде физического файла на диске), другие - нет (вроде сетевого сокета).&lt;br /&gt;
&lt;br /&gt;
Потоки данных являются де-факто стандартом для обмена данными в Delphi. Всюду в своих процедурах, где вам необходимо принимать или отправлять нетипизированный набор данных, используйте потоки данных. Многие механизмы Delphi по умолчанию умеют работать именно с потоками данных, предоставляя методы вроде &lt;code&gt;LoadFromStream&lt;/code&gt; и &lt;code&gt;SaveToStream&lt;/code&gt; (и иногда предоставляя к ним обёртки-переходники вроде &lt;code&gt;LoadFromFile&lt;/code&gt; и &lt;code&gt;SaveToFile&lt;/code&gt;).&lt;br /&gt;
&lt;br /&gt;
Примечание: поток данных (&lt;b&gt;stream&lt;/b&gt;) не следует путать с потоком кода (&lt;b&gt;thread&lt;/b&gt;), который также иногда называют &lt;i&gt;нитью&lt;/i&gt;. Они не имеют между собой ничего общего, кроме слова "поток" в названии. Если какой-то текст говорит про "потоки", не уточняя кода или данных, то значение термина должно быть ясно из контекста. Эти понятия не пересекаются, так что здесь не должно быть никаких проблем с пониманием. Замечу, что подобная путаница возможна только в русском языке, где два разных понятия переводятся одинаково. В английском языке для них используются разные слова (stream и thread).&lt;br /&gt;
&lt;br /&gt;
В Delphi все потоки данных реализованы как объекты (экземпляры) классов, наследуемых от &lt;a href="http://docwiki.embarcadero.com/VCL/en/Classes.TStream" title="Classes.TStream"&gt;&lt;code&gt;TStream&lt;/code&gt;&lt;/a&gt;. &lt;code&gt;TStream&lt;/code&gt; является абстрактным базовым классом, который поддерживает операции чтения, записи и позиционирования, но сам при этом не умеет делать ничего. Конкретная работа реализуется его классами-наследниками.&lt;br /&gt; 
&lt;br /&gt;
В Delphi есть широкий набор классов, предназначенный для работы с чем угодно: от файла на диске до блока памяти. Каждый такой класс-наследник реализует базовые методы &lt;code&gt;TStream&lt;/code&gt; по-своему. К примеру, при чтении из потока данных: наследник для работы с файлами вызовет функцию чтения данных с диска, а наследник для работы с блоком памяти использует процедуру &lt;code&gt;Move&lt;/code&gt; для копирования данных.&lt;br /&gt;
&lt;br /&gt;
Вот (неполный) список классов-наследников &lt;code&gt;TStream&lt;/code&gt; (примеры):
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://docwiki.embarcadero.com/VCL/en/Classes.TFileStream" title="Classes.TFileStream"&gt;&lt;code&gt;TFileStream&lt;/code&gt;&lt;/a&gt; (для работы с файлами)&lt;/li&gt;
&lt;li&gt;&lt;a href="http://docwiki.embarcadero.com/VCL/en/Classes.TResourceStream" title="Classes.TResourceStream"&gt;&lt;code&gt;TResourceStream&lt;/code&gt;&lt;/a&gt; (для работы с ресурсами программы)&lt;/li&gt;
&lt;li&gt;&lt;a href="http://docwiki.embarcadero.com/VCL/en/Classes.TStringStream" title="Classes.TStringStream"&gt;&lt;code&gt;TStringStream&lt;/code&gt;&lt;/a&gt; (для работы со строками)&lt;/li&gt;
&lt;li&gt;&lt;a href="http://docwiki.embarcadero.com/VCL/en/Classes.TMemoryStream" title="Classes.TMemoryStream"&gt;&lt;code&gt;TMemoryStream&lt;/code&gt;&lt;/a&gt; (для работы с буфером в памяти)&lt;/li&gt;
&lt;li&gt;&lt;a href="http://docwiki.embarcadero.com/VCL/en/DBTables.TBlobStream" title="DBTables.TBlobStream"&gt;&lt;code&gt;TBlobStream&lt;/code&gt;&lt;/a&gt; (для работы с BLOB полями)&lt;/li&gt;
&lt;li&gt;&lt;a href="http://docwiki.embarcadero.com/VCL/en/ScktComp.TWinSocketStream" title="ScktComp.TWinSocketStream"&gt;&lt;code&gt;TWinSocketStream&lt;/code&gt;&lt;/a&gt; (для работы с сетевым сокетом)&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
По сравнению с &lt;a title="Сериализация - файлы в стиле Pascal" href="http://www.gunsmoker.ru/2011/10/pascal.html"&gt;прошлой темой&lt;/a&gt;, где было всего три вполне конкретных файловых типа, это может быть немного непонятно: зачем нужен какой-то абстрактный класс и классы-наследники? Очень просто: пусть вы хотите уметь загружать растровые изображения (bitmap). Но ведь рисунок может лежать не только в файле, он может быть и в ресурсах программы и в памяти. Не писать же три разных метода, которые делают одно и то же? Вот поэтому и нужен абстрактный класс. Он объявляет общий контракт, которому обязуются следовать все его наследники. Поэтому вы можете спокойно написать (один раз) код, который грузит рисунок из &lt;code&gt;TStream&lt;/code&gt;. А уж вызывающий подставит вам &lt;code&gt;TFileStream&lt;/code&gt; для загрузки рисунка из файла, &lt;code&gt;TResourceStream&lt;/code&gt; для загрузки из ресурса и &lt;code&gt;TMemoryStream&lt;/code&gt; для загрузки из памяти. В определённом смысле все эти классы-наследники представляют собой простые переходники (от общей спецификации, определённой &lt;code&gt;TStream&lt;/code&gt;, до конкретного метода доступа: файл, ресурс, память, сеть и так далее; и наоборот). В общем, &lt;a title="Delphi полиморфизм" href="http://blogs.embarcadero.com/vsevolodleonov/2010/12/24/delphipoly-2/"&gt;полиморфизм в действии&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
Здесь и ниже я буду говорить в основном про физические файлы и примеры приводить в расчёте на &lt;code&gt;TFileStream&lt;/code&gt; - как наиболее типичный случай. Просто имейте в виду, что всё сказанное будет применимо и к другим потокам данных.&lt;br /&gt;
&lt;br /&gt;
Кроме того, потоки обеспечивают поддержку для загрузки/сохранения компонентов и форм. Именно благодаря этому механизму работает загрузка .dfm файлов в run-time. Этот механизм работает автоматически. Впрочем, вы можете использовать его и в своих целях. Но на это мы посмотрим в другой раз, потому что он тесно связан с RTTI. Это будет темой одной из следующих статей.&lt;br /&gt;
&lt;br /&gt;
&lt;a name="work"&gt;&lt;/a&gt;&lt;h1&gt;Общие принципы работы с потоками данных&lt;/h1&gt;
&lt;b&gt;Чтение/запись&lt;/b&gt;&lt;br /&gt;
Класс &lt;code&gt;TStream&lt;/code&gt; имеет два метода для чтения данных и два метода для записи данных. Работают они обычным образом: поток читает (или пишет) указанный блок данных и сдвигает текущую позицию на размер блока данных, так что следующая операция начинается там, где закончилась предыдущая. Ничего сложного.&lt;br /&gt;
&lt;br /&gt;
Итак, для чтения класс &lt;code&gt;TStream&lt;/code&gt; предлагает методы &lt;a href="http://docwiki.embarcadero.com/VCL/en/Classes.TStream.Read" title="Classes.TStream.Read"&gt;&lt;code&gt;Read&lt;/code&gt;&lt;/a&gt; и &lt;a href="http://docwiki.embarcadero.com/VCL/en/Classes.TStream.ReadBuffer" title="Classes.TStream.ReadBuffer"&gt;&lt;code&gt;ReadBuffer&lt;/code&gt;&lt;/a&gt;, а для записи - методы &lt;a href="http://docwiki.embarcadero.com/VCL/en/Classes.TStream.Write" title="Classes.TStream.Write"&gt;&lt;code&gt;Write&lt;/code&gt;&lt;/a&gt; и &lt;a href="http://docwiki.embarcadero.com/VCL/en/Classes.TStream.WriteBuffer" title="Classes.TStream.WriteBuffer"&gt;&lt;code&gt;WriteBuffer&lt;/code&gt;&lt;/a&gt;. Эти методы используются одинаково: первым параметром указывается буфер (это нетипизированный параметр), а вторым параметром - его размер в байтах, например:
&lt;pre class="brush:delphi"&gt;procedure LoadFromStream(AStream: TStream);
var
  I: array of Integer; 
begin
  // Чтение содержимого AStream в массив I
  SetLength(I, AStream.Size div SizeOf(I[0]));
  AStream.ReadBuffer(I[0], AStream.Size);

  // &lt;- тут какая-то работа с I
end;

procedure SaveToStream(AStream: TStream);
var
  I: array of Integer; 
begin
  // &lt;- тут какая-то работа с I

  // Запись массива I в поток данных AStream 
  AStream.WriteBuffer(I[0], Length(I) * SizeOf(I[0]));
end;&lt;/pre&gt;
Разница между методами с "Buffer" и без него заключается в том, что методы с суффиксом "Buffer" гарантируют выполнение операции до конца. Если же это невозможно (к примеру, в файле 12 байт, а вы командуете прочитать 24 байта), то будет возбуждено исключение (см. также ниже раздел про обработку ошибок).&lt;br /&gt;
&lt;br /&gt;
А вот методы без суффикса "Buffer" допускают частичное выполнение операции. Они никогда не возбуждают ошибку, а вместо этого возвращают, сколько реально байт было прочитано или записано. Иногда, это может быть и 0 байт. К примеру, если в файле 12 байт, а вы вызываете &lt;code&gt;Read&lt;/code&gt;, указывая 24 байта, то метод &lt;code&gt;Read&lt;/code&gt; прочитает 12 байт и вернёт вам число 12 (это метод-функция). Ещё пример: если у вас поток связан с сетевым сокетом и вы вызываете &lt;code&gt;Read&lt;/code&gt;, но пока никаких данных ещё не пришло: метод завершится тут же, возвращая 0.&lt;br /&gt;
&lt;br /&gt;
Замечу, что некоторые потоки данных могут быть только для чтения или только для записи. К примеру, однонаправленный pipe может не допускать чтения, а ресурсы, очевидно, не разрешают запись. В таких случаях попытка вызова запрещённого метода приведёт к ошибке.&lt;br /&gt;
&lt;br /&gt;
Помимо общих методов чтения/записи, потоки поддерживают специализированные методы для реализации чтения/записи компонентов - но, как я уже сказал, это тема для следующего раза.&lt;br /&gt;
&lt;br /&gt;
Разумеется, некоторые из наследников &lt;code&gt;TStream&lt;/code&gt; могут вводить свои специальные методы для чтения/записи. Мы посмотрим на некоторые примеры ниже.&lt;br /&gt;
&lt;br /&gt;
Кроме чтения и записи &lt;code&gt;TStream&lt;/code&gt; имеет и некоторые другие методы.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Позиционирование&lt;/b&gt;&lt;br /&gt;
Во-первых, это методы позиционирования. Вы можете вызвать &lt;a href="http://docwiki.embarcadero.com/VCL/en/Classes.TStream.Size" title="Classes.TStream.Size"&gt;свойство &lt;code&gt;Size&lt;/code&gt;&lt;/a&gt;, которое вернёт вам размер потока данных в байтах (кроме того, вы можете устанавливать свойство &lt;code&gt;Size&lt;/code&gt;, чтобы менять размер потока, но это поддерживается далеко не всеми видами потоков данных). &lt;a href="http://docwiki.embarcadero.com/VCL/en/Classes.TStream.Position" title="Classes.TStream.Position"&gt;Свойство &lt;code&gt;Position&lt;/code&gt;&lt;/a&gt; указывает текущую позицию в потоке данных, где 0 соответствует началу, а значение, равное &lt;code&gt;Size&lt;/code&gt;, - концу. Вы можете читать свойство &lt;code&gt;Position&lt;/code&gt;, чтобы узнать текущую позицию, и записывать значение в &lt;code&gt;Position&lt;/code&gt;, чтобы изменить текущую позицию (позиционирование поддерживается не всеми видами потоков). К примеру:
&lt;pre class="brush:delphi"&gt;var
  Stream: TStream;
  SavedPos: Int64;
begin
  ...
  SavedPos := Stream.Position; // Где мы сейчас?
  Stream.Position := 0; // Перешли в начало потока данных
  // что-то сделали...
  Stream.Position := SavedPos; // Вернулись, где были до этого
  ...
  Stream.Position := Stream.Size; // Перешли в конец потока
  ...
end;&lt;/pre&gt;
Кроме абсолютного позиционирования через свойство &lt;code&gt;Position&lt;/code&gt;, потоки данных поддерживают относительное позиционирование в стиле файловых Seek-процедур: &lt;a href="http://docwiki.embarcadero.com/VCL/en/Classes.TStream.Seek" title="Classes.TStream.Seek"&gt;метод &lt;code&gt;Seek&lt;/code&gt;&lt;/a&gt;. Этот метод имеет два параметра: позицию и точку отсчёта. Причём последнее может иметь значения &lt;code&gt;soBeginning&lt;/code&gt; (отсчёт от начала потока, аналог абсолютного позиционирования), &lt;code&gt;soCurrent&lt;/code&gt; (отсчёт от текущей позиции, относительное смещение) и &lt;code&gt;soEnd&lt;/code&gt; (отсчёт от конца потока). Для &lt;code&gt;soCurrent&lt;/code&gt; позиция может быть и положительным числом (смещение в сторону конца потока) и отрицательным (смещение в сторону начала), для &lt;code&gt;soBeginning&lt;/code&gt; смещение может быть только положительным (или нулём), а для &lt;code&gt;soEnd&lt;/code&gt; - только отрицательным (или нулём). Есть также устаревшие константы вида &lt;code&gt;soFromBeginning&lt;/code&gt;, &lt;code&gt;soFromCurrent&lt;/code&gt;, &lt;code&gt;soFromEnd&lt;/code&gt; - не используйте их в новом коде. При этом метод &lt;code&gt;Seek&lt;/code&gt; возвращает предыдущее значение текущей позиции (до позиционирования). К примеру:
&lt;pre class="brush:delphi"&gt;var
  Stream: TStream;
  SavedPos: Int64;
begin
  ...
  SavedPos := Stream.Seek(0, soBeginning); // Перемещаемся в начало потока, одновременно сохраняя (старую) текущую позицию
  // что-то сделали...
  Stream.Seek(SavedPos, soBeginning); // Вернулись, где были до этого
  ...
  Stream.Seek(0, soEnd); // Перешли в конец потока
  ...
end;&lt;/pre&gt;
&lt;br /&gt;
&lt;b&gt;Копирование&lt;/b&gt;&lt;br /&gt;
У &lt;code&gt;TStream&lt;/code&gt; есть ещё один метод: &lt;a href="http://docwiki.embarcadero.com/VCL/en/Classes.TStream.CopyFrom" title="Classes.TStream.CopyFrom"&gt;&lt;code&gt;CopyFrom&lt;/code&gt;&lt;/a&gt;. Этот метод копирует указанное количество данных (в байтах) из указанного массива. Копирование производится с текущей позиции. Метод работает аналогично методу записи &lt;code&gt;WriteBuffer&lt;/code&gt;, сдвигая текущую позицию на указанное количество байт и возбуждая исключение при ошибках. Использование &lt;code&gt;CopyFrom&lt;/code&gt; позволяет избежать создания буфера, чтения в него данных из исходного потока, запись буфера в выходной поток и удаления буфера - все эти действия выполняются автоматически внутри метода &lt;code&gt;CopyFrom&lt;/code&gt;. К примеру, вот простейший пример копирования файлов (см. ниже описание &lt;code&gt;TFileStream&lt;/code&gt;):
&lt;pre class="brush:delphi"&gt;procedure CopyFile(const ASourceFileName, ATargetFileName: String);
var
  Source: TFileStream;
  Dest: TFileStream;
begin
  // Открыли исходный файл на чтение
  Source := TFileStream.Create(ASourceFileName, fmOpenRead or fmShareDenyWrite);
  try
    // Создали целевой файл (в режиме записи)
    Dest := TFileStream.Create(ATargetFileName, fmCreate or fmShareExclusive);
    try
      // Копируем данные из потока в поток
      Dest.CopyFrom(Source, Source.Size);
    finally
      FreeAndNil(Dest);
    end; 
  finally
    FreeAndNil(Source);
  end;
end;&lt;/pre&gt;
У &lt;code&gt;CopyFrom&lt;/code&gt; есть специальный случай: если последний параметр (размер) равен 0, то &lt;code&gt;CopyFrom&lt;/code&gt; скопирует весь поток целиком - начиная с начала потока (вне зависимости от текущей позиции) и до конца. Так что если число байт для записи у вас не фиксировано, а как-то вычисляется, то вставьте перед вызовом &lt;code&gt;CopyFrom&lt;/code&gt; проверку на 0: иначе вы скопируете поток целиком вместо 0 байт.&lt;br /&gt;
&lt;br /&gt;
&lt;a name="errors"&gt;&lt;/a&gt;&lt;h1&gt;Обработка ошибок&lt;/h1&gt;
Здесь нет ничего сложного, потоки данных используют стандартную обработку исключений. Вы можете просто писать код, не производя проверок - он по умолчанию будет иметь обработку ошибок. Конечно же, вам нужно использовать &lt;code&gt;try..finally&lt;/code&gt; для корректной обработки утечек ресурсов программы:
&lt;pre class="brush:delphi"&gt;// Случай 1: код в рамках одной процедуры.

begin
  Stream := ???.Create(...);
  try
    // Работа с потоком
  finally
    FreeAndNil(Stream);
  end;
end;

...

// Случай 2: использование в конструкторах и деструкторах объектов.

constructor TSomeClass.Create;
begin
  inherited Create;
  FStream := ???.Create(...);
end;

procedure TSomeClass.DoSomething;
begin
  // Работа с потоком
end;

destructor TSomeClass.Destroy;
begin
  FreeAndNil(FStream);
  inherited Destroy;
end;

...

SomeObj := TSomeClass.Create;
try
  SomeObj.DoSomething;
finally
  FreeAndNil(SomeObj);
end;&lt;/pre&gt;
Во втором примере &lt;code&gt;try..finally&lt;/code&gt; не используется при работе с потоком, потому что он вынесен во внешний код (вызывающий).&lt;br /&gt;
&lt;br /&gt;
Все исключения, возбуждаемые самим потоком, наследуются от &lt;a href="http://docwiki.embarcadero.com/VCL/en/Classes.EStreamError" title="Classes.EStreamError"&gt;&lt;code&gt;EStreamError&lt;/code&gt;&lt;/a&gt;. Наиболее типичными случаями являются ошибки &lt;a href="http://docwiki.embarcadero.com/VCL/en/Classes.EFileStreamError" title="Classes.EFileStreamError"&gt;&lt;code&gt;EFileStreamError&lt;/code&gt;&lt;/a&gt;, &lt;a href="http://docwiki.embarcadero.com/VCL/en/Classes.EFCreateError" title="Classes.EFCreateError"&gt;&lt;code&gt;EFCreateError&lt;/code&gt;&lt;/a&gt;, &lt;a href="http://docwiki.embarcadero.com/VCL/en/Classes.EFOpenError" title="Classes.EFOpenError"&gt;&lt;code&gt;EFOpenError&lt;/code&gt;&lt;/a&gt;, &lt;a href="http://docwiki.embarcadero.com/VCL/en/Classes.EReadError" title="Classes.EReadError"&gt;&lt;code&gt;EReadError&lt;/code&gt;&lt;/a&gt; и &lt;a href="http://docwiki.embarcadero.com/VCL/en/Classes.EWriteError" title="Classes.EWriteError"&gt;&lt;code&gt;EWriteError&lt;/code&gt;&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
Так что код обработки исключений потоков данных (если он вам нужен) может выглядеть как-то так:
&lt;pre class="brush:delphi"&gt;try
  Stream := ???.Create(...);
  try
    // Работа с TStream
  finally
    FreeAndNil(Stream);
  end;
except
  on E: EStreamError do
    Application.MessageBox(PChar(Format('При работе с потоком произошла ошибка %s', [E.Message])), 'Ошибка', MB_OK or MB_ICONERROR);
end;&lt;/pre&gt;
&lt;br /&gt;
Всё вышеуказанное - это типичное поведение для работы с исключениями. Тут не должно быть ничего нового и нетипичного.&lt;br /&gt;
&lt;br /&gt;
Так что единственное "ага!" в обработке ошибок - разница между &lt;code&gt;Read&lt;/code&gt; и &lt;code&gt;ReadBuffer&lt;/code&gt; и между &lt;code&gt;Write&lt;/code&gt; и &lt;code&gt;WriteBuffer&lt;/code&gt;. Запомните, что если вы вызываете методы без суффикса "Buffer", то вы должны либо явно проверять результат их вызова (прямо как с кодом на кодах ошибок), либо иметь в виду, что данные могут прочитаться/записаться не полностью.&lt;br /&gt;
&lt;br /&gt;
&lt;a name="rules"&gt;&lt;/a&gt;&lt;h1&gt;Правила использования&lt;/h1&gt;
Хотелось бы сказать о "правилах использования" - а, скорее, о наиболее типичных ошибках новичков при работе с потоками данных.&lt;br /&gt;
&lt;br /&gt;
&lt;ol&gt;
&lt;li&gt;(Почти) всегда используйте &lt;code&gt;ReadBuffer&lt;/code&gt; и &lt;code&gt;WriteBuffer&lt;/code&gt;. Используйте &lt;code&gt;Read&lt;/code&gt; и &lt;code&gt;Write&lt;/code&gt; только если вам не важно выполнение операции до конца (к примеру, вы не знаете, сколько данных нужно прочитать). Иными словами, &lt;code&gt;ReadBuffer&lt;/code&gt; и &lt;code&gt;WriteBuffer&lt;/code&gt; должны быть вашим вариантом по умолчанию, а не наоборот.&lt;br /&gt;
&lt;br /&gt;&amp;nbsp;&lt;/li&gt;
&lt;li&gt;Если вы в своей процедуре принимаете или отправляете какие-то данные - используйте &lt;code&gt;TStream&lt;/code&gt;. Не используйте для этого нетипизированные параметры, указатели или конкретные экземпляры &lt;code&gt;TStream&lt;/code&gt;. Т.е. вместо:
&lt;pre class="brush:delphi"&gt;procedure A(AData: Pointer; ADataSize: Cardinal);
procedure B(const AData; ADataSize: Cardinal);
procedure C(AData: TFileStream);&lt;/pre&gt;
должно быть:
&lt;pre class="brush:delphi"&gt;procedure A(AData: TStream);
procedure B(AData: TStream);
procedure C(AData: TStream);&lt;/pre&gt;
&lt;br /&gt;&amp;nbsp;&lt;/li&gt;
&lt;li&gt;Частая ошибка новичков: они забывают про позиционирование. К примеру:
&lt;pre class="brush:delphi"&gt;Stream.WriteBuffer(MyData, SizeOf(MyData)); // записали данные для объекта Obj
Obj.LoadFromStream(Stream); // ошибка: загрузка объекта "из конца потока"&lt;/pre&gt;
Должно быть:
&lt;pre class="brush:delphi"&gt;Stream.WriteBuffer(MyData, SizeOf(MyData)); // записали данные для объекта Obj
Stream.Position := 0; // сместились к началу потока
Obj.LoadFromStream(Stream); // правильно: загрузка того, что мы только что записали&lt;/pre&gt;
&lt;br /&gt;&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;TStream&lt;/code&gt; является абстрактным классом. Это значит, что в вашем коде не должно быть строк вида &lt;code&gt;TStream.Create&lt;/code&gt;. Вы всегда должны использовать конкретного наследника - например, &lt;code&gt;TFileStream&lt;/code&gt; или &lt;code&gt;TResourceStream&lt;/code&gt;. Если вам нужен "просто поток", то вы можете использовать &lt;code&gt;TMemoryStream&lt;/code&gt; - это создаст поток в памяти программы. Если при этом вы хотите использовать большой объём данных, то вы можете использовать &lt;code&gt;TFileStream&lt;/code&gt; для временного файла. &lt;a title="Это только временно" href="http://www.transl-gunsmoker.ru/2009/02/blog-post_11.html"&gt;См. также&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;&amp;nbsp;&lt;/li&gt;
&lt;li&gt;Не забывайте, что потоки работают с большими данными, поэтому всё позиционирование осуществляется на базе &lt;code&gt;Int64&lt;/code&gt; (8 байт/64 бита), а не &lt;code&gt;Integer&lt;/code&gt; (4 байта/32 бита). Поэтому если вы по недосмотру где-то используете для позиционирования выражение/переменную типа &lt;code&gt;Integer&lt;/code&gt;, то этим вы автоматически ограничите свои данные 2 Гб. Но этот момент также зависит и от вида используемого потока. К примеру, в 32-битных приложениях &lt;code&gt;TMemoryStream&lt;/code&gt; не может работать с памятью больше 4 Гб, а &lt;code&gt;TResourceStream&lt;/code&gt; ограничен 2 Гб - потому что он работает с ресурсами в исполняемых файлах формата PE. А любой файл формата PE не может быть больше 2 Гб.&lt;br /&gt;
&lt;br /&gt;&amp;nbsp;&lt;/li&gt;
&lt;li&gt;Ну и под конец упомяну ещё такой момент: поскольку базовые методы чтения/записи работают с нетипизированными параметрами, то тут возможны ошибки при использовании динамических типов и указателей. Я уже приводил подобный пример в &lt;a title="Сериализация - файлы в стиле Pascal" href="http://www.gunsmoker.ru/2011/10/pascal.html"&gt;предыдущей статье&lt;/a&gt;, но приведу его ещё раз и тут:
&lt;pre class="brush:delphi"&gt;var
  StatArray: array[0..15] of Integer;
  DynArray: array of Integer;
  AnsiStr: AnsiString;
  Str: String;
  PCh: PChar;
  Stream: TStream;
... 
  // Неправильно (порча памяти):
  Stream.WriteBuffer(DynArray, Length(DynArray) * SizeOf(Integer));
  Stream.WriteBuffer(AnsiStr, Length(AnsiStr));
  Stream.WriteBuffer(Str, Length(Str) * SizeOf(Char));
  Stream.WriteBuffer(PCh, StrLen(PCh) * SizeOf(Char));
 
  // Правильно (при условии, что размер данных &gt; 0):
  Stream.WriteBuffer(StatArray, Length(StatArray) * SizeOf(Integer));
  Stream.WriteBuffer(StatArray, SizeOf(StatArray));
  Stream.WriteBuffer(StatArray[0], Length(StatArray) * SizeOf(Integer));
  Stream.WriteBuffer(StatArray[0], SizeOf(StatArray));
  Stream.WriteBuffer(Pointer(DynArray)^, Length(DynArray) * SizeOf(Integer));
  Stream.WriteBuffer(DynArray[0], Length(DynArray) * SizeOf(Integer));
  Stream.WriteBuffer(Pointer(AnsiStr)^, Length(AnsiStr));
  Stream.WriteBuffer(AnsiStr[1], Length(AnsiStr));
  Stream.WriteBuffer(Pointer(Str)^, Length(Str) * SizeOf(Char));
  Stream.WriteBuffer(Str[1], Length(Str) * SizeOf(Char));
  Stream.WriteBuffer(PCh^, StrLen(PCh) * SizeOf(Char));
  Stream.WriteBuffer(PCh[0], StrLen(PCh) * SizeOf(Char));
 
  // Неправильно (неверный индекс):
  Stream.WriteBuffer(AnsiStr[0], Length(AnsiStr));
  Stream.WriteBuffer(Str[0], Length(Str) * SizeOf(Char));
  Stream.WriteBuffer(PCh[1], StrLen(PCh) * SizeOf(Char));
 
  // Неправильно (неверный размер 1):
  Stream.WriteBuffer(Pointer(DynArray)^, SizeOf(DynArray));
  Stream.WriteBuffer(DynArray[0], SizeOf(DynArray));
  Stream.WriteBuffer(Pointer(AnsiStr)^, SizeOf(AnsiStr));
  Stream.WriteBuffer(AnsiStr[1], SizeOf(AnsiStr));
  Stream.WriteBuffer(Pointer(Str)^, SizeOf(Str));
  Stream.WriteBuffer(Str[1], SizeOf(Str));
 
  // Неправильно (неверный размер 2):
  Stream.WriteBuffer(Pointer(Str)^, Length(Str));
  Stream.WriteBuffer(Str[1], Length(Str));&lt;/pre&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;br /&gt;
&lt;a name="examples"&gt;&lt;/a&gt;&lt;h1&gt;Практика&lt;/h1&gt;
Все примеры ниже приводятся с полной обработкой ошибок. Примеры используют файл в папке с программой. Это удобно для тестирования и экспериментов, но, &lt;a title="Сериализация - налоги" href="http://www.gunsmoker.ru/2011/09/blog-post_11.html"&gt;как указано ранее&lt;/a&gt;, этого нужно избегать в реальных программах. После тестирования и перед использованием кода в релизе вы должны заменить папку программы на подпапку в Application Data.&lt;br /&gt;
&lt;br /&gt;
Как вы сейчас увидите, использование базовых методов потоков данных эквивалентно использованию &lt;a href="http://www.gunsmoker.ru/2011/10/pascal.html#untyped" title="Сериализация - файлы в стиле Pascal"&gt;нетипизированных файлов&lt;/a&gt; со всеми их преимуществами и недостатками. Впрочем, благодаря классам и наследованию, классы-наследники могут предоставлять другие методы и более удобный доступ для частных случаев - подробнее на это мы посмотрим &lt;a href="#misc" title="Особенности"&gt;ниже&lt;/a&gt;, а также в следующих статьях &lt;a title="Сериализация - оглавление" href="http://www.gunsmoker.ru/2011/08/blog-post.html"&gt;цикла&lt;/a&gt;. Также мы увидим как ООП предоставляет нам возможность использования некоторых интересных ходов для записи сложных динамических структур данных.&lt;br /&gt;
&lt;br /&gt;
&lt;ol&gt;
&lt;li&gt;Одно значение: &lt;code&gt;Double&lt;/code&gt;&lt;br /&gt;
&lt;pre class="brush:delphi"&gt;// Сохранение в файл
procedure TForm1.Button1Click(Sender: TObject);
var
  F: TFileStream;
  Value: Double;
begin
  Value := 5.5;

  F := TFileStream.Create(ExtractFilePath(GetModuleName(0)) + 'Test.bin', fmCreate or fmShareExclusive);
  try
    F.WriteBuffer(Value, SizeOf(Value));
  finally
    FreeAndNil(F);
  end;
end;

// Загрузка из файла
procedure TForm1.Button2Click(Sender: TObject);
var
  F: TFileStream;
  Value: Double;
begin
  F := TFileStream.Create(ExtractFilePath(GetModuleName(0)) + 'Test.bin', fmOpenRead or fmShareDenyWrite);
  try
    F.ReadBuffer(Value, SizeOf(Value));
  finally
    FreeAndNil(F);
  end;

  // Здесь Value = 5.5
end;&lt;/pre&gt;
&lt;br /&gt;&amp;nbsp;&lt;/li&gt;
&lt;li&gt;Одно значение переменного размера: &lt;code&gt;String&lt;/code&gt;
&lt;pre class="brush:delphi"&gt;// Сохранение в файл
procedure TForm1.Button1Click(Sender: TObject);
var
  F: TFileStream;
  Value: AnsiString;
begin
  Value := 'Example';

  F := TFileStream.Create(ExtractFilePath(GetModuleName(0)) + 'Test.bin', fmCreate or fmShareExclusive);
  try
    F.WriteBuffer(Pointer(Value)^, Length(Value));
  finally
    FreeAndNil(F);
  end;
end;

// Загрузка из файла
procedure TForm1.Button2Click(Sender: TObject);
var
  F: TFileStream;
  Value: AnsiString;
begin
  F := TFileStream.Create(ExtractFilePath(GetModuleName(0)) + 'Test.bin', fmOpenRead or fmShareDenyWrite);
  try
    SetLength(Value, F.Size);
    F.ReadBuffer(Pointer(Value)^, Length(Value));
  finally
    FreeAndNil(F);
  end;

  // Здесь Value = 'Example'
end;&lt;/pre&gt;
Данный случай прост - размер данных определяется по размеру файла. Для примера я выбрал &lt;code&gt;AnsiString&lt;/code&gt;, а не &lt;code&gt;String&lt;/code&gt; по друм причинам - во-первых, &lt;code&gt;String&lt;/code&gt; - это псевдоним либо на &lt;code&gt;AnsiString&lt;/code&gt;, либо на &lt;code&gt;UnicodeString&lt;/code&gt;, в зависимости от версии Delphi. Так что вам нужно использовать явные типы - &lt;code&gt;AnsiString&lt;/code&gt;, &lt;code&gt;WideString&lt;/code&gt; (или &lt;code&gt;UnicodeString&lt;/code&gt;), а не &lt;code&gt;String&lt;/code&gt;, иначе файл, созданный в одном варианте программы, нельзя будет прочитать в другом варианте программы.&lt;br /&gt;
&lt;br /&gt;
Во-вторых, используя &lt;code&gt;AnsiString&lt;/code&gt;, я показал, как вы можете загрузить в строку весь файл целиком, "как есть". Хотя, если подобный подход использовать в реальных программах, то уж лучше использовать &lt;code&gt;array of Byte&lt;/code&gt; или хотя бы &lt;code&gt;RawByteString&lt;/code&gt; - чтобы подчеркнуть двоичность данных.&lt;br /&gt;
&lt;br /&gt;&amp;nbsp;&lt;/li&gt;
&lt;li&gt;Набор однородных значений: &lt;code&gt;array of Double&lt;/code&gt;
&lt;pre class="brush:delphi"&gt;// Сохранение в файл
procedure TForm1.Button1Click(Sender: TObject);
var
  F: TFileStream;
  Values: array of Double;
  Index: Integer;
begin
  SetLength(Values, 10);
  for Index := 0 to High(Values) do
    Values[Index] := Random * 10;

  F := TFileStream.Create(ExtractFilePath(GetModuleName(0)) + 'Test.bin', fmCreate or fmShareExclusive);
  try
    for Index := 0 to High(Values) do
      F.WriteBuffer(Values[Index], SizeOf(Values[Index]));
  finally
    FreeAndNil(F);
  end;
end;

// Загрузка из файла
procedure TForm1.Button2Click(Sender: TObject);
var
  F: TFileStream;
  Values: array of Double;
  Index: Integer;
begin
  F := TFileStream.Create(ExtractFilePath(GetModuleName(0)) + 'Test.bin', fmOpenRead or fmShareDenyWrite);
  try
    SetLength(Values, F.Size div SizeOf(Values[0]));
    for Index := 0 to High(Values) do
      F.ReadBuffer(Values[Index], SizeOf(Values[Index]));
  finally
    FreeAndNil(F);
  end;

  // Здесь Values тождественно равны исходным данным из Button1Click
end;&lt;/pre&gt;
И снова, благодаря фиксированности размеров элементов, мы можем установить размер массива ещё до чтения из файла. Обратите внимание, что мы могли бы свети этот пример к предыдущему, прочитав/записав весь массив за раз, вместо поэлементного копирования.&lt;br /&gt;
&lt;br /&gt;
Также обратите внимание, что не имеет значения, какой индекс используется внутри выражения у &lt;code&gt;SizeOf&lt;/code&gt;. Более того, не требуется даже наличие (существование) этого элемента. Это потому, что мы не обращаемся к нему - мы только просим у компилятора его размер. Это, по сути, константа. Так что всё выражение вообще не вычисляется - оно просто заменяется числом. Это удобный трюк для написания подобного кода, потому что это удобнее, чем писать тип явно: &lt;code&gt;SizeOf(Double)&lt;/code&gt;. Почему? А что, если мы изменим объявление типа с &lt;code&gt;Double&lt;/code&gt; на &lt;code&gt;Single&lt;/code&gt;? И забудем обновить &lt;code&gt;SizeOf&lt;/code&gt;? Тогда это приведёт к порче памяти - т.к. писаться или читаться будет больше, чем реально есть байт в элементе. Это выглядит не очень страшно для массива из &lt;code&gt;Double&lt;/code&gt;, но рассмотрите вариант, скажем, строки - изменение размера &lt;code&gt;Char&lt;/code&gt; гораздо более вероятно. А вот если мы используем форму &lt;code&gt;SizeOf&lt;/code&gt; как в примере, то такой проблемы не будет - размер изменится автоматически. &lt;br /&gt;
&lt;br /&gt;&amp;nbsp;&lt;/li&gt;
&lt;li&gt;Набор однородных значений переменного размера: &lt;code&gt;array of String&lt;/code&gt;
&lt;pre class="brush:delphi"&gt;// Сохранение в файл
procedure TForm1.Button1Click(Sender: TObject);
var
  F: TFileStream;
  Values: array of String;
  Index: Integer;
  Str: WideString;
  Len: LongInt;
begin
  SetLength(Values, 10);
  for Index := 0 to High(Values) do
    Values[Index] := 'Str #' + IntToStr(Index);

  F := TFileStream.Create(ExtractFilePath(GetModuleName(0)) + 'Test.bin', fmCreate or fmShareExclusive);
  try
    for Index := 0 to High(Values) do
    begin
      Str := Values[Index];
      Len := Length(Str);

      F.WriteBuffer(Len, SizeOf(Len));
      if Len &gt; 0 then
        F.WriteBuffer(Str[1], Length(Str) * SizeOf(Str[1]));
    end;
  finally
    FreeAndNil(F);
  end;
end;

// Загрузка из файла
procedure TForm1.Button2Click(Sender: TObject);
var
  F: TFileStream;
  Values: array of String;
  Str: WideString;
  Len: LongInt;
begin
  F := TFileStream.Create(ExtractFilePath(GetModuleName(0)) + 'Test.bin', fmOpenRead or fmShareDenyWrite);
  try
    while F.Read(Len, SizeOf(Len)) &gt; 0 do
    begin
      SetLength(Str, Len);
      if Len &gt; 0 then
        F.ReadBuffer(Str[1], Length(Str) * SizeOf(Str[1]));

      SetLength(Values, Length(Values) + 1);
      Values[High(Values)] := Str;
    end;
  finally
    FreeAndNil(F);
  end;

  // Здесь Values тождественно равны исходным данным из Button1Click
end;&lt;/pre&gt;
С записью набора динамических данных возникает проблема - как отличить один элемент от другого? Мы не можем более использовать переход на другую строку, как это было с текстовыми файлами. Тут есть несколько вариантов.&lt;br /&gt;
&lt;br /&gt;
Самый простой и очевидный - ввести разделитель данных. Т.е. элементы отделяются друг от друга специальным символом. В качестве такого чаще всего выступает нулевой символ (#0) - это аналог разделителя строк в текстовых файлах. Тогда чтение-запись сведётся к примеру два. Но я не стал показывать этот путь, т.к. он очевидно вводит ограничение на возможные данные: теперь данные не могут содержать в себе разделитель (каким бы вы его ни выбрали). Конечно, вы можете его экранировать, но гораздо проще будет выбор другого подхода.&lt;br /&gt;
&lt;br /&gt;
И я его показал - это явная запись размера данных до записи самих данных. Т.е. мы пишем два значения для каждого элемента: длину и сами данные.&lt;br /&gt;
&lt;br /&gt;
Кроме того, в этом же примере показано, как можно сделать так, чтобы внутри программы работать с хорошо знакомым &lt;code&gt;String&lt;/code&gt;, а в файле хранить фиксированный тип (&lt;code&gt;AnsiString&lt;/code&gt;/&lt;code&gt;RawByteString&lt;/code&gt; или &lt;code&gt;WideString&lt;/code&gt;/&lt;code&gt;UnicodeString&lt;/code&gt;). Вообще говоря, даже если вы работаете на Delphi 7 или любой другой версии Delphi до 2007 включительно - я бы рекомендовал всегда писать Unicode-данные в формате &lt;code&gt;WideString&lt;/code&gt; во внешние хранилища.&lt;br /&gt;
&lt;br /&gt;
Обратите внимание, что в качестве счётчика длины используется &lt;code&gt;LongInt&lt;/code&gt;, а не &lt;code&gt;Integer&lt;/code&gt; - по причинам, указанным выше для типизированных файлов: &lt;code&gt;String&lt;/code&gt;, &lt;code&gt;Extended&lt;/code&gt;, &lt;code&gt;Integer&lt;/code&gt; и &lt;code&gt;Cardinal&lt;/code&gt; могут менять свои размеры в зависимости от окружения - поэтому мы используем другие типы, которые гарантированно всегда имеют один и тот же размер.&lt;br /&gt;
&lt;br /&gt;
Ещё в этом примере показан вариант пример использования метода &lt;code&gt;Read&lt;/code&gt;: идея в том, что если будет достигнут конец потока, то вызов метода &lt;code&gt;Read&lt;/code&gt; вернёт 0. Т.е. это аналог функции EoF. Альтернативным решением является код
&lt;pre class="brush:delphi"&gt;    ...
    while F.Position &lt; F.Size do
    begin
      F.ReadBuffer(Len, SizeOf(Len));
      SetLength(Str, Len);
    ...&lt;/pre&gt;
Вы также можете создать вспомогательную функцию
&lt;pre class="brush:delphi"&gt;function EoS(Stream: TStream): Boolean; // End of Stream
begin
  Result := (F.Position &gt;= F.Size);
end;
    ...
    while not EoS(F) do
    begin
      F.ReadBuffer(Len, SizeOf(Len));
      SetLength(Str, Len);
    ...&lt;/pre&gt;
И тогда этот пример будет эквивалентен &lt;a href="http://www.gunsmoker.ru/2011/10/pascal.html#examples_untyped" title="Практика: нетипизированные файлы"&gt;примеру для нетипизированных файлов Pascal&lt;/a&gt;. Я буду использовать подпрограмму &lt;code&gt;EoS&lt;/code&gt; в примерах ниже.&lt;br /&gt;
&lt;br /&gt;&amp;nbsp;&lt;/li&gt;
&lt;li&gt;Запись - набор неоднородных данных:
&lt;pre class="brush:delphi"&gt;type
  TData = record
    Signature: LongWord;
    Size: LongInt;
    Comment: String;
    CRC: LongWord;
  end;

// Сохранение в файл
procedure TForm1.Button1Click(Sender: TObject);
var
  F: TFileStream;
  Value: TData;
  Len: LongInt;
  Str: WideString;
begin
  Value.Signature := 123;
  Value.Size := SizeOf(Value);
  Value.Comment := 'Example';
  Value.CRC := 0987654321;

  F := TFileStream.Create(ExtractFilePath(GetModuleName(0)) + 'Test.bin', fmCreate or fmShareExclusive);
  try
    F.WriteBuffer(Value.Signature, SizeOf(Value.Signature));
    F.WriteBuffer(Value.Size, SizeOf(Value.Size));
    Str := Value.Comment;
    Len := Length(Str);
    F.WriteBuffer(Len, SizeOf(Len));
    if Len &gt; 0 then
      F.WriteBuffer(Str[1], Length(Str) * SizeOf(Str[1]));
    F.WriteBuffer(Value.CRC, SizeOf(Value.CRC));
  finally
    FreeAndNil(F);
  end;
end;

// Загрузка из файла
procedure TForm1.Button2Click(Sender: TObject);
var
  F: TFileStream;
  Value: TData;
  Len: LongInt;
  Str: WideString;
begin
  F := TFileStream.Create(ExtractFilePath(GetModuleName(0)) + 'Test.bin', fmOpenRead or fmShareDenyWrite);
  try
    F.ReadBuffer(Value.Signature, SizeOf(Value.Signature));
    F.ReadBuffer(Value.Size, SizeOf(Value.Size));
    F.ReadBuffer(Len, SizeOf(Len));
    SetLength(Str, Len);
    if Len &gt; 0 then
      F.ReadBuffer(Str[1], Length(Str) * SizeOf(Str[1]));
    Value.Comment := Str;
    F.ReadBuffer(Value.CRC, SizeOf(Value.CRC));
  finally
    FreeAndNil(F);
  end;

  // Здесь Value = значениям из Button1Click
end;&lt;/pre&gt;
В отличие от типизированных файлов, для нетипизированных файлов нет никаких проблем с записью неоднородных данных - вы просто пишете одно за другим. Данные фиксированного размера самодокументируются, а для динамических данных вы пишете сначала их размер, а затем сами данные. При чтении повторяете всё это в обратном порядке.&lt;br /&gt;
&lt;br /&gt;
Кстати, я бы вынес запись динамических данных в отдельные служебные подпрограммы:
&lt;pre class="brush:delphi"&gt;procedure WriteBufferDyn(F: TFileStream; const AData: WideString); overload;
var
  Len: LongInt;
begin
  Len := Length(AData);
  F.WriteBuffer(Len, SizeOf(Len));
  if Len &gt; 0 then
    F.WriteBuffer(AData[1], Length(AData) * SizeOf(AData[1]));
end;

procedure WriteBufferDyn(F: TFileStream; const AData: array of Byte); overload;
... 

// и так далее для каждого типа данных переменного размера, который вы используете

procedure ReadBufferDyn(F: TFileStream; out AData: String); overload;
var
  Len: LongInt;
  WS: WideString;
begin
  F.ReadBuffer(Len, SizeOf(Len));
  SetLength(WS, Len);
  if Len &gt; 0 then
    F.ReadBuffer(WS[1], Length(WS) * SizeOf(WS[1]));
  AData := WS;
end;

procedure ReadBufferDyn(F: TFileStream; out AData: TDynByteArray); overload;
... 

// и так далее для каждого типа данных переменного размера, который вы используете&lt;/pre&gt;
Тогда чтение-запись свелись бы к:
&lt;pre class="brush:delphi"&gt;    F.WriteBuffer(Value.Signature, SizeOf(Value.Signature));
    F.WriteBuffer(Value.Size, SizeOf(Value.Size));
    WriteBufferDyn(F, Value.Comment);
    F.WriteBuffer(Value.CRC, SizeOf(Value.CRC));
...
    F.ReadBuffer(Value.Signature, SizeOf(Value.Signature));
    F.ReadBuffer(Value.Size, SizeOf(Value.Size));
    ReadBufferDyn(F, Value.Comment);
    F.ReadBuffer(Value.CRC, SizeOf(Value.CRC));&lt;/pre&gt;
Выглядит существенно проще и красивее, не так ли? Иллюстрация силы выделения кода в подпрограммы.&lt;br /&gt;
&lt;br /&gt;
Вообще, конечно же, более правильный код получится при использовании шаблона "декоратор". Суть заключается в создании класса, который реализует методы виде &lt;code&gt;WriteBufferDyn&lt;/code&gt;/&lt;code&gt;ReadBufferDyn&lt;/code&gt;, выполняя их над потоком, который ему указали. Например:
&lt;pre class="brush:delphi"&gt;type
  TStreamDataDecorator = class
  private
    // Управление потоком, с которым мы будем работать 
    FStream: TStream;
    FOwn: Boolean;
    procedure SetStream(const Value: TStream);

    // Вспомогательные методы: добавьте свои на каждый тип динамических данных
    procedure ReadBuffer(var ABuffer; ABufferSize: Integer); overload;
    procedure ReadBuffer(out AStr: String); overload;
    procedure WriteBuffer(const ABuffer; ABufferSize: Integer); overload;
    procedure WriteBuffer(const AStr: WideString); overload;
  public
    // Декоратор
    constructor Create(const AStream: TStream = nil; const AOwn: Boolean = True);
    destructor Destroy; override;

    // Просто для удобства
    property Stream: TStream read FStream write SetStream;
    property Own: Boolean read FOwn write FOwn;

    // Вот ради чего всё делалось: высокоуровневый код
    procedure Write(const AData: TData);
    procedure Read(out AData: TData);
    function EoS: Boolean;
  end;

{ TStreamDataDecorator }

constructor TStreamDataDecorator.Create(const AStream: TStream;
  const AOwn: Boolean);
begin
  inherited Create;
  FStream := AStream;
  FOwn := AOwn;
end;

destructor TStreamDataDecorator.Destroy;
begin
  Stream := nil;
  inherited Destroy;
end;

procedure TStreamDataDecorator.SetStream(const Value: TStream);
begin
  if Assigned(FStream) and FOwn then
    FreeAndNil(FStream);
  FStream := Value;
  FOwn := False;
end;

procedure TStreamDataDecorator.Read(out AData: TData);
begin
  Assert(Assigned(FStream));

  ReadBuffer(AData.Signature, SizeOf(AData.Signature));
  ReadBuffer(AData.Size, SizeOf(AData.Size));
  ReadBuffer(AData.Comment);
  ReadBuffer(AData.CRC, SizeOf(AData.CRC));
end;

procedure TStreamDataDecorator.Write(const AData: TData);
begin
  Assert(Assigned(FStream));

  WriteBuffer(AData.Signature, SizeOf(AData.Signature));
  WriteBuffer(AData.Size, SizeOf(AData.Size));
  WriteBuffer(AData.Comment);
  WriteBuffer(AData.CRC, SizeOf(AData.CRC));
end;

function TStreamDataDecorator.EoS: Boolean;
begin
  Assert(Assigned(FStream));
  Result := (FStream.Position &gt;= FStream.Size);
end;

procedure TStreamDataDecorator.ReadBuffer(var ABuffer; ABufferSize: Integer);
begin
  Assert(Assigned(FStream));

  FStream.ReadBuffer(ABuffer, ABufferSize);
end;

procedure TStreamDataDecorator.ReadBuffer(out AStr: String);
var
  Len: LongInt;
  Str: WideString;
begin
  ReadBuffer(Len, SizeOf(Len));
  SetLength(Str, Len);
  if Len &gt; 0 then
    ReadBuffer(Str[1], Length(Str) * SizeOf(Str[1]));
  AStr := Str;
end;

procedure TStreamDataDecorator.WriteBuffer(const ABuffer; ABufferSize: Integer);
begin
  Assert(Assigned(FStream));

  FStream.WriteBuffer(ABuffer, ABufferSize);
end;

procedure TStreamDataDecorator.WriteBuffer(const AStr: WideString);
var
  Len: LongInt;
begin
  Len := Length(AStr);
  WriteBuffer(Len, SizeOf(Len));
  if Len &gt; 0 then
    WriteBuffer(AStr[1], Length(AStr) * SizeOf(AStr[1]));
end;&lt;/pre&gt;
В этом примере реализовано 3 вещи: во-первых, мы реализовали хранение ссылки на поток данных, над которым будем выполнять операции. Во-вторых, мы реализовали несколько вспомогательных низкоуровневых операций в секции &lt;code&gt;private&lt;/code&gt;. В-третьих, мы реализовали высокоуровневые операции &lt;code&gt;Read&lt;/code&gt; и &lt;code&gt;Write&lt;/code&gt; в секции &lt;code&gt;public&lt;/code&gt;. Тогда сохранение и загрузка сводятся к такому простому коду:
&lt;pre class="brush:delphi"&gt;// Сохранение в файл
procedure TForm1.Button1Click(Sender: TObject);
var
  F: TStreamDataDecorator;
  Value: TData;
begin
  Value.Signature := 123;
  Value.Size := SizeOf(Value);
  Value.Comment := 'Example';
  Value.CRC := 0987654321;

  F := TStreamDataDecorator.Create(TFileStream.Create(ExtractFilePath(GetModuleName(0)) + 'Test.bin', fmCreate or fmShareExclusive));
  try
    F.Write(Value);
  finally
    FreeAndNil(F);
  end;
end;

// Загрузка из файла
procedure TForm1.Button2Click(Sender: TObject);
var
  F: TStreamDataDecorator;
  Value: TData;
begin
  F := TStreamDataDecorator.Create(TFileStream.Create(ExtractFilePath(GetModuleName(0)) + 'Test.bin', fmOpenRead or fmShareDenyWrite));
  try
    F.Read(Value);
  finally
    FreeAndNil(F);
  end;

  // Здесь Value = значениям из Button1Click
end;&lt;/pre&gt;
Ну не красота-ли?&lt;br /&gt;
&lt;br /&gt;
Имейте в виду, что именно этот подход вам нужно применять в реальных программах. В примерах ниже я буду применять процедуры вроде &lt;code&gt;WriteBufferDyn&lt;/code&gt;/&lt;code&gt;ReadBufferDyn&lt;/code&gt; исключительно по соображениям "меньше писать".&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;&amp;nbsp;&lt;/li&gt;
&lt;li&gt;Набор (массив) из записей - иерархический набор данных: 
&lt;pre class="brush:delphi"&gt;type
  TPerson = record
    Name: String;
    Age: Integer;
    Salary: Currency;
  end;

  TPersons = array of TPerson;

// Сохранение в файл
procedure TForm1.Button1Click(Sender: TObject);
var
  F: TFileStream;
  Values: TPersons;
  Index: Integer;
begin
  SetLength(Values, 3);
  for Index := 0 to High(Values) do
  begin
    Values[Index].Name := 'Person #' + IntToStr(Index);
    Values[Index].Age := Random(20) + 20;
    Values[Index].Salary := Random(10) * 5000;
  end;

  F := TFileStream.Create(ExtractFilePath(GetModuleName(0)) + 'Test.bin', fmCreate or fmShareExclusive);
  try
    for Index := 0 to High(Values) do
    begin
      WriteBufferDyn(F, Values[Index].Name);
      F.WriteBuffer(Values[Index].Age, SizeOf(Values[Index].Age));
      F.WriteBuffer(Pointer(@Values[Index].Salary)^, SizeOf(Values[Index].Salary));
    end;
  finally
    FreeAndNil(F);
  end;
end;

// Загрузка из файла
procedure TForm1.Button2Click(Sender: TObject);
var
  F: TFileStream;
  Values: TPersons;
  Value: TPerson;
begin
  F := TFileStream.Create(ExtractFilePath(GetModuleName(0)) + 'Test.bin', fmOpenRead or fmShareDenyWrite);
  try
    while not EoS(F) do
    begin
      ReadBufferDyn(F, Value.Name);
      F.ReadBuffer(Value.Age, SizeOf(Value.Age));
      F.ReadBuffer(Pointer(@Value.Salary)^, SizeOf(Value.Salary));

      SetLength(Values, Length(Values) + 1);
      Values[High(Values)] := Value;
    end;
  finally
    FreeAndNil(F);
  end;

  // Здесь Values тождественно равны исходным данным из Button1Click
end;&lt;/pre&gt;
Для начала хочу сразу же заметить, что странное выражение для поля &lt;code&gt;Salary&lt;/code&gt; сделано для обхода бага Delphi. Вообще, там должно стоять просто &lt;code&gt;F.WriteBuffer(Values[Index].Salary, SizeOf(Values[Index].Salary))&lt;/code&gt;, но в настоящий момент это выражение даёт ошибку "Variable required", поэтому используется обходной путь: мы берём указатель и разыменовываем его. Вообще говоря, это NOP-операция. А смысл её заключается в потере информации о типе. Это достаточно частый трюк, когда мы хотим запустить свои шаловливые руки под капот языка, минуя информацию типа, но в данном случае он используется для более благих целей: обхода бага компилятора. Вы можете использовать &lt;code&gt;F.WriteBuffer(Values[Index].Salary, SizeOf(Values[Index].Salary))&lt;/code&gt;, если ваша версия компилятора это позволяет, или просто выбрать другой тип данных (не &lt;code&gt;Currency&lt;/code&gt;).&lt;br /&gt;
&lt;br /&gt;
В любом случае, надо заметить, что достаточно часто при записи/чтении массива записей новички пытаются сделать такую вещь, как запись элемента целиком (&lt;code&gt;F.WriteBuffer(Values[Index], SizeOf(Values[Index]))&lt;/code&gt;). Это будет работать для записей фиксированного размера, не содержащих динамические данные (указатели). Ровно как это работает для &lt;a title="Типизированные файлы" href="http://www.gunsmoker.ru/2011/10/pascal.html#typed"&gt;типизированных файлов&lt;/a&gt;. Но если в записях у вас встречаются строки, динамические массивы и другие данные-указатели, то этот подход не будет работать. Собственно, если вы используете типизированные файлы, то компилятор даже не даст вам объявить такой тип данных (&lt;code&gt;file of String&lt;/code&gt;, например, или &lt;code&gt;file of Запись&lt;/code&gt;, где &lt;code&gt;Запись&lt;/code&gt; содержит &lt;code&gt;String&lt;/code&gt;). Но суть потоков данных - в прямом доступе, минуя информацию типа. Так что по рукам за это вам никто не даст. Вместо этого код будет просто вылетать или давать неверные результаты. А проблема тут в том, что для динамических данных, поле - это просто указатель. Записывая элемент "как есть" вы запишете в файл значение указателя, но не данные, на которые он указывает. Запись в файл произойдёт нормально, но в файле вы не найдёте своих строк. Чтение из файла тоже пройдёт отлично. Но как только вы попробуете обратиться к прочитанной строке - код вылетит с access violation, потому что указатель строки указывает в космос, на мусор.&lt;br /&gt;
&lt;br /&gt;
Аналогично предыдущим обсуждениям, самый простой способ решения проблемы (но не всегда достаточный) - замена &lt;code&gt;String&lt;/code&gt; в записях на &lt;code&gt;ShortString&lt;/code&gt; или статический массив символов. Я не буду рассматривать этот вариант, т.к. он сводится к предыдущим примерам с записью данных фиксированного размера.&lt;br /&gt;
&lt;br /&gt;
Вместо этого в примере я показал уже известную технику: запись длины строки вместе с её данными. Это избавляет вас от всех недостатков &lt;code&gt;ShortString&lt;/code&gt;/массива символов, но даёт новый недостаток: теперь вы не можете сохранить данные одной строчкой, вам нужно писать их поле-за-полем.&lt;br /&gt;
&lt;br /&gt;
Также по аналогии с предыдущим примером я покажу, как будет выглядеть код, если вы введёте класс-декоратор. Само описание класса я опущу для краткости - оно аналогично (но не тождественно) предыдущему примеру:
&lt;pre class="brush:delphi"&gt;type
  TStreamPersonDecorator = class
  ...
    procedure Write(const AData: TPerson);
    procedure Read(out AData: TPerson);
  ...
  end;

...

procedure TStreamPersonDecorator.Read(out AData: TPerson);
begin
  Assert(Assigned(FStream));

  ReadBuffer(AData.Name);
  ReadBuffer(AData.Age, SizeOf(AData.Age));
  ReadBuffer(AData.Salary, SizeOf(AData.Salary));
end;

procedure TStreamPersonDecorator.Write(const AData: TPerson);
begin
  Assert(Assigned(FStream));

  WriteBuffer(AData.Name);
  WriteBuffer(AData.Age, SizeOf(AData.Age));
  WriteBuffer(AData.Salary, SizeOf(AData.Salary));
end;

...

// Сохранение в файл
procedure TForm1.Button1Click(Sender: TObject);
var
  F: TStreamPersonDecorator;
  Values: TPersons;
  Index: Integer;
begin
  SetLength(Values, 3);
  for Index := 0 to High(Values) do
  begin
    Values[Index].Name := 'Person #' + IntToStr(Index);
    Values[Index].Age := Random(20) + 20;
    Values[Index].Salary := Random(10) * 5000;
  end;

  F := TStreamPersonDecorator.Create(TFileStream.Create(ExtractFilePath(GetModuleName(0)) + 'Test.bin', fmCreate or fmShareExclusive));
  try
    for Index := 0 to High(Values) do
      F.Write(Values[Index]);
  finally
    FreeAndNil(F);
  end;
end;

// Загрузка из файла
procedure TForm1.Button2Click(Sender: TObject);
var
  F: TStreamPersonDecorator;
  Values: TPersons;
  Value: TPerson;
begin
  F := TStreamPersonDecorator.Create(TFileStream.Create(ExtractFilePath(GetModuleName(0)) + 'Test.bin', fmOpenRead or fmShareDenyWrite));
  try
    while not F.EoS do
    begin
      F.Read(Value);

      SetLength(Values, Length(Values) + 1);
      Values[High(Values)] := Value;
    end;
  finally
    FreeAndNil(F);
  end;

  // Здесь Values тождественно равны исходным данным из Button1Click
end;&lt;/pre&gt;
Мы также могли внести методы &lt;code&gt;Read&lt;/code&gt; и &lt;code&gt;Write&lt;/code&gt;, работающие с массивом из &lt;code&gt;TPerson&lt;/code&gt; в класс-декоратор.&lt;br /&gt;
&lt;br /&gt;&amp;nbsp;&lt;/li&gt;
&lt;li&gt;Массив из записей внутри записи - составные данные:
&lt;pre class="brush:delphi"&gt;type
  TCompose = record
    Signature: LongInt;
    Person: TPerson;
    Count: Integer;
    Related: TPersons;
  end;

  TComposes = array of TCompose;

procedure WriteBufferDyn(F: TFileStream; const APerson: TPerson); overload;
begin
  WriteBufferDyn(F, APerson.Name);
  F.WriteBuffer(APerson.Age, SizeOf(APerson.Age));
  F.WriteBuffer(Pointer(@APerson.Salary)^, SizeOf(APerson.Salary));
end;

procedure WriteBufferDyn(F: TFileStream; const APersons: TPersons); overload;
var
  Len: LongInt;
  Index: Integer;
begin
  Len := Length(APersons);
  F.WriteBuffer(Len, SizeOf(Len));
  for Index := 0 to High(APersons) do
    WriteBufferDyn(F, APersons[Index]);
end;

procedure ReadBufferDyn(F: TFileStream; out APerson: TPerson); overload;
begin
  ReadBufferDyn(F, APerson.Name);
  F.ReadBuffer(APerson.Age, SizeOf(APerson.Age));
  F.ReadBuffer(Pointer(@APerson.Salary)^, SizeOf(APerson.Salary));
end;

procedure ReadBufferDyn(F: TFileStream; out APersons: TPersons); overload;
var
  Len: LongInt;
  Index: Integer;
begin
  F.ReadBuffer(Len, SizeOf(Len));
  SetLength(APersons, Len);
  for Index := 0 to High(APersons) do
    ReadBufferDyn(F, APersons[Index]);
end;

// Сохранение в файл
procedure TForm1.Button1Click(Sender: TObject);
var
  F: TFileStream;
  Values: TComposes;
  Index: Integer;
  PersonIndex: Integer;
begin
  SetLength(Values, 3);
  for Index := 0 to High(Values) do
  begin
    Values[Index].Signature := 123456;
    Values[Index].Person.Name := 'Person #' + IntToStr(Index);
    Values[Index].Person.Age := Random(10) + 20;
    Values[Index].Person.Salary := Random(10) * 5000;
    Values[Index].Count := Random(10);
    SetLength(Values[Index].Related, Random(10));
    for PersonIndex := 0 to High(Values[Index].Related) do
    begin
      Values[Index].Related[PersonIndex].Name := 'Related #' + IntToStr(Index);
      Values[Index].Related[PersonIndex].Age := Random(10) + 20;
      Values[Index].Related[PersonIndex].Salary := Random(10) * 5000;
    end;
  end;

  F := TFileStream.Create(ExtractFilePath(GetModuleName(0)) + 'Test.bin', fmCreate or fmShareExclusive);
  try
    for Index := 0 to High(Values) do
    begin
      F.WriteBuffer(Values[Index].Signature, SizeOf(Values[Index].Signature));
      WriteBufferDyn(F, Values[Index].Person);
      F.WriteBuffer(Values[Index].Count, SizeOf(Values[Index].Count));
      WriteBufferDyn(F, Values[Index].Related);
    end;
  finally
    FreeAndNil(F);
  end;
end;

// Загрузка из файла
procedure TForm1.Button2Click(Sender: TObject);
var
  F: TFileStream;
  Values: TComposes;
  Value: TCompose;
begin
  F := TFileStream.Create(ExtractFilePath(GetModuleName(0)) + 'Test.bin', fmOpenRead or fmShareDenyWrite);
  try
    while not EoS(F) do
    begin
      F.ReadBuffer(Value.Signature, SizeOf(Value.Signature));
      ReadBufferDyn(F, Value.Person);
      F.ReadBuffer(Value.Count, SizeOf(Value.Count));
      ReadBufferDyn(F, Value.Related);

      SetLength(Values, Length(Values) + 1);
      Values[High(Values)] := Value;
    end;
  finally
    FreeAndNil(F);
  end;

  // Здесь Values тождественно равны исходным данным из Button1Click
end;&lt;/pre&gt;
Как видите - здесь нет никаких проблем, вы просто соединяете воедино техники из предыдущих примеров. Мы используем технику с записью счётчика длины для динамических данных в двух местах: при записи строк и при записи массивов (поле &lt;code&gt;Related&lt;/code&gt;). &lt;br /&gt;
&lt;br /&gt;
Кроме того, хотя я мог бы написать весь код в цикле, друг за другом, я всё же выделил новые подпрограммы - исключительно ради удобства. Код теперь выглядит компактно и аккуратно. Он прозрачен и его легко проверить. А если бы я внёс код из подпрограмм в главные циклы, то получилась бы &lt;a title="Как писать понятный код - руководство для учащихся" href="http://www.gunsmoker.ru/2011/01/blog-post.html"&gt;слабочитаемая мешанина кода&lt;/a&gt;. &lt;br /&gt;
&lt;br /&gt;
Заметьте, что вы всё ещё должны писать записи по отдельным полям. И если вы меняете объявление записи - вам лучше бы не забыть поменять код, сериализующий и десериализующий запись.&lt;br /&gt;
&lt;br /&gt;
И снова: не забывайте, что это только пример. В реальных программах вам следует посмотреть в сторону класса-декоратора. Я не буду приводить тут пример: он делается по аналогии с предыдущими случаями. Просто добавьте новые методы чтения/записи, вот и всё.&lt;br /&gt;
&lt;br /&gt;&amp;nbsp;&lt;/li&gt;
&lt;/ol&gt;
&lt;br /&gt;
&lt;a name="misc"&gt;&lt;/a&gt;&lt;h1&gt;Особенности&lt;/h1&gt;
Посмотрим теперь на различные конкретные наследники класса &lt;code&gt;TStream&lt;/code&gt; и то, что они нам предлагают.&lt;br /&gt;
&lt;br /&gt;
Примечание: не все из нижеперечисленных возможностей существуют во всех версиях Delphi. Если у вас старая версия Delphi, то у вас могут отсутствовать некоторые описываемые классы, свойства или методы. В этом случае вам придётся обходится без них или писать аналоги самостоятельно.&lt;br /&gt;
&lt;br /&gt;
&lt;a name="thandlestream"&gt;&lt;/a&gt;&lt;h1&gt;THandleStream&lt;/h1&gt;
&lt;a href="http://docwiki.embarcadero.com/VCL/en/Classes.THandleStream" title="Classes.THandleStream"&gt;&lt;code&gt;THandleStream&lt;/code&gt;&lt;/a&gt; предназначен для работы с файлами в смысле операционной системы.&lt;br /&gt;
&lt;br /&gt;
Короче говоря, &lt;code&gt;THandleStream&lt;/code&gt; является оболочкой к файлам в стиле ОС. Объект этого типа можно связать с &lt;code&gt;THandle&lt;/code&gt;, полученный любым способом - скажем, от &lt;a title="MSDN: CreateFile Function" href="http://msdn.microsoft.com/en-us/library/windows/desktop/aa365747(v=vs.85).aspx"&gt;&lt;code&gt;CreateFile&lt;/code&gt;&lt;/a&gt;, &lt;a href="http://msdn.microsoft.com/en-us/library/windows/desktop/aa365152(v=vs.85).aspx" title="MSDN: CreatePipe function"&gt;&lt;code&gt;CreatePipe&lt;/code&gt;&lt;/a&gt; и так далее: лишь бы на этот описатель можно было вызывать &lt;code&gt;ReadFile&lt;/code&gt; и &lt;code&gt;WriteFile&lt;/code&gt;.&lt;br /&gt;
&lt;br /&gt;
Используйте &lt;code&gt;THandleStream&lt;/code&gt; в двух случаях:
&lt;ol&gt;
&lt;li&gt;Функция ОС вернула вам описатель &lt;code&gt;THandle&lt;/code&gt;, а код, который вы хотите вызвать, требует &lt;code&gt;TStream&lt;/code&gt;. Тогда просто создайте &lt;code&gt;THandleStream&lt;/code&gt;, передав в его конструктор описатель от функции ОС, и передайте полученный объект коду. Например:
&lt;pre class="brush:delphi"&gt;var
  ReadHandle, WriteHandle: THandle;
  ReadStream, WriteStream: TStream;  
begin
  Win32Check(CreatePipe(@ReadHandle, @WriteHandle, nil, 0));
  try
    ReadStream := THandleStream.Create(ReadHandle);
    WriteStream := THandleStream.Create(WriteHandle);
    try
      ...
      Bitmap.SaveToStream(WriteStream); // Отправляем растровое изображение по pipe
      ...
    finally
      FreeAndNil(WriteStream);
      FreeAndNil(ReadStream);
    end;
  finally
    CloseHandle(WriteHandle);
    CloseHandle(ReadHandle);
  end;
end;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;Вы хотите использовать возможность, которую предоставляет функция ОС, но не объект Delphi. См. первый пример в следующем пункте.&lt;/li&gt;
&lt;/ol&gt;
&lt;br /&gt;
Сам &lt;code&gt;THandleStream&lt;/code&gt; не имеет ограничений и поддерживает все операции: чтение, запись, позиционирование и изменение размера. Однако нижележащий дескриптор объекта ядра может поддерживать не все операции. К примеру, описатель от файла на диске может быть открыт только для чтения, а описатель pipe не поддерживает позиционирование.&lt;br /&gt;
&lt;br /&gt;
Описатель, которым инициализирован объект, доступен через &lt;a href="http://docwiki.embarcadero.com/VCL/en/Classes.THandleStream.Handle" title="Classes.THandleStream.Handle"&gt;свойство &lt;code&gt;Handle&lt;/code&gt;&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
Обратите внимание, что сам &lt;code&gt;THandleStream&lt;/code&gt; никогда не закрывает описатель (ему нельзя передать ответственность за него). Поэтому вы должны закрывать описатель вручную или использовать &lt;code&gt;TFileStream&lt;/code&gt; (см. ниже).&lt;br /&gt;
&lt;br /&gt;
&lt;a name="tfilestream"&gt;&lt;/a&gt;&lt;h1&gt;TFileStream&lt;/h1&gt;
&lt;a href="http://docwiki.embarcadero.com/VCL/en/Classes.TFileStream" title="Classes.TFileStream"&gt;&lt;code&gt;TFileStream&lt;/code&gt;&lt;/a&gt; предназначен для работы с файлами на диске.&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;TFileStream&lt;/code&gt; наследуется от &lt;code&gt;THandleStream&lt;/code&gt;, так что он получает все возможности своего предка. &lt;code&gt;TFileStream&lt;/code&gt; не имеет ограничений на операции и поддерживает конструктор с передачей описателя. Например:
&lt;pre class="brush:delphi"&gt;var
  FileHandle: THandle;
  Stream: TFileStream;
begin
  FileHandle := CreateFile('...\Temp.tmp', GENERIC_READ or GENERIC_WRITE, 0, nil, CREATE_ALWAYS, FILE_ATTRIBUTE_TEMPORARY or FILE_FLAG_DELETE_ON_CLOSE, 0);
  Win32Check(FileHandle &lt;&gt; INVALID_FILE_HANDLE);
  Stream := TFileStream.Create(FileHandle);
  try
    ...
  finally
    FreeAndNil(Stream);
    // &lt;- не нужно делать CloseHandle(FileHandle);
  end;
end;&lt;/pre&gt;
В отличие от &lt;code&gt;THandleStream&lt;/code&gt;, &lt;code&gt;TFileStream&lt;/code&gt; закрывает открытый описатель файла, вне зависимости от способа его инициализации.&lt;br /&gt;
&lt;br /&gt;
Кроме того, &lt;code&gt;TFileStream&lt;/code&gt; поддерживает перегруженный конструктор, позволяющий открывать файлы. У него есть два параметра: (имя файла) и (режим открытия + режим разделения).&lt;br /&gt;
&lt;br /&gt;
Допустимые режимы открытия:
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;fmCreate&lt;/code&gt; - создаёт новый файл. Если такой файл уже есть, он удаляется перед созданием. Файл открывается в режиме записи.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;fmOpenRead&lt;/code&gt; - открывает файл только для чтения.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;fmOpenWrite&lt;/code&gt; - открывает файл только для записи.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;fmOpenReadWrite&lt;/code&gt; - открывает файл для чтения-записи.&lt;/li&gt;
&lt;/ol&gt;
Режимы разделения:
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;fmShareExclusive&lt;/code&gt; - запретить совместное использование.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;fmShareDenyWrite&lt;/code&gt; - запретить другим приложениям запись.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;fmShareDenyRead&lt;/code&gt; - запретить другим приложениям чтение.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;fmShareDenyNone&lt;/code&gt; - не вводить ограничения.&lt;/li&gt;
&lt;/ol&gt;
Режимы следует соединять друг с другом операцией &lt;code&gt;or&lt;/code&gt;.&lt;br /&gt;
&lt;br /&gt;
Примечание: у &lt;code&gt;TFileStream&lt;/code&gt; есть вариант конструктора с дополнительным параметром, но этот (третий) параметр существует только для обратной совместимости и сейчас игнорируется. Не следует пытаться передавать в него режим разделения файла.&lt;br /&gt;
&lt;br /&gt;
Флаги разделения используют &lt;a title="Но почему биты называются FILE_SHARE_READ и FILE_SHARE_WRITE?" href="http://www.transl-gunsmoker.ru/2009/04/fileshareread-filesharewrite.html"&gt;устаревшую deny-семантику MS-DOS&lt;/a&gt;, в отличие от современного API. См. также: &lt;a href="http://www.transl-gunsmoker.ru/2009/04/fileshare.html" title="Как биты FILE_SHARE_* взаимодействуют с запрашиваемыми битами доступа?"&gt;взаимодействие флагов режима открытия и разделения&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
Типичный пример открытия файла выглядит так:
&lt;pre class="brush:delphi"&gt;TFileStream.Create('MyFile.dat', fmOpenRead or fmShareDenyWrite);&lt;/pre&gt;
А создания файла - так:
&lt;pre class="brush:delphi"&gt;TFileStream.Create('MyFile.dat', fmCreate or fmShareExclusive);&lt;/pre&gt;
При работе с файлами типа логов (для которых необходимы совместное использование или мониторинг) могут использоваться такие вызовы:
&lt;pre class="brush:delphi"&gt;TFileStream.Create('MyFile.log', fmOpenRead or fmShareDenyNone);
TFileStream.Create('MyFile.log', fmCreate or fmShareDenyNone);&lt;/pre&gt;
&lt;br /&gt;
Далеко не все возможности функций открытия файлов ОС доступны через конструктор &lt;code&gt;TFileStream&lt;/code&gt;, но зато он является универсальным для любых платформ. Предпочтительно использовать именно его для доступа к файлам вместо функций ОС. Если же вам нужны какие-либо возможности, недоступные через конструктор &lt;code&gt;TFileStream&lt;/code&gt;, то используйте &lt;code&gt;TFileStream&lt;/code&gt;, инициализировав его описателем файла от системной функции открытия файлов, как указано в примерах выше.&lt;br /&gt;
&lt;br /&gt;
Если объект &lt;code&gt;TFileStream&lt;/code&gt; создавался через конструктор с именем файла, то это имя файла доступно в &lt;a href="http://docwiki.embarcadero.com/VCL/en/Classes.TFileStream.FileName" title="Classes.TFileStream.FileName"&gt;свойстве &lt;code&gt;FileName&lt;/code&gt;&lt;/a&gt;, иначе доступно только свойство &lt;code&gt;Handle&lt;/code&gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;a name="tmemorystream"&gt;&lt;/a&gt;&lt;h1&gt;TMemoryStream&lt;/h1&gt;
&lt;a href="http://docwiki.embarcadero.com/VCL/en/Classes.TMemoryStream" title="Classes.TMemoryStream"&gt;&lt;code&gt;TMemoryStream&lt;/code&gt;&lt;/a&gt; реализует потоковую обёртку к данным в памяти программы. Т.е. к буферу в динамической памяти осуществляется последовательный доступ.&lt;br /&gt;
&lt;br /&gt;
Используйте &lt;code&gt;TMemoryStream&lt;/code&gt;, если вам нужен "просто поток" или вам нужен промежуточный буфер-поток.&lt;br /&gt; 
&lt;br /&gt;
&lt;code&gt;TMemoryStream&lt;/code&gt; имеет конструктор без параметров, который создаёт пустой объект. Для заполнения потока после создания можно использовать обычные методы записи или же специальные методы &lt;a href="http://docwiki.embarcadero.com/VCL/en/Classes.TMemoryStream.LoadFromStream" title="Classes.TMemoryStream.LoadFromStream"&gt;&lt;code&gt;LoadFromStream&lt;/code&gt;&lt;/a&gt; (аналог вызова &lt;code&gt;CopyFrom(Stream, 0)&lt;/code&gt;) и &lt;a href="http://docwiki.embarcadero.com/VCL/en/Classes.TMemoryStream.LoadFromFile" title="Classes.TMemoryStream.LoadFromFile"&gt;&lt;code&gt;LoadFromFile&lt;/code&gt;&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
Также есть методы &lt;a href="http://docwiki.embarcadero.com/VCL/en/Classes.TCustomMemoryStream.SaveToStream" title="Classes.TCustomMemoryStream.SaveToStream"&gt;&lt;code&gt;SaveToStream&lt;/code&gt;&lt;/a&gt; и &lt;a href="http://docwiki.embarcadero.com/VCL/en/Classes.TCustomMemoryStream.SaveToFile" title="Classes.TCustomMemoryStream.SaveToFile"&gt;&lt;code&gt;SaveToFile&lt;/code&gt;&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;TMemoryStream&lt;/code&gt; не имеет ограничений и поддерживает все операции, включая изменение размера.&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;TMemoryStream&lt;/code&gt; хранит данные в динамической куче процесса, выделяя память по мере необходимости. Он сам автоматически управляет памятью. Реально памяти может быть выделено больше, чем лежит данных в потоке - т.н. "capacity &gt; size". Это стандартная оптимизация для "побайтовых записей".&lt;br /&gt;
&lt;br /&gt;
Дополнительной возможностью &lt;code&gt;TMemoryStream&lt;/code&gt; является предоставление свойства &lt;a href="http://docwiki.embarcadero.com/VCL/en/Classes.TCustomMemoryStream.Memory" title="Classes.TCustomMemoryStream.Memory"&gt;&lt;code&gt;Memory&lt;/code&gt;&lt;/a&gt;, позволяющего обратиться к данным потока напрямую, через указатель, минуя последовательные методы чтения/записи. По этой причине вы можете рассматривать &lt;code&gt;TMemoryStream&lt;/code&gt; как "переходник" между &lt;code&gt;TStream&lt;/code&gt; и нетипизированным указателем.&lt;br /&gt;
&lt;br /&gt;
Простейший пример использования &lt;code&gt;TMemoryStream&lt;/code&gt; (в данном случае - для конвертации строки в &lt;code&gt;TStream&lt;/code&gt;):
&lt;pre class="brush:delphi"&gt;procedure LoadFromText(const AHandle: THandle; const AText: AnsiString);
var
  Data: TMemoryStream;
begin
  Data := TMemoryStream.Create;
  try
    Data.Write(PAnsiChar(AText)^, Length(AText));
    Data.Position := 0;
    LoadFromStream(AHandle, Data);
  finally
    FreeAndNil(Data);
  end;
end;&lt;/pre&gt;
Примечание: в данном примере более эффективной была бы другая конструкция. Данный пример по сути копирует данные строки в поток. Но поскольку реально нам нужно здесь только чтение, то можно поступить по другому: создать поток, который будет работать прямо поверх данных строки, без их копирования. Это будет настоящий адаптер. Мы посмотрим на такой пример ниже.&lt;br /&gt;
&lt;br /&gt;
&lt;a name="tresourcestream"&gt;&lt;/a&gt;&lt;h1&gt;TResourceStream&lt;/h1&gt;
&lt;a href="http://docwiki.embarcadero.com/VCL/en/Classes.TResourceStream" title="Classes.TResourceStream"&gt;&lt;code&gt;TResourceStream&lt;/code&gt;&lt;/a&gt; предназначен для организации последовательного доступа к ресурсам в исполняемых файлах.&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;TResourceStream&lt;/code&gt; похож на &lt;code&gt;TMemoryStream&lt;/code&gt;. Он тоже работает с памятью программы (только не с кучей, а с ресурсами), он поддерживает методы &lt;a href="http://docwiki.embarcadero.com/VCL/en/Classes.TCustomMemoryStream.SaveToStream" title="Classes.TCustomMemoryStream.SaveToStream"&gt;&lt;code&gt;SaveToStream&lt;/code&gt;&lt;/a&gt; и &lt;a href="http://docwiki.embarcadero.com/VCL/en/Classes.TCustomMemoryStream.SaveToFile" title="Classes.TCustomMemoryStream.SaveToFile"&gt;&lt;code&gt;SaveToFile&lt;/code&gt;&lt;/a&gt;, а также свойство &lt;a href="http://docwiki.embarcadero.com/VCL/en/Classes.TCustomMemoryStream.Memory" title="Classes.TCustomMemoryStream.Memory"&gt;&lt;code&gt;Memory&lt;/code&gt;&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
Но в отличие от &lt;code&gt;TMemoryStream&lt;/code&gt;, &lt;code&gt;TResourceStream&lt;/code&gt; поддерживает только методы чтения, но не записи, а также не поддерживает изменение размера. Иными словами, &lt;code&gt;TResourceStream&lt;/code&gt; - это read-only.&lt;br /&gt;
&lt;br /&gt;
Собственно для инициализации у &lt;code&gt;TResourceStream&lt;/code&gt; есть два варианта конструктора, которые имеют по три параметра: описатель модуля, имя ресурса и тип ресурса. А разница между ними заключается в способе указания имени ресурса (второго параметра): по ID или по имени.&lt;br /&gt;
&lt;br /&gt;
Ну и несколько примеров:
&lt;pre class="brush:delphi"&gt;// Пример на использование метода SaveToFile 

// Извлечение своего ресурса в отдельный файл:
procedure ExtractRes(const ResType: PChar; const ResName, ResNewFileName: String);
var
  Res: TResourceStream;
begin
  Res := TResourceStream.Create(HInstance, ResName, ResType);
  try
    Res.SaveToFile(ResNewFileName);
  finally
    FreeAndNil(Res);
  end;
end;

...

ExtractRes('BINFILE', 'MYFILE', 'C:\MyFile.bin');
ExtractRes(RT_RCDATA, 'MYDATA', 'C:\MyData.bin');&lt;/pre&gt;
&lt;pre class="brush:delphi"&gt;// Пример на использование свойства Memory

// Проигрывание MP3 прямо из ресурса, не выгружая его в файл (с использованием BASS)
var
  Res: TResourceStream;
  BkMusic: HSAMPLE;
...
  // Создали обёртку TStream
  Res := TResourceStream.Create(HInstance, 'BKMUSIC', RT_RCDATA);
  // Загружаем напрямую из памяти - нет копирования данных
  BkMusic := BASS_SampleLoad(True, Res.Memory, 0, Res.Size, 1, BASS_SAMPLE_LOOP);
  // Поехали! (С)
  if BkMusic &lt;&gt; 0 then
    BASS_ChannelPlay(BkMusic, False);
...
  if BkMusic &lt;&gt; 0 then
  begin
    BASS_ChannelStop(BkMusic);
    BASS_SampleFree(BkMusic);
    BkMusic := 0;
  end;
  FreeAndNil(Res);
...&lt;/pre&gt;
&lt;br /&gt;
&lt;a name="tbytesstream"&gt;&lt;/a&gt;&lt;h1&gt;TBytesStream&lt;/h1&gt;
&lt;a href="http://docwiki.embarcadero.com/VCL/en/Classes.TBytesStream" title="Classes.TBytesStream"&gt;&lt;code&gt;TBytesStream&lt;/code&gt;&lt;/a&gt; хранит данные потока в массиве байтов.&lt;br /&gt;
&lt;br /&gt;
Используйте &lt;code&gt;TBytesStream&lt;/code&gt; как переходник между &lt;a href="http://docwiki.embarcadero.com/VCL/en/SysUtils.TBytes" title="SysUtils.TBytes"&gt;&lt;code&gt;TBytes&lt;/code&gt;&lt;/a&gt; и &lt;code&gt;TStream&lt;/code&gt;.&lt;br /&gt;
&lt;br /&gt;
Собственно, &lt;code&gt;TBytesStream&lt;/code&gt; аналогичен &lt;code&gt;TMemoryStream&lt;/code&gt;, только вместо блока памяти в куче он использует &lt;code&gt;TBytes&lt;/code&gt; в качестве хранилища для данных. Он также не имеет ограничений на операции, поддерживая чтение, запись, позиционирование и изменение размера. Он поддерживает методы &lt;a href="http://docwiki.embarcadero.com/VCL/en/Classes.TMemoryStream.LoadFromStream" title="Classes.TMemoryStream.LoadFromStream"&gt;&lt;code&gt;LoadFromStream&lt;/code&gt;&lt;/a&gt;, &lt;a href="http://docwiki.embarcadero.com/VCL/en/Classes.TMemoryStream.LoadFromFile" title="Classes.TMemoryStream.LoadFromFile"&gt;&lt;code&gt;LoadFromFile&lt;/code&gt;&lt;/a&gt;, &lt;a href="http://docwiki.embarcadero.com/VCL/en/Classes.TCustomMemoryStream.SaveToStream" title="Classes.TCustomMemoryStream.SaveToStream"&gt;&lt;code&gt;SaveToStream&lt;/code&gt;&lt;/a&gt; и &lt;a href="http://docwiki.embarcadero.com/VCL/en/Classes.TCustomMemoryStream.SaveToFile" title="Classes.TCustomMemoryStream.SaveToFile"&gt;&lt;code&gt;SaveToFile&lt;/code&gt;&lt;/a&gt;, а также свойство &lt;a href="http://docwiki.embarcadero.com/VCL/en/Classes.TCustomMemoryStream.Memory" title="Classes.TCustomMemoryStream.Memory"&gt;&lt;code&gt;Memory&lt;/code&gt;&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
В отличие от &lt;code&gt;TMemoryStream&lt;/code&gt;, у &lt;code&gt;TBytesStream&lt;/code&gt; есть конструктор, который принимает переменную типа &lt;code&gt;TBytes&lt;/code&gt; - это будут начальные данные потока. При этом не производится копирование данных (используется счётчик ссылок динамического массива). Все операции чтения-записи будут оперировать с исходными данными в оригинальной переменной типа &lt;code&gt;TBytes&lt;/code&gt;. Однако если вы измените размер потока (либо явно через &lt;code&gt;Size&lt;/code&gt;/&lt;code&gt;SetSize&lt;/code&gt;, либо неявно через запись данных в конец потока данных), то &lt;b&gt;поток сделает копию данных&lt;/b&gt; и будет работать уже с ней. При этом все будущие изменения в потоке не затронут оригинальной переменной типа &lt;code&gt;TBytes&lt;/code&gt;.&lt;br /&gt;
&lt;br /&gt;
Вы также можете передать &lt;code&gt;nil&lt;/code&gt; в конструктор, чтобы инициализировать пустой поток. В этом случае он не будет связан с переменной.&lt;br /&gt;
&lt;br /&gt;
Дополнительно &lt;code&gt;TBytesStream&lt;/code&gt; вводит &lt;a href="http://docwiki.embarcadero.com/VCL/en/Classes.TBytesStream.Bytes" title="Classes.TBytesStream.Bytes"&gt;свойство &lt;code&gt;Bytes&lt;/code&gt;&lt;/a&gt; оно работает аналогично свойству &lt;code&gt;Memory&lt;/code&gt;, только имеет тип &lt;code&gt;TBytes&lt;/code&gt;, а не &lt;code&gt;Pointer&lt;/code&gt;. &lt;b&gt;Предупреждение:&lt;/b&gt; не пытайтесь использовать &lt;code&gt;Length&lt;/code&gt; для определения размера данных. Размер хранилища может быть больше актуального размера ("Capacity &gt; Size"). Используйте свойство &lt;code&gt;Size&lt;/code&gt; для определения размера данных.&lt;br /&gt;
&lt;br /&gt;
Простой пример использования &lt;code&gt;TBytesStream&lt;/code&gt; как переходника (обратите внимание на усечение данных до их актуального размера, указанного в свойстве &lt;code&gt;Size&lt;/code&gt;):
&lt;pre class="brush:delphi"&gt;function DecodeBase64(const Input: AnsiString): TBytes;
var
  InStr: TPointerStream;
  OutStr: TBytesStream;
  Len: Integer;
begin
  InStr := TPointerStream.Create(PAnsiChar(Input), Length(Input));
  try
    OutStr := TBytesStream.Create;
    try
      DecodeStream(InStr, OutStr);
      Result := OutStr.Bytes;
      Len := OutStr.Size;
    finally
      FreeAndNil(OutStr);
    end;
  finally
    FreeAndNil(InStr);
  end;
  SetLength(Result, Len);
end;&lt;/pre&gt;
&lt;br /&gt;
&lt;code&gt;TBytes&lt;/code&gt; является динамическим массивом, т.е. автоуправляемым типом. Явно освобождать его не нужно, заботиться о вопросах владения - тоже.&lt;br /&gt;
&lt;br /&gt;
&lt;a name="tstringstream"&gt;&lt;/a&gt;&lt;h1&gt;TStringStream&lt;/h1&gt;
&lt;a href="http://docwiki.embarcadero.com/VCL/en/Classes.TStringStream" title="Classes.TStringStream"&gt;&lt;code&gt;TStringStream&lt;/code&gt;&lt;/a&gt; предоставляет последовательный доступ к информации, хранящейся в обычной строке.&lt;br /&gt;
&lt;br /&gt;
Используйте &lt;code&gt;TStringStream&lt;/code&gt; для хранения данных в строках. Использование &lt;code&gt;TStringStream&lt;/code&gt; даст вам в руки мощные возможности &lt;code&gt;TStream&lt;/code&gt;. &lt;code&gt;TStringStream&lt;/code&gt; удобен как промежуточный объект, который умеет хранить данные в строке, а также читать и писать их. &lt;br /&gt;
&lt;br /&gt;
По сути, &lt;code&gt;TStringStream&lt;/code&gt; является обёрткой к &lt;code&gt;TBytesStream&lt;/code&gt;, которая просто конвертирует строку в байты и обратно. У &lt;code&gt;TStringStream&lt;/code&gt; есть несколько вариантов конструкторов, которые инициализируют поток по разным типам строк. В Unicode версиях Delphi конструкторы также позволяют вам указывать кодировку для ANSI строк.&lt;br /&gt;
&lt;br /&gt;
Методы чтения-записи &lt;code&gt;TStringStream&lt;/code&gt; не затрагивают исходную строку, а всегда работают с копией данных (внутреннее хранилище в виде &lt;code&gt;TBytes&lt;/code&gt;).&lt;br /&gt;
&lt;br /&gt;
Ну и, конечно же, &lt;code&gt;TStringStream&lt;/code&gt; предоставляет строко-ориентированные свойства и методы. Во-первых, это методы &lt;a href="http://docwiki.embarcadero.com/VCL/en/Classes.TStringStream.WriteString" title="Classes.TStringStream.WriteString"&gt;WriteString&lt;/a&gt; и &lt;a href="http://docwiki.embarcadero.com/VCL/en/Classes.TStringStream.WriteString" title="Classes.TStringStream.ReadString"&gt;ReadString&lt;/a&gt;, которые пишут и читают данные из потока в виде строки. При этом кодировка (в Unicode-ных версиях Delphi) контролируется &lt;a href="http://docwiki.embarcadero.com/VCL/en/Classes.TStringStream.Encoding" title="Classes.TStringStream.Encoding"&gt;свойством &lt;code&gt;Encoding&lt;/code&gt;&lt;/a&gt;. И, равно как и предыдущие классы, &lt;code&gt;TStringStream&lt;/code&gt; выставляет наружу хранилище в "родном" формате: &lt;a href="http://docwiki.embarcadero.com/VCL/en/Classes.TStringStream.DataString" title="Classes.TStringStream.DataString"&gt;&lt;code&gt;DataString&lt;/code&gt;&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
Простой пример использования &lt;code&gt;TStringStream&lt;/code&gt; как переходника между строками и &lt;code&gt;TStream&lt;/code&gt;:
&lt;pre class="brush:delphi"&gt;function EncodeString(const Input: String): String;
var
  InStr, OutStr: TStringStream;
begin
  InStr := TStringStream.Create(Input); // &lt;- использует текущую кодовую страницу ANSI 
  // Можно было так:
  // InStr := TStringStream.Create(Input, CP_UTF8); // &lt;- использует UTF-8
  // или так:
  // InStr := TStringStream.Create(Input, TEncoding.Unicode); // &lt;- использует UTF-16
  try
    OutStr := TStringStream.Create(''); // &lt;- аналогичные замечания
    try
      EncodeStream(InStr, OutStr); // работает с потоками TStream - т.е. двоичными данными
      Result := OutStr.DataString;
    finally
      FreeAndNil(OutStr);
    end;
  finally
    FreeAndNil(InStr);
  end;
end;&lt;/pre&gt;
&lt;br /&gt;
Заметьте, что несмотря на наличие методов чтения строк и загрузки/сохранения данных из/в файлы, &lt;code&gt;TStringStream&lt;/code&gt; не пригоден для работы с текстовыми файлами. Он не работает с BOM и не позволяет прочитать одну строку (в смысле line) от разделителя до разделителя (он читает только указанное количество символов). По этой причине для работы с текстовыми файлами используют вспомогательный класс - &lt;code&gt;TStringList&lt;/code&gt;. Этому классу будет посвящена следующая статья (где и будут показаны методы работы с текстом), а здесь же я только приведу примеры шифрования/расшифровки текстового файла, использующие оба этих класса:
&lt;pre class="brush:delphi"&gt;procedure EncodeTextFile(const ASourceFileName, ADestFileName: String);
var
  SourceFile: TStringList;
  SourceData: TStringStream;
  DestFile: TFileStream;
begin
  SourceData := nil;
  try
    // Загрузка данных
    SourceFile := TStringList.Create;
    try
      // "Правильная" загрузка текстового файла с учётом BOM и кодировок
      SourceFile.LoadFromFile(ASourceFileName);

      // Конвертируем строку в TStream
      SourceData := TStringStream.Create(SourceFile.Text, TEncoding.Unicode);
    finally
      FreeAndNil(SourceFile);
    end;

    DestFile := TFileStream.Create(ADestFileName, fmCreate or fmShareExclusive);
    try
      EncodeStream(SourceData, DestFile); // двоичное шифрование
    finally
      FreeAndNil(DestFile);
    end;
  finally
    FreeAndNil(SourceData);
  end;
end;

procedure DecodeToTextFile(const ASourceFileName, ADestFileName: String);
var
  SourceFile: TFileStream;
  DestData: TStringStream;
  DestFile: TStringList;
begin
  SourceFile := TFileStream.Create(ASourceFileName, fmOpenRead or fmShareDenyWrite);
  try
    DestData := TStringStream.Create('', TEncoding.Unicode);
    try
      DecodeStream(SourceFile, DestData); // двоичное дешиврование

      DestFile := TStringList.Create;
      try
        // Конвертируем TStream в строку   
        DestFile.Text := DestData.DataString;

        // "Правильное" сохранение текстового файла с BOM (используем UTF-8)
        DestFile.SaveToFile(ADestFileName, TEncoding.UTF8);
      finally
        FreeAndNil(DestFile);
      end;
    finally
      FreeAndNil(DestData);
    end;
  finally
    FreeAndNil(SourceFile);
  end;
end;&lt;/pre&gt;
Конечно, на практике такой пример не имеет большого смысла, потому что гораздо проще просто работать с текстовым файлом как с двоичным - обработав его через &lt;code&gt;TFileStream&lt;/code&gt;. Но более удачного и простого примера мне сейчас в голову не приходит, а код выше прекрасно показывает пример соединения трёх классов для работы.&lt;br /&gt;
&lt;br /&gt;
&lt;a name="tstreamadapter"&gt;&lt;/a&gt;&lt;h1&gt;TStreamAdapter&lt;/h1&gt;
Понятие "потока данных" есть не только в Delphi, но и практически в любом другом современном языке. Разумеется, другие языки понятия не имеют, как работать с объектами Delphi, и наоборот: Delphi не знает, как устроены классы и объекты в других языках. К счастью, под Windows у нас есть COM и интерфейсы. С ними умеют работать почти все языки, так что это является де-факто стандартом межязыкового взаимодействия. И, конечно же, не могло быть иначе: для такой популярной концепции как "поток данных" существует свой интерфейс - &lt;a title="MSDN: IStream Interface" href="http://msdn.microsoft.com/en-us/library/windows/desktop/aa380034(v=vs.85).aspx"&gt;&lt;code&gt;IStream&lt;/code&gt;&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
Иными словами, если вам нужно передать куда-то поток данных - вы используете &lt;code&gt;IStream&lt;/code&gt;. Если вам кто-то передаёт поток данных, то это будет &lt;code&gt;IStream&lt;/code&gt;.&lt;br /&gt;
&lt;br /&gt;
Тут возникает маленькая проблемка: ваши Delphi объекты вообще-то не умеют работать с интерфейсом &lt;code&gt;IStream&lt;/code&gt;: они работают с классом &lt;code&gt;TStream&lt;/code&gt;. Что же делать?&lt;br /&gt;
&lt;br /&gt;
Для этого в Delphi есть два класса-адаптера, которые конвертируют &lt;code&gt;TStream&lt;/code&gt; в &lt;code&gt;IStream&lt;/code&gt; и наоборот. При этом они являются тонкими оболочками, которые просто перенаправляют вызовы. Они конвертируют интерфейс, копирования данных потока не происходит: просто вызовы, скажем, класса конвертируются в вызовы интерфейса (и наоборот), работая с данными оригинального потока данных напрямую.&lt;br /&gt;
&lt;br /&gt;
&lt;a href="http://docwiki.embarcadero.com/VCL/en/Classes.TStreamAdapter" title="Classes.TStreamAdapter"&gt;&lt;code&gt;TStreamAdapter&lt;/code&gt;&lt;/a&gt; предоставляет переходник от &lt;code&gt;TStream&lt;/code&gt; к &lt;code&gt;IStream&lt;/code&gt;. Он принимает в конструкторе экземпляр &lt;code&gt;TStream&lt;/code&gt; и выставляет наружу &lt;code&gt;IStream&lt;/code&gt;, который вы можете передать в чужой код.&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;TStreamAdapter&lt;/code&gt; поддерживает те же операции, что и оригинальный поток, который в него завёрнут.&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;TStreamAdapter&lt;/code&gt; может взять на себе ответственность за удаление исходного потока данных, а может оставить её вам. Вот два примера использования &lt;code&gt;TStreamAdapter&lt;/code&gt;, иллюстрирующие оба подхода:
&lt;pre class="brush:delphi"&gt;var
  Stream: TMemoryStream;
  COMStream: IStream;
begin
  // Готовим поток-источник: это TStream, который приходит от нашего Delphi-кода
  Stream := TMemoryStream.Create;
  try
    Bitmap.SaveToStream(Stream);
    Stream.Position := 0;
    
    // Создаём адаптер. Исходный поток мы должны удалять сами
    COMStream := TStreamAdapter.Create(Stream, soReference);

    // Загрузка растра в Windows Imaging Component 
    FImagingFactory.CreateDecoderFromStream(COMStream, guid_null, WICDecodeMetadataCacheOnDemand, BitmapDecoder));
    ...
  finally
    COMStream := nil;
    FreeAndNil(Stream);
  end; 
end;&lt;/pre&gt;
&lt;pre class="brush:delphi"&gt;var
  Stream: TFileStream;
  COMStream: IStream;
begin
  // Готовим поток-источник: это TStream, который приходит от нашего Delphi-кода
  Stream := TFileStream.Create('My.bmp', fmOpenRead or fmShareDenyWrite);

  // Создаём адаптер. Адаптер удаляет поток, мы не должны его удалять
  COMStream := TStreamAdapter.Create(Stream, soOwned);

  // Загрузка растра в Windows Imaging Component 
  FImagingFactory.CreateDecoderFromStream(COMStream, guid_null, WICDecodeMetadataCacheOnDemand, BitmapDecoder));
  ...
end;&lt;/pre&gt;
&lt;br /&gt;
&lt;a name="tolestream"&gt;&lt;/a&gt;&lt;h1&gt;TOleStream&lt;/h1&gt;
&lt;a href="http://docwiki.embarcadero.com/VCL/en/AxCtrls.TOleStream" title="AxCtrls.TOleStream"&gt;&lt;code&gt;TOleStream&lt;/code&gt;&lt;/a&gt; представляет собой обратный класс к &lt;code&gt;TStreamAdapter&lt;/code&gt;: переходник от &lt;code&gt;IStream&lt;/code&gt; к &lt;code&gt;TStream&lt;/code&gt;. &lt;br /&gt;
&lt;br /&gt;
Используется он аналогично. Единственная разница - нет вопроса владения, поскольку интерфейсы относятся к автоуправляемым типам.&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;TOleStream&lt;/code&gt; поддерживает те же операции, что и оригинальный поток, который в него завёрнут.&lt;br /&gt;
&lt;br /&gt;
Пример использования &lt;code&gt;TOleStream&lt;/code&gt;:
&lt;pre class="brush:delphi"&gt;// Открывает файл из композитного OLE-хранилища
function StreamFileRead(const APath: String): TStream;
var
  Strm: IStream;
  StorageToOpen: IStorage;
  FileToOpen: WideString;
begin
  // Пропущены проверки и подготовка
  ...

  // Получаем файл в виде IStream
  if Failed(StorageToOpen.OpenStream(PWideChar(FileToOpen), nil, STGM_READ or STGM_SHARE_EXCLUSIVE, 0, Strm)) then
  begin
    Result := nil;
    Exit;
  end;

  // Конвертируем IStream в TStream. Оригинальный поток (Strm) удалится сам когда надо
  Result := TOleStream.Create(Strm);
  Result.Position := 0;
end;&lt;/pre&gt;
&lt;br /&gt;
&lt;a name="other"&gt;&lt;/a&gt;&lt;h1&gt;Прочие потоки&lt;/h1&gt;
Это далеко не все стандартные потоки, реализованные в Delphi. К примеру, есть ещё &lt;code&gt;TWinSocketStream&lt;/code&gt;, &lt;code&gt;TBLOBStream&lt;/code&gt;, &lt;code&gt;TZCompressionStream&lt;/code&gt; и многие-многие другие. Многие из них являются переходниками, но много классов также предоставляют свою собственную функциональность.&lt;br /&gt;
&lt;br /&gt;
&lt;a name="create"&gt;&lt;/a&gt;&lt;h1&gt;Создание своих классов-потоков&lt;/h1&gt;
Как бы много в Delphi ни было классов-наследников, всегда найдётся случай, когда вас не устраивают стандартные классы. В этом случае вам нужно реализовывать свой класс-наследник.&lt;br /&gt;
&lt;br /&gt;
К примеру, помните пример для &lt;code&gt;TMemoryStream&lt;/code&gt;? Там мы копировали данные строки в поток. Давайте сейчас напишем класс, который позволял бы работать с блоком памяти напрямую, без копирования. Разумеется, такой класс не может поддерживать операцию изменения размера, но чтение, запись и позиционирование - вполне.&lt;br /&gt;
&lt;br /&gt;
Это несложно, если использовать в качестве базового класса &lt;code&gt;TCustomMemoryStream&lt;/code&gt; - он уже умеет читать данные из блока памяти, поддерживает позиционирование, но не умеет писать и никак не управляет нижележащим хранилищем. Тогда мы получаем:
&lt;pre class="brush:delphi"&gt;type
  // Наш новый класс - наследуем способности TCustomMemoryStream
  TPointerStream = class(TCustomMemoryStream)
  public
    // Нам нужно инициализировать поток данных блоком памяти
    constructor Create(P: Pointer; Size: Integer);
    // И научить его в него писать (а читать уже умеет TCustomMemoryStream)
    function Write(const Buffer; Count: Longint): Longint; override;
  end;

{ TPointerStream }

constructor TPointerStream.Create(P: Pointer; Size: Integer);
begin
  inherited Create;
  SetPointer(P, Size);
end;

function TPointerStream.Write(const Buffer; Count: Integer): Longint;
var
  Pos, EndPos: Int64;
  Mem: Pointer;
  Sz: Int64;
begin
  Result := 0;

  // Есть что писать? 
  Pos := Position;
  if (Pos &lt; 0) or (Count &lt;= 0) then
    Exit;

  // Данные потока
  Mem := Memory;
  Sz := Size;

  // Вычисляем конечную позицию и проверяем, что она в пределах блока памяти
  EndPos := Pos + Count;
  if EndPos &gt; Sz then
    raise EStreamError.Create('Ошибка записи данных в поток');

  // Перенос данных в буфер (в текущую позицию)
  System.Move(Buffer, Pointer(NativeUInt(Mem) + Pos)^, Count);

  // Обновляем текущую позицию потока
  Position := Pos;
  Result := Count;
end;

...

procedure LoadFromText(const AHandle: THandle; const AText: AnsiString);
var
  Data: TPointerStream;
begin
  // Нет копирования данных, оперирует сразу со строкой
  Data := TPointerStream.Create(PAnsiChar(AText), Length(AText));
  try
    LoadFromStream(AHandle, Data); 
  finally
    FreeAndNil(Data);
  end;
end;&lt;/pre&gt;
Вот список методов, которые вы можете захотеть переопределить в своих классах-наследниках:
&lt;ul&gt;
&lt;li&gt;(protected) &lt;code&gt;function GetSize: Int64;&lt;/code&gt; и &lt;code&gt;procedure SetSize(const NewSize: Int64);&lt;/code&gt; - для управление размером хранилища. Если вы не зададите свой &lt;code&gt;GetSize&lt;/code&gt;, то реализация по умолчанию будет использовать &lt;code&gt;Seek&lt;/code&gt; для поиска конца потока и определения размера (равному текущей позиции в конце потока). &lt;code&gt;SetSize&lt;/code&gt; по умолчанию просто ничего не делает.&lt;/li&gt;
&lt;li&gt;(public) &lt;code&gt;function Read(var Buffer; Count: Longint): Longint;&lt;/code&gt; и &lt;code&gt;function Write(const Buffer; Count: Longint): Longint;&lt;/code&gt; для чтения и записи данных. По умолчанию оба метода абстрактные. В любом наследнике вы должны их замещать.&lt;/li&gt;
&lt;li&gt;(public) &lt;code&gt;function Seek(const Offset: Int64; Origin: TSeekOrigin): Int64;&lt;/code&gt; для перемещения по потоку. Реализация по умолчанию вызывает исключение. Вы должны заместить этот метод, если ваши данные поддерживают позиционирование. Обычно это так. Исключение составляют случаи вроде сетевых сокетов.&lt;/li&gt;
&lt;/ul&gt;
Вот и всё. Всего три категории и всего пять методов. Реализуйте их - и у вас будет готовый поток. Все прочие методы и свойства являются переходниками к вышеуказанным. К примеру, метод &lt;code&gt;GetPosition&lt;/code&gt; (get-акцессор для свойства &lt;code&gt;Position&lt;/code&gt;) реализован как &lt;code&gt;Result := Seek(0, soCurrent);&lt;/code&gt;&lt;br /&gt;
&lt;br /&gt;
В общем, как видите, создать свой класс-поток - это очень просто.&lt;br /&gt;
&lt;br /&gt;
&lt;a name="conclusion"&gt;&lt;/a&gt;&lt;h1&gt;Преимущества и недостатки потоков данных&lt;/h1&gt;
Плюсы:
&lt;ul&gt;
&lt;li&gt;Де-факто стандарт языка Delphi&lt;/li&gt;
&lt;li&gt;Являются основой для других (более высокоуровневых) механизмов&lt;/li&gt;
&lt;li&gt;Имеют готовые оболочки для самых типичных случаев ("не надо писать самому" - в отличие от &lt;a href="http://www.gunsmoker.ru/2011/10/pascal.html" title="Сериализация - файлы в стиле Pascal"&gt;файлов в стиле Pascal&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Гибкость&lt;/li&gt;
&lt;li&gt;Стандартная обработка ошибок на исключениях&lt;/li&gt;
&lt;li&gt;Поддержка произвольных файлов (нет ограничения на размер)&lt;/li&gt;
&lt;li&gt;Нет проблем с многопоточностью&lt;/li&gt;
&lt;li&gt;Межъязыковая совместимость (через &lt;code&gt;IStream&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Поддержка Unicode и кодировок при работе с текстовыми данными (но не текстовыми файлами - нет поддержки BOM)&lt;/li&gt;
&lt;li&gt;Легко расширяются написанием классов-наследников&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
Минусы:
&lt;ul&gt;
&lt;li&gt;Необходимость ручной сериализации данных&lt;/li&gt;
&lt;li&gt;Ориентированы на побайтовую обработку, слабо пригодны для работы с текстовыми файлами&lt;/li&gt;
&lt;li&gt;Часто неопытные программисты используют &lt;code&gt;Read&lt;/code&gt; и &lt;code&gt;Write&lt;/code&gt; вместо &lt;code&gt;ReadBuffer&lt;/code&gt; и &lt;code&gt;WriteBuffer&lt;/code&gt;, не делая проверку результатов. Часто это приводит к некорректному коду без обработки ошибок&lt;/li&gt;
&lt;li&gt;Круче кривая обучения: сам &lt;code&gt;TStream&lt;/code&gt; не умеет делать ничего. Значит, чтобы делать в программах что-то полезное, нужно изучать многочисленные наследники &lt;code&gt;TStream&lt;/code&gt;, чтобы знать кто что умеет и когда что кого нужно применять. К примеру, если вам нужно отправить растр по сети, то вы должны сообразить, что вы можете создать &lt;code&gt;THandleStream&lt;/code&gt; для описателя сетевого сокета и   использовать его в сочетании с методом &lt;code&gt;SaveToStream&lt;/code&gt; объекта растра. Сравните это с файлами в стиле Pascal, где было всего три файловых типа, покрывавших все случаи использования&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
Вывод: если вы работаете в Delphi и хотите работать с любыми данными, то потоки данных должны быть вашим первым вариантом. Используйте что-то другое, только если это "другое" больше подходит для вашей задачи (к примеру, динамический массив для типизированных данных; также некоторые люди могут рассматривать &lt;a title="Сериализация - файлы в стиле Pascal" href="http://www.gunsmoker.ru/2011/10/pascal.html"&gt;файлы в стиле Pascal&lt;/a&gt; более подходящими для работы с текстовыми данными).&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1702873441071265539-2632220399538197041?l=www.gunsmoker.ru' alt='' /&gt;&lt;/div&gt;&lt;div class="feedflare"&gt;
&lt;a href="http://feeds.feedburner.com/~ff/GunSmokersBlog?a=_KQ1oTLUZsM:_sF0bPAkRog:yIl2AUoC8zA"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GunSmokersBlog?d=yIl2AUoC8zA" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GunSmokersBlog?a=_KQ1oTLUZsM:_sF0bPAkRog:4cEx4HpKnUU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GunSmokersBlog?i=_KQ1oTLUZsM:_sF0bPAkRog:4cEx4HpKnUU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GunSmokersBlog?a=_KQ1oTLUZsM:_sF0bPAkRog:F7zBnMyn0Lo"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GunSmokersBlog?i=_KQ1oTLUZsM:_sF0bPAkRog:F7zBnMyn0Lo" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GunSmokersBlog?a=_KQ1oTLUZsM:_sF0bPAkRog:V_sGLiPBpWU"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GunSmokersBlog?i=_KQ1oTLUZsM:_sF0bPAkRog:V_sGLiPBpWU" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GunSmokersBlog?a=_KQ1oTLUZsM:_sF0bPAkRog:qj6IDK7rITs"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GunSmokersBlog?d=qj6IDK7rITs" border="0"&gt;&lt;/img&gt;&lt;/a&gt; &lt;a href="http://feeds.feedburner.com/~ff/GunSmokersBlog?a=_KQ1oTLUZsM:_sF0bPAkRog:-BTjWOF_DHI"&gt;&lt;img src="http://feeds.feedburner.com/~ff/GunSmokersBlog?i=_KQ1oTLUZsM:_sF0bPAkRog:-BTjWOF_DHI" border="0"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/GunSmokersBlog/~4/_KQ1oTLUZsM" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://www.gunsmoker.ru/feeds/2632220399538197041/comments/default" title="Комментарии к сообщению" /><link rel="replies" type="text/html" href="http://www.gunsmoker.ru/2011/11/blog-post_12.html#comment-form" title="Комментарии: 8" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1702873441071265539/posts/default/2632220399538197041?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1702873441071265539/posts/default/2632220399538197041?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/GunSmokersBlog/~3/_KQ1oTLUZsM/blog-post_12.html" title="Сериализация - потоки данных" /><author><name>Александр Алексеев</name><uri>https://profiles.google.com/113168002104297556003</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="32" height="32" src="//lh3.googleusercontent.com/-aUMttZQBsuI/AAAAAAAAAAI/AAAAAAAAC3Y/QuZ7K9t_WzE/s512-c/photo.jpg" /></author><thr:total>8</thr:total><feedburner:origLink>http://www.gunsmoker.ru/2011/11/blog-post_12.html</feedburner:origLink></entry></feed>

