пятница, 16 марта 2012 г.

Урок 34. Создание библиотеки (Dll)

"...когда-то давно, когда наша Земля была еще маленькая 
и тепленькая, и по ней бегали такие ма-а-а-аленькие...
и с тех пор пошло и пошло, пошло и пошло..."

Мой отец коллекционировал записи А.Райкина, Р.Карцева и В.Ильченко, М.Мироновой и А.Менакера и других... Я любил слушать их вместе с ним, поэтому помню многое наизусть...

Так вот, с тех пор как появилась вычислительная техника в виде камушков (первые счеты - абак) и пошло, и пошло, и пошло...  Вот только задачи усложнялись временем... и повторялись...

К чему я это все? Да к тому, что застав в своем развитии вычислительную машину ДВК-2М, я приобрел привычку экономно относиться к памяти. Кто из Вас сейчас, владеющих гигабайтными дисками, способен представить себе пятидюймовую дискету стандартным объемом 360 килобайт? При особом искусстве разметить (форматировать) эту дискету можно было на 720кБ. И, о счастье!, в ДВК было целых два дисковода! Значит, в распоряжении программиста - аж целых 1440 килобайт! Не тут-то было! А операционная система? Значит, на пространстве немногим более одного (!) мегабайта нужно было исхитриться и прожить. Но эта трудность была даже не главной. А крошечное ОЗУ? Вот где была настоящая засада! Вот куда нужно было умудриться запихнуть еще что-то помимо операционной системы! Собственно от этого и пошло то, о чем я сегодня поведу речь...

Небольшие объемы оперативной памяти заставляли программистов делить задачу на кусочки и размещать в памяти только нужный фрагмент, именно тот, который сейчас будет использован в вычислениях, выбрасывая из памяти отработавший свою функцию кусок. Мало кто из современных авторов почитаемых мною сайтов, посвященных программированию, может припомнить BASICD и его замечательный оператор COMMON, дающий возможность загружать из библиотеки необходимую подпрограмму...

Когда читаю на различных сайтах доводы в пользу Dll, у меня невольно они вызывают улыбку:
при современных объемах оперативной памяти нет необходимости задумываться программисту об ее экономии. В принципе, программист может весь код забабахать в один модуль, получить на выходе огромного объема exe-к и радоваться. А что Вы говорите: пользователь будет долго ждать, пока этот exe-к загрузится? Пусть купит себе комп по мощнее...

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

Совсем другое дело, если у Вас много наработок. Тем более, если десяток программ используют одни и те же функции. Привести пример? Легко: перевод числа в строку прописью, подсчет доходности по ценным бумагам... в нашем случае - экспорт отчета в Excel.

Да-да, именно этот функционал я и предлагаю перетащить в библиотеку, вдруг где-то еще понадобится?

Начать следует с создания модуля библиотеки:

Много вопросов визард не задаст, но сгенерирует вот такое предупреждение:


library Project1;


{ Important note about DLL memory management: ShareMem must be the
  first unit in your library's USES clause AND your project's (select
  Project-View Source) USES clause if your DLL exports any procedures or
  functions that pass strings as parameters or function results. This
  applies to all strings passed to and from your DLL--even those that
  are nested in records and classes. ShareMem is the interface unit to
  the BORLNDMM.DLL shared memory manager, which must be deployed along
  with your DLL. To avoid using BORLNDMM.DLL, pass string information
  using PChar or ShortString parameters. }


uses
  SysUtils,
  Classes;


{$R *.res}


begin
end.


В двух словах: если функции Вашей библиотеки будут передавать или получать строковые значения, то нужно в Uses на первом месте иметь ссылку на ShareMem, соответственно, в системе должна присутствовать библиотека BORLNDMM.DLL. 

Если же, использование этой библиотеки по каким-то причинам невозможно (а такое бывает!), то передаваемые параметры должны быть типов PChar or ShortString.


Способов подключений библиотек два. У каждого есть свои преимущества и недостатки, но этой информации много в сети, не буду на ней останавливаться, оговорюсь лишь, что я буду использовать способ привязки DLL к программе. Моя задача иная: я хочу проиллюстрировать, как из готовой программы извлечь фрагмент и разместить его в библиотеке.

Сохраним проект под именем ProDelphiLyb.

Добавим в Uses ссылки на следующие модули:

  ShareMem,          // Нас предупреждали!!!

  DBGridEh,           // Для работы с сеткой
  ExcelXP,            // Для работы с приложением Excel
  DB,                 // Для работы с закладкой


и далее - текст процедуры, практически полностью совпадающий с аналогичной процедурой из предыдущего урока:


Procedure MyExportToExcel(WorkSheetName: String;  PGridEh: TDBGridEh;  XLApp: TExcelApplication; RepName: String; RepPeriod: String); stdcall;


// Вывод данных из источника объекта DBGridEh в Excel


var
  WorkBk: _WorkBook;     //  определяем WorkBook
  WorkSheet: _WorkSheet; //  определяем WorkSheet
  I, J: Integer;
  SavePlace: TBookmark;


begin




Try


  // Установка закладки
  SavePlace := PGridEh.DataSource.DataSet.GetBookmark;


  // Соединяемся с сервером TExcelApplication
  XLApp.Connect;


  // Добавляем WorkBooks в ExcelApplication
  XLApp.WorkBooks.Add(xlWBatWorkSheet, 0);


  // Выбираем первую WorkBook
  WorkBk := XLApp.WorkBooks.Item[1];


  // Определяем первый WorkSheet
  WorkSheet := WorkBk.WorkSheets.Get_Item(1) as _WorkSheet;


  // Заполняем свойства WorkSheet
  WorkSheet.Name := WorkSheetName;


  // Заголовок отчета
  J := 1;
  Worksheet.Cells.Item[J,1].Value:= RepName;
  Worksheet.Cells.Item[J,1].Font.Bold := True;
  Worksheet.Cells.Item[J,1].Font.size := 16;


  // Указание периода
  J := 2;
  Worksheet.Cells.Item[J,1].Value:= RepPeriod;
  Worksheet.Cells.Item[J,1].Font.Italic := True;


  // Заголовки колонок
  J := 3;
  PGridEh.DataSource.DataSet.First;
  with PGridEh.Columns do
    begin
      for i := 1 to Count  do
        if Items[I-1].Visible then
        begin
          Worksheet.Cells.Item[J,I].Value:= Items[I-1].Title.Caption;
          Worksheet.Cells.Item[J,I].Font.Bold := True;
          Worksheet.Cells.Item[J,I].HorizontalAlignment := xlCenter;
        end;
    end;


  // Собственно вывод данных из источника под сеткой в Excel
  J := 4;
  with PGridEh.DataSource.DataSet do
    begin
      First;
      while not Eof do
        begin
          with PGridEh.Columns do
            begin
              for I := 1 to Count  do
                if Items[I-1].Visible then
                  Worksheet.Cells.Item[J,I].Value:=FieldByName(Items[I-1].FieldName).AsVariant;
            end;
          Inc(J, 1);
          next;
        end;
    end;


  WorkSheet.Columns.ColumnWidth := 25;


  // Показываем Excel
  XLApp.Visible[0] := True;


  // Разрываем связь с сервером
  XLApp.Disconnect;


  // Возврат на закладку
  PGridEh.DataSource.DataSet.GotoBookmark(SavePlace);
  PGridEh.DataSource.DataSet.FreeBookmark(SavePlace);


Finally
End;
end;


exports
MyExportToExcel



begin
end.                                 // NB точка в конце

Для экспорта процедур и функций из DLL, необходимо использовать ключевое слово export, после которого перечисляются процедуры.

Выполните Compile и Build проекта. В результате последнего действия, в каталоге с проектом появится файл: ProDelphiLyb.dll

Теперь - самое время вернуться к проекту "Расходы".



Найдите в модуле главной формы процедуру ExportTo и замените все ее содержимое (которое переехало в модуль library ProDelphiLyb)  вот на это:


Procedure TMainFrm.ExportTo(p_WorkSheetName: String;  p_PGridEh: TDBGridEh;  p_XLApp: TExcelApplication;
                          p_RepName: String; p_RepSubTitle: String);
begin


  // Экспорт в Эксель
  Try
    MyExportToExcel(p_WorkSheetName, p_PGridEh, p_XLApp, p_RepName, p_RepSubTitle);
  Except
    MyMessenger.TitleString:='Проблема';
    MyMessenger.MessageString:='Возможно, в Вашем компьютере не установлен MS Excel';
    MyMessenger.Buttons:=[mbOk];
    MyMessenger.ShowMessage;
  end;


end;


А теперь - напишите оператор, подгружающий процедуру MyExportToExcel из вновь созданной библиотеки:


implementation
Uses  MOs, Oborot;


{$R *.dfm}


Procedure MyExportToExcel(WorkSheetName: String;  PGridEh: TDBGridEh;  XLApp: TExcelApplication;
                          RepName: String; RepPeriod: String); stdcall; external 'ProDelphiLyb.dll';



Если все сделано правильно, то работа программы "Расходы" внешне ни чем не будет отличаться от того, как она работала до сего момента. Но, если Вы попробуете установить эту программу на другой компьютер, у Вас, вполне вероятно, будут неприятности, связанные с использованием BORLNDMM.DLL, точнее - с отсутствием этой библиотеки в компьютере.

Предлагаю воспользоваться предупреждением и избавиться от строковых параметров.

начать нужно с модуля библиотеки, убрав из Uses Sharemem и изменив тип принимаемых параметров со String на PChar:


library ProDelphiLyb;
uses
//  ShareMem,
  DBGridEh,           // Для работы с сеткой
  ExcelXP,            // Для работы с приложением Excel
  DB,                 // Для работы с закладкой
  SysUtils,
  Classes;


{$R *.res}


Procedure MyExportToExcel(WorkSheetName: PChar;  PGridEh: TDBGridEh;  XLApp: TExcelApplication; RepName: PChar; RepPeriod: PChar); stdcall;


далее - в теле самой процедуры выполнить преобразование типов, поскольку ячейки Excel, в которые вписываются заголовок отчета и подзаголовок (период) не могут принять значения типа PChar, им - строки подавай:


  Worksheet.Cells.Item[J,1].Value:= StrPas(RepName);
и
  Worksheet.Cells.Item[J,1].Value:= StrPas(RepPeriod);


Save, compile, build.

В модуле main проекта "Расходы" в объявлениях процедур тоже внести необходимые поправки:


    { Public declarations }
    Procedure ExportTo(p_WorkSheetName: PChar;  p_PGridEh: TDBGridEh;  p_XLApp: TExcelApplication;  p_RepName: PChar; p_RepSubTitle: PChar);


(не забудьте внести аналогичные поправки и в декларации этой процедуры в разделе implementation !!!)

и


{$R *.dfm}


Procedure MyExportToExcel(WorkSheetName: PChar;  PGridEh: TDBGridEh;  XLApp: TExcelApplication; RepName: PChar; RepPeriod: PChar); stdcall; external 'ProDelphiLyb.dll';


И последнее, в модуле Oborot выполнить преобразование передаваемых параметров из строковых в PChar:

procedure TOborotFrm.N_ExportClick(Sender: TObject);
begin

  // Экспорт в Эксель
  MainFrm.ExportTo(PChar('ОВ'), DBGridEh1, MainFrm.XLApp, PChar('Оборотная ведомость'), PChar('Период: '+FormatDateTime('dd/mm/yy',Date_N.Value)+' - '+FormatDateTime('dd/mm/yy',Date_K.Value)));

end;


Трудились долго, нудно, но заметных на первый взгляд отличий не получили...
Смотрите на вопрос ширше или ширее, а лучше - глыбже... :-)







Комментариев нет:

Отправить комментарий