Автоматическое управление памятью
Автоматическое управление памятью есть один из сервисов, которые CLR обеспечивает во время управляемого исполнения. Сборка мусора управляет распределением и освобождением памяти. Это избавляет разработчика от необходимости писать соответствующий код. Автоматическое управление памятью решает типичные проблемы, такие как утечка памяти или попытка освободить уже уничтоженный объект.
Когда инициируется новый процесс, для него резервируется непрерывное адресное пространство, называемое управляемой кучей. Управляемая куча поддерживает указатель на следующий распределяемый в памяти объект. Первоначально он указывает на базовый адрес управляемой кучи. Все типы указателей распределяются в управляемой кучи. Когда создается первый указатель, память для ассоциированного с ним типа начинается с базового адреса кучи. При создании следующего указателя, память выделяется непосредственно за первым. Пока адресное пространство доступно, процесс продолжается описанным образом.
Распределение в управляемой куче идет быстрее, чем в неуправляемой. CLR просто наращивает значение указателя кучи, что почти также быстро, как при заталкивании данных в стек. Кроме того, так как новые объекты распределяются в памяти последовательно, приложение обращается к ним быстрее.
Оптимизирующая машина сборщика мусора определяет наилучшее время для удаления мусора. В процессе очистки он удаляет из памяти объекты, которые более не используются приложением. Для этого он исследует корни приложений. Каждое приложение имеет набор корней. Каждый корень либо ссылается на объект в управляемой куче, либо содержит NIL. Корни включают указатели на глобальные и статические объекты, локальные переменные и ссылки на объектные параметры в стеке потока и регистрах процессора. Мусорщик имеет доступ к списку активных корней, которые управляются компилятором времени исполнения и CLR. Используя этот список, он проверяет корни и строит граф, который содержит все объекты, которые порождены от корня.
Объекты, которые не содержит граф, не порождены от корней приложения. Мусорщик удаляет эти объекты из памяти. При этом он оптимизирует состояние управляемой кучи и нужным образом корректирует указатели.
База данных без BDE
Сергей Кривошеев
Издательский Дом "КОМИЗДАТ"
Что есть жизнь Delphi-разработчика без Borland Database Engine aka BDE?
Полная зависимость от прихотей этого мощного, но при этом не лишенного недостатков механизма связи с базами данных с самого начала нравилась далеко не всем. Тем не менее, до последнего времени BDE была одним из наиболее распространенных механизмов доступа к данным из приложений, разработанных с использованием систем программирования от Borland.
Впрочем, альтернативы BDE существовали всегда. Многие программисты, работающие на Delphi, пошли своим путем. Так начали появляться всевозможные компоненты для работы с различными базами данных - настольными и серверными СУБД, текстовыми файлами, временными таблицами в памяти и т.п. Кроме того, в самой системе разработки Borland Delphi существуют альтернативы BDE, которые, однако, выбора не упрощают. На сегодня на палитре компонент Delphi 7.0 присутствуют следующие закладки, относящиеся к механизмам доступа к данным: dbExpress, BDE, ADO и Interbase.
![](image/bde1.jpg)
Из числа перечисленных механизмов фирма Borland наиболее активно продвигает сегодня технологию dbExpress - не в последнюю очередь по той причине, что она предоставляет кроссплатформенную поддержку как для Delphi и C++ Builder под Windows, так и для Kylix под Linux. Следует учитывать также и тот факт, что с целью повышения скорости доступа к данным dbExpress переписана практически "с нуля". Однако на сегодняшний день вряд ли можно считать dbExpress безоговорочной преемницей BDE.
Delphi 7 Enterprise поставляется с драйверами dbExpress для работы с DB2, Informix, InterBase, MS SQL Server, MySQL и Oracle. Но для создания бесплатных приложений подходят только MySQL и FireBird (InterBase-совместимая СУБД, распространяемая по лицензии Open Source). Поэтому мы рассмотрим использование dbExpress для работы с СУБД MySQL.
Библиотека классов и пространства имен
Замечательной особенностью новой технологии является ее объектная ориентация. Вместе с CLR в рамках программного продукта .NET Framework (этот продукт свободно доступен на сайте http://www.microsoft.com/) поставляется обширная библиотека классов, которую должна использовать новая операционная система с рабочим названием Longhorn (выпуск намечен на 2005-2006 гг.) и все работающие под ее управлением программы.
Классы в .NET Framework организованы в ветвящиеся иерархические структуры, которые называются пространствами имен. До технологии .NET практически все промышленные операционные системы (ОС) строились из крошечных "кирпичиков", которые назывались функциями API (Application Program Interface - интерфейс прикладных программ). Эти функции выполняли небольшие локальные задачи как для нужд самой ОС, так и для любой работающей под ее управлением программы. В системах с .NET Framework роль функций API играют объекты (экземпляры классов).
DbExpress и MySQL
Какие возможности связи со структурами данных из Delphi-проекта с помощью dbExpress существуют?
Взглянув на список возможных значений свойства Connection компонента TSQLConnection, мы обнаружим там значение MySQLConnection. Кроме этого необходимо указать значение для свойства VendorLib - libMySQL.dll. Для корректной работы приложения нужно обеспечить обнаружение данного файла нашей программой. Для чего следует либо скопировать библиотеку в один из каталогов, где приложение будет искать файлы (например, WinNT\System32), либо внести в список таких каталогов c:\mysql\bin. Конечно, можно жестко задать путь к файлу библиотеки (MySQLConnection.VendorLib:= 'c:\mysql\bin\libmysql.dll') - но вряд ли это лучший вариант, хотя на этапе разработки и отладки сойдет.
Последним штрихом настройки компонента Connection является установка имени базы данных, с которой будет работать приложение. Не стоит забывать и о таких параметрах, как имя пользователя и пароль доступа к БД. В данном случае мы работаем под именем root, но в реальных проектах набор пользователей, естественно, будет иным.
![](image/bde2.jpg)
Для доступа к таблицам, входящим в состав созданной нами базы данных testdbExpress (пока что это единственная таблица customer), можно воспользоваться компонентами TSQLTable, TSQLDataSet и TSQLQuery.
Создадим в качестве примера небольшое приложение для просмотра и модификации данных. Поскольку компоненты DataSet, входящие в состав dbExpress, предоставляют только возможность чтения данных read-only, то для полноценного доступа к хранящейся в таблице информации потребуются компоненты TSQLTable, TDataSetProvider, TClientDataSet и TDataSource. Ниже приведен фрагмент.dfm-файла с описанием тех свойств компонентов, которые следует изменить:
object SQLTableCustomer: TSQLTable … SQLConnection = SQLConnection TableName = 'customer' … end object dspCustomer: TDataSetProvider DataSet = SQLTableCustomer … end object cdsCustomer: TClientDataSet … ProviderName = 'dspCustomer' … end object dsCustomer: TDataSource DataSet = cdsCustomer … end
Одним из основных отличий dbExpress от BDE является необходимость использования компонентов DataSetProvider и ClientDataSet для взаимодействия компонентов доступа к данным dbExpress с компонентом DataSource и средствами визуализации информации (закладка Data Controls). Поначалу эта особенность может сбивать с толку, однако скоро вы к ней привыкнете и перестанете считать это неудобством.
Для оформления формы просмотра и редактирования данных из таблицы customer можно воспользоваться стандартными компонентами Delphi, расположенными на закладке Data Controls панели компонентов. Однако не следует забывать, что для реальной записи в таблицу базы данных MySQL необходимо явно вызвать метод компонента ClientDataSet.ApplyUpdates. Это можно сделать при обработке событий OnAfterPost и OnAfterDelete компонента cdsCustomer - а можно и по факту нажатия некоей кнопки (например, "Сохранить изменения"). Кроме того, обработчик события запроса на закрытие формы OnCloseQuery может проверять свойство cdsCustomer.ChangeCount на равенство нулю. При отрицательном результате приложению следует уточнять у пользователя, имеет ли он в виду завершение работы без сохранения изменений (что-нибудь типа: Application.MessageBox ('Сохранить изменения?', 'Внимание', mb_YESNOCANCEL or mb_ICONWARNING)). Ниже приводится пример применения ApllyUpdates:
procedure TMainForm.cdsCustomerAfterPostOrDelete (DataSet: TDataSet); begin (DataSet as TClientDataSet).ApplyUpdates (0) end; procedure TMainForm.FormCloseQuery (Sender: TObject; var CanClose: Boolean); var AnswId:Integer; begin CanClose:= True; if cdsCustomer.ChangeCount > 0 then begin AnswId:= Application.MessageBox ('Сохранить изменения?', 'Внимание', mb_YESNOCANCEL or mb_ICONWARNING); if AnswId = ID_CANCEL then CanClose:= False else if AnswId = ID_YES then cdsCustomer.ApplyUpdates (0); end; end;
Обратите внимание: такой подход позволяет без дополнительных усилий предоставить пользователю возможность принять решение о необходимости сохранения на сервере изменений, внесенных в БД. При использовании BDE это также возможно, однако требует намного больше усилий. В случае же с dbExpress для отмены изменений, внесенных в процессе работы, достаточно применить RevertRecord (для текущей записи) или метод ClientDataSet.UndoLastChange (для всей несохраненной информации). Правда, следует помнить, что при таком положении дел нельзя использовать метод ApplyUpdates в обработчике события OnAfterPost - это вызовет очистку буфера несохраненных изменений и запись данных в таблицу БД.
Delphi 6: первое знакомство
В. Ковалев,
Ну вот наконец-то, в руках коробочка с диском и бумажкой, на которой кто-то старательно вывел фломастером Delphi 6. Из коробочки вынимается диск, вставляется в cd-rom и вот оно: чудо священнодействия начинается!
Нет, конечно, я не отношусь к разряду тех людей, для которых установка новой программы обрастает ритуальными плясками. Порой случается и так, что за день с компьютера навечно стираются две-три программы, а еще больше обретают свой дом - правда не известно на какой период времени. Такова уж судьба технического писателя - приходится бороться с программами не на жизнь, а на смерть.
Но тут совсем другой случай. Все-таки Delphi. Любимая всеми (или почти всеми) компания Borland вернула себе свое имя и представила на суд новое творение. Испокон веков повелось, что российские разработчики неравнодушны к "делфям". Уж не знаю, каким чудным образом это произошло, но это так. Конечно, находятся сторонники и других RAD-средств программирования, но Delphi пока надежно удерживает пальму первенства на пост-советском пространстве.
Ну ладно, не будем долго расстилаться маслом по бутерброду, перейдем к первым (и естественно наиболее ярким) впечатлениям от Delphi 6. Тут нелишни будет напомнить, в каких собственно вариантах поставки, распространяется сей продукт. Их три: Delphi 6 Personal, Delphi 6 Professional и Delphi 6 Enterprise. Естественно, "навороченность" увеличивается от пакета к пакету. Разумеется, "нормальный русский программист" спешит поставить себе Delphi 6 Enterprise. Я не исключение. Поэтому все, что будет сказано далее, имеет право на достоверность именно для этой версии.
На что я обратил внимание сразу же. На то, что Delphi теперь использует при инсталляции стандартный инсталлятор от Microsoft, отчего сам процесс установки теперь совершенно не отличим от установки Office 2000.
![](image/delphi-6-1.gif)
Те же самые возможности по установки на диск или загрузки компонентов продукта с cd-rom-а по мере необходимости. Стандарт Microsoft процветает.
Не долго думая, я выбрал все имеющиеся компоненты и стал смотреть как они слоями укладываются на жесткий диск. Тут не грех вспомнить и о минимальной конфигурации. Borland настаивает на следующем раскладе:
Intel Pentium 166 MHz (рекомендуется P2 400)
Microsoft Windows Me, 2000, 98 или NT 4.0 с установленным Service Pack 5+
64Mb RAM (рекомендуется 128Mb)
115Mb жесткого диска (compact install), 350Mb жесткого диска (full install)
Естественно лучше - больше, но если и будет чуть меньше - не велика беда. Если вы, конечно, не заняты сборкой достаточно большого проекта. А то придется курить перед компьютером. А курение - сами знаете - вредит здоровью и влияет на интеллект (вроде как).
Что же нового я увидел по сравнению с Delphi 5? Первым делом, конечно, полез в компоненты, что бы оценить насколько близко приблизилась Delphi к так называемой интеграции с интернетом и всего прочего, о чем так долго говорили. Шаг сделан достаточно внушительный (будем надеяться, что в нужном направление) и обеспечит не один месяц почесывания головы и разбора нововведений.
Итак, что же нового. Добавилось несколько новых, да некоторые из имеющихся в предыдущей версии "обросли" парой-другой компонентов. Например, на закладке Additional появились ValueListEditor, ColorBox, ActionManager, ActionMainMenuBar, ActionToolBar и CustomizeDlg. Следует отметить, что больше всего "пострадали" компоненты, связанные с базами данных и интернетом. Появились наборы WebServices, WebSnap, компоненты Indy, для работы с BDE выделен отдельный набор компонентов.
И так повсеместно. Borland постаралось, что бы при работе с Delphi не было необходимости навешивать среду разработки компонентами от третьих лиц. Вряд ли это будет возможно, но стремление безусловно хорошее.
Интерфейсных нововведений практически нет. Разве что, кроме кода проекта можно теперь на закладке Diagram попробовать себя в качестве художника.
![](image/delphi-6-2.gif)
Здесь можно выстроить алгоритм программы. Компоненты с Object TreeView переносятся в окно диаграммы, а здесь лишь остается указать необходимые связи.
Еще из приятного. В Delphi 6 используется средства Borland Translation Suite, позволяющего быстро переводить на различные языки сообщения и текстовые элементы интерфейса приложений. Очень удобно. Говорить о том, что на уровне кода, все, что сделано в Delphi 6 совместимо с Kylix-ом и не стоит. Знают все.
Конечно, рассказать о всех нововведениях в Delphi 6 мне не удастся. Для этого нужно писать целую книгу. И напоследок. После установки Delphi 6, имеющаяся на компьютере Delphi 5 не подала пока никаких признаков для беспокойства - достаточно крепко стоит на ногах и падать вроде как не собирается. Что, между прочим, добавляет еще одну ложку меда. О ложках дегтя - немного позже.
В. Ковалев / ©
Домены и сборки
Перед запуском приложения вы должны загрузить сборку в домен. Запуск типичного приложения вызывает загрузку различных сборок в программный домен. По умолчанию, CLR загружает сборку в домен, который содержит ссылку на нее. Таким образом, код и данные сборки изолируются от использующего его приложения.
Если одна и та же сборка используется несколькими доменами, ее код (но не данные) могут разделяться доменами. Это уменьшает затраты памяти. Этот метод подобен разделению DLL. Сборка называется доменно-нейтральной, если ее код может разделяться другими доменами в одном процессе. CLR решает, будет ли сборка доменно-нейтральной.
Сборка не разделяется между доменами, если предоставляемые ею возможности нужны лишь одному домену.
Домены приложений
Исторически сложилось так, что параллельно запускаемые на одном компьютере программы всемерно изолированы друг от друга.
Приложения изолируются прежде всего из-за того, что адресные указатели зависят от процесса. Указатель, переданный из одного приложения другому, никак не может использоваться в нем. Более того, вы не можете из одного процесса обратиться непосредственно к другому. Вместо этого вы должны использовать механизм представителей (proxy), которые реализуют косвенные вызовы, как это делается в СОМ (Component Object Model - компонентная модель объектов).
Домены приложений репализуют безопасный процесс, который позволяет CLR изолировать приложения. Вы можете запускать различные домены в рамках единственного процесса с уровнем изоляции, который существует между отдельными процессами, и не заботиться о перекрестных обращениях и переключениях. Возможность запуска множества приложений в едином процессе существенно увеличивает масштабируемость серверов.
Изоляция приложений важна также для безопасности программ. Например, вы можете запускать управления из Web-приложения в единственном процессе браузера так, что управления не будут иметь доступ к другим данным и ресурсам.
Изоляция, обеспечиваемая доменами приложений, имеет следующие преимущества.
Ошибка в одном приложении не может повлиять на другое приложение. Неправильное приложение может быть выгружено без выгрузки других приложений.
Замечание:
Нельзя выгрузить сборку или тип, но можно - домен.
Код, исполняемый в одном приложении, не имеет непосредственного доступа к коду или ресурсу другого приложения. CLR обеспечивает изоляцию путем предотвращения вызовов между объектами в разных доменах приложений. Объекты, которыми обмениваются домены, могут быть копиями или полученными через представителей. Если объект - копия, его вызов локальный. Это означает, что как получатель объекта, так и сам объект находятся в одном домене. Если объект получен через представителя, этот объект - удаленный. В этом случае получатель и объект находятся в разных доменах. Как следствие, доступ к метаданным объекта должны иметь оба домена, чтобы обеспечить правильную работу встроенному компилятору для вызова методов (в противном случае - ошибка).
Две модели Windows-приложений
Несмотря на то, что Delphi теперь всего лишь один из языков, поддерживающих .NET, сама система программирования Delphi имеет богатую историю, и миллионы программистов до сих пор с удовольствием работают с ней. Учитывая это, разработчики Delphi обеспечили максимально возможную совместимость (пусть - мнимую, см. выше) новейшей версии с предыдущими.
С этой целью они создали пространства имен Borland.VCL.XXXX, почти полностью имитирующие библиотеку компонентов VCL (Visual Component Library - библиотека визуальных компонентов) предыдущих версий. В эти пространства имен вошли VCL-классы, хорошо известные по предыдущим версиям - TApplication, TForm, TButton, TCheckBox и т. д. Символы ХХХХ в названиях пространств имен совпадают с именами соответствующих модулей VCL. Например, пространство имен Borland.VCL.DB содержит классы, определенные в модуле DB библиотеки VCL (клас-сы для работы с базами данных). Класс TForm определен в пространстве имен Borland.VCL.Forms, в этом же пространстве определен класс TApplication, класс TButton - в пространстве имен Borland.VCL.StdCtrls, в этом пространстве объявлен также класс TCheckBox и т. д.
Пространства имен Borland.VCL.XXXX являются прозрачными надстройками над пространствами имен классов, входящих в .NET Framework, но не закрывают их. Это означает, что вам доступны и классы .NET Framework. Чтобы создать Windows-приложение, базирующееся на классах .NET Framework, вы выбираете команду меню File > New > Windows Forms Application, для создания VCL-подобного приложения - команду File > New > VCL Forms Application.
FWS-компоненты для работы с базами данных без использования BDE
При всей своей эффективности технология dbExpress - далеко не универсальное средство, годящееся на все случаи жизни. В некоторых случаях использование стандартных решений от фирмы Borland может быть связано с рядом неудобств. Это обстоятельство является одним из основных побудительных мотивов движения FWS (FreeWare With Source, бесплатное ПО с открытым кодом). В интернете то и дело встречаются оригинальные и, главное, применимые на практике разработки. О двух из них мне и хотелось бы рассказать в рамках этого лирического отступления.
Исполнение кода
CLR обеспечивает инфраструктуру, которая позволяет управлять процессом исполнения машинного кода, а также предоставляет различные службы, которые могут быть использованы во время исполнения. Перед вызовом метода он должен быть скомпилирован в машинные инструкции. Каждый метод, для которого есть CIL-код, должен вначале с помощью JIT-компилятора генерироваться в машинный и затем выполняться. Каждый следующий раз компилятор не вызывается, но используется созданный им код. Этот процесс повторяется до конца прогона.
Во время выполнения управляемый код получает дополнительное обслуживание, такое как сборка мусора, повышенная защита, взаимодействие с неуправляемым кодом, поддержка межъязыковой отладки , улучшение распространения программ и версионного контроля.
Компилирование CIL в машинные инструкции
Перед выполнением кода CIL он должен быть преобразован с помощью JIT-компилятора в машинные инструкции. В процессе компиляции JIT-компилятор подсчитывает также те участки кода, которые могут никогда не вызываться. Компилятор преобразует CIL по мере надобности и вставляет заглушки на место вызова любого метода. При первом же вызове кода заглушка передается JIT-компилятору, который заменяет заглушку реальным кодом. Такого рода обращения к компилятору производятся непосредственно из реальных машинных инструкций, ранее сгенерированных компилятором, что повышает скорость исполнения программ.
В ходе компиляции CIL-кода он передается верификационному процессу. Верификация проверяет код CIL и метаданные в поисках выхода из надежного кода. Надежность типов объектов есть надежность их изолирования от других объектов и надежность защиты их от ошибочного иди злонамеренного разрушения.
Во время верификации код проверяется на доступ к разрешенной памяти и вызов только правильно определенных методов. Например, не допускается обращение к полям, которые выходят за отведенные им границы. Дополнительно верификация проверяет правильность генерации машинного кода. Процесс верификации открывает доступ к правильно определенному надежному коду. Если встретился ненадежный код, возбуждается исключение.
Компилирование в промежуточный язык CIL
При создании управляемого кода компилятор языка программирования, поддерживающего .NET (Visual Basic .NET, C#, J#. а с появлением Delphi 8 еще и Delphi4 ) транслирует исходный код в набор машинно-независимых инструкций языка CIL.
Замечание:
Синтаксис того или иного языка никак не влияет на CLR. Однако некоторые языки (C#, Delphi), не имеют существенных синтаксических ограничений и позволяют использовать практически все возможности CLR.
Эти инструкции могут затем легко переводиться в машинно-зависимые. CIL включает инструкции для загрузки, сохранения, инициализации объектов, вызова их методов, а также для логических и арифметических операций, управления потоками, прямого доступа к памяти, поддержки исключений и др. Перед выполнением программы инструкции CIL преобразуются JIT-компилятором CLR в машинно-зависимые инструкции процессора.
Одновременно с инструкциями CIL производятся также метаданные. Метаданные описывают типы вашего кода, в том числе содержат описание каждого типа, сигнатуры вызова методов объектов, ссылки на членов вашего кода и другие необходимые при выполнении данные. MSIL и метаданные содержатся в выполняемом файле формата РЕ, который основан на расширенной версии опубликованного формата MS PE и общего объектного файлового формата, использующегося исторически для выполняемых программ. Этот файловый формат, который объединяет CIL-код и метаданные, предоставляет операционной системе компьютера исполнения всю необходимую информацию для создания объектов CLR. Присутствие в CIL-кодах метаданных позволяет коду описывать самого себя и, таким образом, отказаться от библиотек типов и языка IDL (Interface Definition Language - язык описания интерфейсов). CLR находит и извлекает метаданные из РЕ-файла по мере надобности в ходе прогона.
Ловим баги или Почему программы допускают "недопустимые операции"
Е. Левшаков, В. Ковалев,
Ошибки - неизбежное зло программирования. Видимо, пока трудно даже представить средство с помощью которого можно избавится от них. Человеку, который выдумает это чудодейственное лекарство, благодарные потомки-программисты, несомненно, воздвигнут памятник. Пока же остается лишь заниматься обычным делом: ловлей багов.
"Нарушение Доступа" - фраза, которую пользователи видят, когда приложение делает попытки обратиться к памяти, которая не обозначена для их использования - и как следствие происходит сбой в работе программы:
Access violation at address <HEX_value> in module <Application.Exe>. Read of address <HEX_value_2>
Ситуация, при которой Windows давала бы полную свободу программам - записывай данные куда хочешь, скорее всего бы привела к разноголосице программ и полной потери управления над компьютером. Но этого не происходит - Windows стоит на страже "границ памяти" и отслеживает недопустимые операции. Если сама она справиться с ними не в силах - происходит запуск утилиты Dr. Watson, которая записывает данные о возникшей ошибке, а сама программа закрывается.
Известно, что при программировании, особенно крупных программных продуктов, уследить за всеми процессами в коде невозможно, да и нет необходимости. Использование сторонних компонентов и библиотек только усложняет дело. Именно поэтому программисты Delphi порой и сталкиваются со "своенравными" программами, которые то и дело норовят "сбросить пользователя". Итак, давайте рассмотрим некоторые вопросы, связанные с корректной средой программирования, так и непосредственно проблемы написания кода, которые ведут к возникновению ошибок типа "ошибка доступа" (AVS) и очертим наиболее известные пути их исправления.
Мы можем поделить AVS, с которыми сталкиваются при разработке в Delphi, на два основных типах: ошибки при выполнения и некорректная разработка проекта, что вызывает ошибки при работе программы.
Ошибки возникают при старте и закрытии Delphi или формировании проекта. Причиной могут являться сбои в "железе" компьютера.
Эти ошибки могут быть вызваны различными источниками, включая систему BIOS, операционную систему или аппаратные подпрограммы драйверов. Некоторые видео-, звуковые или сетевые платы могут фактически вызывать подобного рода ошибки в Delphi. Для решения подобных аппаратных проблем можно предпринять последовательность неких "стандартных" ходов:
проверить, что не имеется никаких конфликтов между установленными устройствами, устранить обнаруженные конфликты; попробовать слегка уменьшить "аппетиты" видеодрайвера - поставить меньшее разрешение; в случае если у вас двухпроцесорная система обеспечить равное изменение шага для каждого процессора;
И в конце концов просто попытаться заменить драйвера на более свежие.
Но помимо чисто железных проблем - большую головную боль могут вызвать ошибки в работе программного обеспечения. Особенно это касается непосредственно операционной системы. Зачастую Windows терпит крах спонтанно. Вот рекомендации которые помогут вам создать более устойчивую среду программирования:
Хотя Windows 9X популярная система, разработку лучше проводить в Windows NT или Windows 2000 - это более устойчивые операционные системы. Естественно, при переходе на них придется отказаться от некоторых благ семейства Windows 95/98/Me - в частности, не все программы адаптированы для Windows NT/2000. Зато вы получите более надежную и стабильную систему.
Не забывайте о том, как важно всегда иметь под рукой свежие версии компонентов для Delphi и дополнительных библиотек. В отличие от Windows создатели данных пакетов стараются от версии к версии уменьшать количество ошибок.
Следите за тем, чтобы устанавливаемые компоненты были предназначены непосредственно для вашей версии Delphi. Попробуйте деинсталлировать чужеродные компоненты один за другим (или пакет за пакетом), пока проблема не будет устранена.
Контролируйте все программные продукты, установленные на вашей машине и деинсталлируйте те из них, которые сбоят. Фаворитами AV среди них являются шароварные утилиты и программы и бета версии программных продуктов.
Все вышеперечисленное в основном не касалось самого процесса программирования и в малой степени зависит от разработчика. Теперь же обратимся к теме, как не допустить при разработке программного продукта ситуации, при которой он сам будет являться причиной ошибки.
Вы могли бы рассмотреть компилирование вашего приложения с директивой {$D}, данная директива компилятора может создавать файлы карты (файлы с расширением map, которые можно найти в том же каталоге, что и файлы проекта), которые могут послужить большой справкой в локализации источника подобных ошибок. Для лучшего "контроля" за своим приложением компилируйте его с директивой {$D}. Таким образом, вы заставите Delphi генерировать информацию для отладки, которая может послужить подспорьем при выявление возникающих ошибок.
Следующая позиция в Project Options - Linker & Compiler позволяет вам, определить все для последующей отладки. Лучше всего, если помимо самого выполняемого кода будет доступна и отладочная информация - это поможет при поиске ошибок. Отладочная информация увеличивает размер файла и занимает дополнительную память при компилировании программ, но непосредственно на размер или быстродействие выполняемой программы не влияет. Включение опций отладочной информации и файла карты дают детальную информацию только если вы компилируете программу с директивой {$D+}.
Эта информация состоит из таблицы номеров строк для каждой процедуры, которая отображает адреса объектных кодов в номера строк исходного текста. Директива $D обычно используется совместно с другой директивой - $L, что позволяет или запрещает генерацию информации о локальных символах для отладки.
Таким образом вы без труда сможете найти точный адрес той подпрограммы, которая была ответственна за ошибку. Одна из наиболее общих причин ошибок выполнения - использование объекта, который еще не был создан. Если второй адрес при выдачи ошибки - FFFFFFF (или 0000000) Вы можете почти утверждать, что было обращение к объекту, который еще не был создан. Например, вызов метода формы, которая не была создана.
procedure TfrMain.OnCreate(Sender: TObject); var BadForm: TBadForm; begin BadForm.Refresh; // причина ошибки end;
Попытаемся разобратся в этой ситуации. Предположим, что BadForm есть в списке "Available forms" в окне Project Options|Forms. В этом списке находятся формы, которые должны быть созданы и уничтожены вручную. В коде выше происходит вызов метода Refresh формы BadForm, что вызывает нарушение доступа, так как форма еще не была создана, т.е. для объекта формы не было выделено памяти.
Если вы установите "Stop on Delphi Exceptions" в Language Exceptions tab в окне Debugger Options, возможно возникновение сообщения об ошибке, которое покажет, что произошло ошибка типа EACCESSVIOLATION. EACCESSVIOLATION - класс исключение для недопустимых ошибок доступа к памяти. Вы будете видеть это сообщение при разработке вашего приложения, т.е. при работе приложения, которое было запущено из среды Delphi.
Следующее окно сообщения будет видеть пользователь - и программа будет закрыта при совершение недопустимой операции:
Access violation at address 0043F193 in module 'Project1.exe' Read of address 000000.
Первое шестнадцатиричное число ('0043F193') - адрес ошибки во время выполнения программы. Выберите опцию меню 'Search|Find Error', введите адрес, в котором произошла ошибка ('0043F193') в диалоге и нажмите OK. Теперь Delphi перетранслирует ваш проект и покажет вам строку исходного текста, где произошла ошибка во время выполнения программы, то есть BadForm.Refresh.
Естественно, что списка наиболее общих причин ошибок, вызывающих аварийное завершение работы программы, написанной в Delphi, в чистом виде нет. Есть несколько общих "узких мест" в коде и структуре программы, когда подобная ошибка может произойти. Перечислим наиболее распространенные.
Недопустимый параметр API
Если вы пытаетесь передать недопустимый параметр в процедуру Win API, может произойти ошибка. Необходимо отслеживать все нововведения в API при выходе новых версий операционных систем и их обновлений.
Уничтожение исключения
Никогда не уничтожайте временный объект исключения. Обработка исключения автоматически уничтожает объект исключения. Если вы уничтожите объект самостоятельно, то приложение попытается уничтожать объект снова, и произойдет ошибка.
Zero:=0; try dummy:= 10 / Zero; except on E: EZeroDivide do MessageDlg('Can not divide by zero!', mtError, [mbOK], 0); E.free. // причина ошибки end;
Индексация пустой строки
Пустая строка не имеет никаких достоверных данных. Следовательно, попытка индексировать пустую строку - подобно попытке обратиться к нулю, что приведет также к ошибке:
var s: string; begin s:=''; s[1]:='a'; // причина ошибки end;
Обращение к динамической переменной
Вы должны строить обращение к динамической переменной корректно, иначе вы перемещаете адреса указателей и возможно разрушаете другие выделенные ячейки памяти.
procedure TForm1.Button1Click(Sender: TObject); var p1 : pointer; p2 : pointer; begin GetMem(p1, 128); GetMem(p2, 128); {эта строка может быть причиной ошибки} Move(p1, p2, 128); {данная строка корректна } Move(p1^, p2^, 128); FreeMem(p1, 128); FreeMem(p2, 128); end;
Перечисленные подходы позволят избежать наиболее частых недочетов в разработке, которые могут вызвать столь неприятное как для пользователя, так и для разработчика сообщение о том, что программа выполнила "недопустимую операцию".
Удачной вам ловли багов, господа! Е. Левшаков, В. Ковалев / ©
Использованы материалы
наследник TDataSet, предназначенный для доступа
Этот компонент - наследник TDataSet, предназначенный для доступа к DBF-файлам без использования BDE. Демонстрационный проект радует глаз. Как говорится, простенько и со вкусом. Огорчает отсутствие файла помощи, нет даже комментариев в исходных текстах проекта. Но ведь сами-то тексты есть! Поэтому я все-таки решил исследовать данный компонент поглубже.
Среди немногих свойств компонента TMDBFTable, доступных в режиме design-time, следует выделить MakeBackup, PackOnSave и ShowDeleted. Даже не искушенный в английском языке читатель без труда определит, какие функции они выполняют.
Увы, как показало тестирование, DBFTable не подходит для работы с таблицами, где число записей превышает 100 тыс. Последовательных приближений к тому количеству записей, при работе с которыми демо-приложение не "вываливается" с удручающим сообщением "Out of memory", я не делал - однако таблицу на 15 тыс. оно восприняло вполне спокойно. А все потому, что для загрузки информации из таблиц используется обычный TStringList.
Тут обнаружилось еще одно удручающее обстоятельство - вместо русских букв, на экране в качестве значений текстовых полей отображаются "крокозябры". Кроме того, DBFTable поддерживает отнюдь не любые dbf-файлы, как можно было бы предположить из его названия, а только с DBase-III или IV. Умиляют также закомментированные строки кода в описании компонента.
Последнее, что я занесу в "пассив" этой разработки: компонент не лишен "глюков", возникающих в design-time и в ходе работы приложений. Критичными их не назовешь, но помнить о них следует. Подозреваю, что кроме парочки выловленных мною есть и другие.
Все же использовать MiTeC DBFTable v.1.5 в реальных проектах) можно. Во-первых, DBFTable (как и предыдущий кандидат на звание альтернативы BDE) может послужить базой для дальнейших разработок. Во-вторых, начинающим программистам очень полезно будет изучить исходные тексты компонента. Уверен, они почерпнут оттуда много интересного. В-третьих, DBFTable, как-никак, рабочий компонент и вполне может подойти для определенного класса задач - например, для создания на диске пользователя временных файлов данных, для описания настроек приложения и т.п. А проблему с отображением русских букв вполне можно решить, внеся изменения в исходный код DBFTable.
И все-таки очень жаль, что нет файла помощи!
Компонент:
TjanSQL v.1.1 MiTeC |
DBFTable v.1.5 |
Разработчик |
Jan Verhoeven ( ) |
MichaL MutL ( ) |
Краткое описание |
однопользовательская реляционная СУБД с поддержкой подмножества SQL для работы с плоскими текстовыми файлами |
обеспечивает доступ к файлам формата DBASE III, IV |
Версии Delphi |
D4, D5, D6, D7 |
D3, D4, D5, D6, D7 |
Адрес архива |
http://www.torry.net/ db/ direct/ custom/ jansql.zip |
http://www.torry.net/ db/ direct/ db_dbf/ mitecdbftable.zip |
Сайт поддержки |
http://jansfreeware.com/ |
http://www.mitec.cz/ |
Объем архива |
425 Кб |
274 Кб |
Демонстрационные проекты |
+ |
+ |
Справочная система |
+ |
- |
Проблемы с установкой |
для обеспечения работоспособности демо-проекта пришлось править его исходный код |
нет |
Общее впечатление |
материал для собственных разработок; использовать непосредственно в проектах проблематично |
приятный на вид компонент с не вполне понятной (по причине отсутствия вразумительного описания) функциональностью |
MySQL: краткая справка
MySQL представляет собой сейчас одну из наиболее распространенных СУБД с открытым кодом, она регламентируется лицензией GPL (GNU General Public License, http://www.gnu.org/ licenses/ licenses.html). В некоммерческих проектах MySQL можно использовать бесплатно. Однако при встраивании кода MySQL в коммерческие приложения следует приобрести коммерческую лицензионную версию.
Первоначально MySQL разрабатывалась программистами-энтузиастами с целью усовершенствовать возможности использовавшегося в те время сервера mSQL. В дальнейшем компания MySQL AB, созданная для поддержки продукта, стала предоставлять широкую техническую поддержку пользователям MySQL - за счет чего эта компания существует и по сей день. На сайте MySQL AB, помимо исходных кодов и скомпилированных под различные платформы модулей самого сервера, выложено множество утилит, облегчающих жизнь разработчикам и администраторам этой СУБД.
MySQL обычно используется для решения не очень серьезных задач (ведение статистики, форумов и т.п.), однако существует опыт реализации на ее базе достаточно крупных проектов. MySQL доступна для работы в различных средах - как Windows, так и Linux. Она является приоритетным вариантом при выборе СУБД для хранения данных на большинстве веб-серверов. MySQL предоставляет многие возможности, характерные для реляционных СУБД, демонстрируя особенно высокую производительность при выполнении запросов на чтение данных. Кроме того, она поддерживает SQL, разработку клиент-серверных приложений и даже транзакции.
Новые возможности Delphi
Как уже отмечалось, записи не могут иметь вариантных частей, но могут - процедуры и функции. Это в какой-то степени сближает их с классами. Вот пример объявления:
type
MyRec = record
a: Integer; procedure aProc; end;
procedure MyRec.aProc; begin
end;
Несмотря на похожесть, записи, конечно, не классы - в них нет механизмов наследования и полиморфизма.
.NET позволяет интегрировать в единое целое код, написанный на разных языках, в которых используются, в общем случае, разные ключевые слова. Как быть, если в CTS определен класс, совпадающий с ключевым словом? В Delphi для этого можно использовать стандартный прием - составное имя. Например:
var
T: System.Type;
Однако "путь" к классу может быть достаточно длинным, и составное имя окажется громоздким. В этом случае Delphi разрешает перед именем класса ставить символ "&" (амперсанд):
var
T: &Type;
Встретив такое описание, компилятор станет просматривать список доступных модулей в поисках типа Type и найдет его в модуле System.
Существенным изменениям подверглось объявление класса. Дело в том, что Delphi и CLR по-разному трактуют области видимости секций private и protected: в Delphi члены, объявленные в этих секциях, видны всюду в пределах данного модуля. В CLR секция private объявляет члены, доступные только внутри методов класса, а секция protected - члены, доступные потомкам класса. В связи с этим, в Delphi перед названиями секций следует ставить спецификатор class - в этом случае области видимости в Delphi и CLR совпадут:
type
MyClass = class
class private
a: Integer; // Поле видно только в методах класса MyClass class protected
b: Boolean; // Поле видно только потомкам класса и //самому классу end;
Классы можно лишить потомков, а виртуальный метод - возможности перекрытия. Для этого объявление класса сопровождается директивой sealed, а объявление метода - директивой final:
type
NoInst = class
public
procedure NoOverride; dynamic; final; // Метод нельзя перекрыть end sealed; // Класс не может иметь потомков
Приведенный выше пример лишь иллюстрирует синтаксис объявлений и по существу бессмыслен: если класс не имеет потомков, то ни один его метод не может быть перекрыт. Замечу, что следующее объявление ошибочно:
procedure NoOverride; final;
Можно лишить возможности перекрывать только виртуальные методы, то есть объявленные с директивами dynamic, virtual или override.
Общеязыковая инфраструктура
Общеязыковая инфраструктура CLI (Common Language Infrastructure) - это набор перечисленных ниже спецификаций, определяющих различные аспекты технологии .NET.
Common Type System (CTS) - общая система типов. Определяет возможность взаимодействия программ и их частей, написанных на разных языках программирования. Каждый компилятор, вырабатывающий инструкции CLI, должен частично или полностью использовать CTS и никакие другие типы данных, кроме указанных в CTS. Набор перечисленных в CTS типов значительно превышает количество типов в реально существующих языках программирования. Common Intermediate Language (CLI) - общий промежуточный язык программирования 2. Это - язык инструкций абстрактного процессора. В этом отношении CLI - аналог байткода Java. Extensible Metadata - расширяемые метаданные. В технологии подразумевается, что инструкции CLI помещаются в единицу распространения - сборку (assembly) - и сопровождаются метаданными, которые делают сборку полностью самоописываемым объектом. В метаданные помещаются имя и версия сборки, сведения о локализации, данные о типах, включенных в сборку, список внешних файлов (сборок), от которых зависит данная сборка и т. п. Framework Class Library (сокращенно .NET Framework) - это библиотека классов, которые должна использовать любая программа в рамках технологии. Библиотека VCL Delphi в чем-то подобна .NET Framework. Разница между ними, прежде всего, состоит в том, что библиотеку .NET Framework можно использовать при создании программ на любом, поддерживающем технологию .NET, языке программирования. Более того, в Delphi (или в C#, J# и т. д.) вы можете расширять эту библиотеку новыми классами, которые затем могут использоваться в программах на других языках программирования. Platform Invocation Service (сокращенно P/Invoke) - служба согласования платформ. Программы, исполняемые в .NET, предельно изолированы друг от друга и от средств операционной системы. Однако вне этих средств .NET Framework не может реально работать. P/Invoke реализует взаимодействие .NET Framework и операционной системы. Extended Portable Executable (PE) File Format - стандартный формат исполняемых файлов в Win32, используемый для хранения объектов технологии. Он загружается обычным загрузчиком точно так же, как и любой другой исполняемый файл. Однако в его заголовке имеется бит, указывающий на то, что файл относится к технологии .NET. Обнаружив бит, загрузчик вызывает CLR, которая и производит обработку файла.
Общеязыковая среда исполнения
Общеязыковая среда исполнения (CLR) играет ключевую роль во всей технологии. Она поддерживает строгую систему правил и соглашений, которым должен следовать промежуточный язык. Этот язык представляет собой код, который называется управляемым. Важнейшим свойством CLR является то обстоятельство, что входной поток данных может представлять собой и неуправляемый код, а также управляемый и неуправляемый одновременно! Неуправляемые участки входных данных являются обычными машинными инструкциями, которые без изменений поступают в процессор. Указателем на то, что входной поток содержит управляемый код, является специальный бит в заголовке файла.
Управляемый код в общем случае порождает объекты с управляемым временем жизни. Такие объекты автоматически уничтожаются, когда надобность в них исчезает (например, завершает работу создавшая их программа). Таким образом, одной их замечательных способностей CLR является встроенная в нее борьба с утечкой памяти3.
Для создания объектов с управляемым временем жизни в управляемый код помещаются также метаданные, которые содержат подробные инструкции о порождаемых объектах, их свойствах, методах и событиях. Управляемый код перед запуском должен пройти процесс верификации (если только администратор не разрешил его пропустить). Процесс верификации определяет, будет ли код пытаться получить доступ к неправильным адресам памяти или производить другие неверные действия. Код, удовлетворяющий верификации, называется надежным (safe). Возможность верификации позволяет CLR обеспечивать высокий уровень изолированности объектов при минимальном снижении производительности. CLR использует метаданные, чтобы найти и загрузить классы, поместить их экземпляры в память, разрешить вызов программ, генерировать машинные инструкции, усиливать безопасность и устанавливать динамические контекстные границы.
CLR облегчает разработку компонентов и программ, объекты которых взаимодействуют с помощью языка. Объекты, написанные на разных языках, могут взаимодействовать друг с другом, и их поведение может быть тесно связанным. Например, вы можете определить класс и его потомка на разных языках или вызвать метод класса, написанного на другом языке. Межъязыковое взаимодействие возможно потому, что языковые компиляторы и инструменты целевой машины используют общую систему типов, определенную в CLI, и они следуют общим правилам для определения новых типов, а также их создания, использования, снабжения данными и связывания типов.
В отличие от обычных компиляторов,
1 JIT-компилятор - компилятор, вызываемый по мере надобности. В отличие от обычных компиляторов, JIT-компилятор может вызываться в процессе выполнения программы неоднократно. 2До стандартизации технологии в декабре 2001 г. назывался Microsoft Intermediate Language (промежуточный язык корпорации Microsoft). 3 Утечкой памяти обычно называют зарезервированную программой оперативную память, которая не освобождается после завершения работы программ. 4 В ранних версиях Delphi (6 и меньше) этот язык программирования назывался Object Pascal. 5 По распространенной версии свое название СП Delphi получила в честь древнегреческого города, прославившегося своим оракулом - дельфийский оракул.
6 Справедливости ради отмечу, что поддержка ASP введена в Delphi 5, 6 и 7.
document.write('');
|
![](/pictures/1x1.gif) |
![](/pictures/1x1.gif) |
|
|
![](/pictures/1x1.gif) |
![](/pictures/1x1.gif) |
Новости мира IT: |
02.08 - 02.08 - 02.08 - 02.08 - 02.08 - 01.08 - 01.08 - 01.08 - 01.08 - 01.08 - 01.08 - 01.08 - 01.08 - 01.08 - 01.08 - 31.07 - 31.07 - 31.07 - 31.07 - 31.07 -
Архив новостей
![](/pictures/1x1.gif) |
![](/pictures/1x1.gif) |
![](/pictures/1x1.gif) |
|
Последние комментарии:
(66)
2 Август, 17:53
(19)
2 Август, 17:51
(34)
2 Август, 15:40
(42)
2 Август, 15:35
(1)
2 Август, 14:54
(3)
2 Август, 14:34
(3)
2 Август, 14:15
(2)
2 Август, 13:34
(7)
2 Август, 13:04
(3)
2 Август, 12:28
![](/pictures/1x1.gif) |
![](/pictures/1x1.gif) |
![](/pictures/1x1.gif) |
BrainBoard.ru
Море работы для программистов, сисадминов, вебмастеров.
Иди и выбирай!
|
![](/pictures/1x1.gif) |
![](/pictures/1x1.gif) |
![](/pictures/1x1.gif) |
Loading
google.load('search', '1', {language : 'ru'}); google.setOnLoadCallback(function() { var customSearchControl = new google.search.CustomSearchControl('018117224161927867877:xbac02ystjy'); customSearchControl.setResultSetSize(google.search.Search.FILTERED_CSE_RESULTSET); customSearchControl.draw('cse'); }, true);
|
|
|
![](/pictures/1x1.gif) |
![](/pictures/1x1.gif) |
IT-консалтинг |
Software Engineering |
Программирование |
СУБД |
Безопасность |
Internet |
Сети |
Операционные системы |
Hardware |
| PR-акции, размещение рекламы — , тел. +7 495 6608306, ICQ 232284597
| Пресс-релизы —
|
|
|
![](http://top.list.ru/counter?id=26978;t=54;l=1) |
|
This Web server launched on February 24, 1997
Copyright © 1997-2000 CIT, © 2001-2009 |
Внимание! Любой из материалов, опубликованных на этом сервере, не может быть воспроизведен в какой бы то ни было форме и какими бы то ни было средствами без письменного разрешения владельцев авторских прав. |
Для Вас: с гибкой системой скидок.
|
Работа с базами данных
Отличительной особенностью системы программирования Delphi была и остается встроенная в нее возможность работы с различными промышленными базами данных (БД). Добрая треть компонентов VCL в Delphi 7 в той или иной степени связаны с созданием приложений для работы с БД.
В .NET Framework встроена архитектура ADO.NET, решающая аналогичные задачи. Упрощенная схема этой архитектуры показана на рис.1.1.
![](image/delphi8.gif)
Рис. 1.1. Архитектура ADO.NET
На этой схеме Источник данных - физическая БД или XML-файл с данными. Провайдер данных обеспечивает связь с Источником данных и передает ему команды. Набор данных предназначен для отображения данных. С любым источником данных (ИД) могут быть связаны один или несколько наборов данных (НД) и наоборот - единственный НД может отображать данные из нескольких ИД.
Провайдер данных, входящий в архитектуру ADO.NET, обеспечивает взаимодействие наборов данных с такими ИД, как MS SQL Server, OLE DB и Oracle. В Delphi 8 можно использовать также провайдер BDP.NET (Borland Database Provider for .NET - провайдер баз данных корпорации Borland для .NET), который обеспечивает взаимодействие с серверами DB2, Oracle и InterBase. Дублирование в BDP.NET связи с промышленным сервером Oracle не случайно: корпорации Borland и Oracle связывает многолетнее плодотворное сотрудничество5. По оценкам разработчиков Delphi, управление сервером Oracle с помощью BDP.NET дает определенные преимущества по сравнению с управлением через провайдер ADO.NET.
Наличие BDP.NET не ограничивает новаторского подхода разработчиков Delphi к интеграции с технологией .NET Framework. Связано это с тем, что изначально Delphi тяготела к приложениям для работы с БД в значительно большей степени, чем разработанная в Microsoft технология ADO (Active Data Object - активный объект данных), которая и легла в основу ADO.NET. Delphi 7, например, поддерживала такие технологии, как BDE (Borland Database Engine - машина баз данных корпорации Borland; обеспечивала доступ к файл-серверным БД на таблицах Paradox, dBASE и т. п.), dbExpress (набор скоростных драйверов для непосредственного доступа к некоторым промышленным серверам, в том числе - к MySQL), IBX (доступ к серверу InterBase на уровне его API-функций), DataSnap (разработка многозвенных СУБД) и ряд других.
Delphi 8 определяет естественную миграцию этих технологий в новую среду. Для этого в ее состав включены такие дополнительные технологии:
TADONETConnector - универсальный класс (наследник TDataSet), который разработчики рекомендуют как наиболее простой и эффективный путь миграции существующих приложений с НД (например, TADODataSet); dbExpress.NET - миграция технологии dbExpress; DataSnap .NET Client - поддержка многозвенных СУБД; IBX.NET - миграция технологии IBX; BDE.NET - миграция технологии BDE.
Поддержка этих технологий доступна только в режиме разработки VCL Forms Application.
Работа с директориями (папками) в Дельфи
, ()
В этой статье я постараюсь познакомить Вас с некоторыми стандартными функциями для работы с директориями. И еще приведу несколько пользовательских функций и примеры их использования. Также рассмотрен вопрос вызова диалога выбора директории.
Для начала начнем с простой функции для создания новой папки. Общий вид функции такой:
function CreateDir(const Dir: string): Boolean;
То есть если папка успешно создана функция возвращает true. Сразу же простой пример ее использования:
procedure TForm1.Button1Click(Sender: TObject);
begin
if createdir('c:\TestDir') = true then
showmessage('Директория успешно создана')
else
showmessage('При создании директории произошла ошибка');
end;
При нажатии на кнопку программа пытается создать папку с именем TestDir на диске C: и если попытка увенчалась успехом, то выводится соответствующее сообщение. Следует отметить, что если вы не указываете имя диска, на котором хотите создавать папку, то функция будет создавать папку в той же директории, где находится сама программа.
Объявления
createdir(edit1.text);
и
createdir(extractfilepath(paramstr(0))+edit1.text);
приведут к одному и тому же результату.
Теперь рассмотрим функцию для удаления папок. Ее объявление выглядит так:
function RemoveDir(const Dir: string): Boolean;
Сразу же хочу предупредить, что данная функция способна удалять только пустые папки, и если там что-нибудь будет, то произойдет ошибка! Но выход есть!!! Здесь нам на помощь придет пользовательская функция с простым названием MyRemoveDir. Вот описание функции:
Function MyRemoveDir(sDir : String) : Boolean;
var
iIndex : Integer;
SearchRec : TSearchRec;
sFileName : String;
begin
Result := False;
sDir := sDir + '\*.*';
iIndex := FindFirst(sDir, faAnyFile, SearchRec);
while iIndex = 0 do begin
sFileName := ExtractFileDir(sDir)+'\'+SearchRec.Name;
if SearchRec.Attr = faDirectory then begin
if (SearchRec.Name <> '' ) and
(SearchRec.Name <> '.') and
(SearchRec.Name <> '..') then
MyRemoveDir(sFileName);
end else begin
if SearchRec.Attr <> faArchive then
FileSetAttr(sFileName, faArchive);
if NOT DeleteFile(sFileName) then
ShowMessage('Could NOT delete ' + sFileName);
end;
iIndex := FindNext(SearchRec);
end;
FindClose(SearchRec);
RemoveDir(ExtractFileDir(sDir));
Result := True
end;
Копируете это все в Вашу программу, а затем эту функцию можно вызвать например так:
if NOT MyRemoveDir('C:\TestDir') then
ShowMessage('Не могу удалить эту директорию');
Теперь маленько отстранимся от непосредственной работы с папками и рассмотрим волнующий многих вопрос. Как вызвать диалог выбора папки (как при установке программ)?? ПРОСТО!!!
Подключаем в uses модуль Filectrl.pas (то есть uses FileCtrl;). Теперь ставим на форму еще кнопочку (чтобы не путаться :) и пишем такой код:
procedure TForm1.Button3Click(Sender: TObject);
const
SELDIRHELP = 1000;
var
Dir: string;
begin
Dir := 'C:\windows';
if SelectDirectory(Dir, [sdAllowCreate, sdPerformCreate, sdPrompt],SELDIRHELP) then
Caption := Dir;
end;
При выборе директории в заголовке формы отобразиться ее название!
Теперь рассмотрим следующую процедуру. К примеру Вам надо создать папку Dir1 по адресу: C:\MyDir\Test\Dir1, но при этом папок MyDir и Test на Вашем компьютере не существует. Функция CreateDir здесь не сработает, поэтому воспользуемся процедурой ForceDirectories. Ее общий вид таков:
procedure ForceDirectories(Dir: string);
Пример ее использования (как всегда я поставил на форму новую кнопку, а там написал)
procedure TForm1.Button4Click(Sender: TObject);
var
Dir: string;
begin
Dir := 'C:\MyDir\Test\Dir1';
ForceDirectories(Dir);
end;
Ну и напоследок приведу функцию для проверки: существует ли директория или нет. Ее общий вид такой:
function DirectoryExists(Name: string): Boolean;
Если директория указанная в параметре Name существует - то функция возвратит true.
Надеюсь, что помог Вам описанием данных функций и процедур. Сразу хочется дать совет: почаще заглядывайте в HELP, там много интересной и полезной информации!
(С) Автор статьи: // (). При использовании этого материала ссылка на автора и источник информации обязательна!!!
Удачи в программировании...
Работа с Интернет
Работа с Интернет никогда не была в Delphi столь же эффективной, как работа с БД. И хотя уже в Delphi 2 была вкладка Internet (компоненты FTP, HTML, POP и т. д.) поддержке Интернет в Delphi всегда не хватало некоторой системности. Даже многочисленные компоненты Indy в Delphi 7 годятся лишь на то, чтобы создать "самопальный" Outlook Express или скромный Web-браузер. В то же время в Microsoft разработали технологию ASP (Active Server Pages - активные страницы сервера), которая во многом упрощала актуальную ныне задачу создания интерактивных Web-сайтов (например, для электронной торговли товарами и услугами)6 . Технология ASP вошла в .NET Framework в виде ASP.NET и в полной мере доступна в Delphi 8. Для использования технологии ASP.NET на хостинге (то есть на машине, на которой развернут сайт) должен функционировать сервер Microsoft IIS (Internet Information Server - информационный сервер для Интернет корпорации Microsoft), а сам хостинг работать под управлением Windows 2000/ХР.
Основой технологии являются компоненты Web-страниц, на которых размещаются серверные управляющие компоненты и HTML-текст. Страницы содержат внутренний код, обеспечивающий логику работы, и поддерживаются скомпилированными DLL. Когда пользователь впервые обращается к активной странице, ASP.NET автоматически создает и компилирует DLL, которая в конечном счете передает пользователю HTML-код. Этот код способен исполняться в браузере клиента. В результате значительная часть работы по взаимодействию клиента с сервером выполняется на машине клиента, что повышает пропускную способность хостинга.
Активные страницы помимо HTML-текста могут содержать различные серверные элементы управления, например, обеспечивающие необходимую идентификацию пользователя, а также специализированные компоненты, такие как календарь, решетка с данными, навигатор, списки и т. п.
Внутри .NET Framework активные страницы получают доступ к данным через ADO.NET c "родным" провайдером или BDP.NET.
Сборки
Сборки - фундаментальные части программирования с .NET Framework. Сборка выполняет перечисленные ниже функции.
Она содержит код, который выполняет CLR. СIL-код в файле формата РЕ не будет исполняться, если не имеет связанного с ним манифеста сборки. Учтите, что сборка может иметь только одну точку входа. Она формирует границы защиты. Сборка есть блок, в котором запрашиваются и выдаются разрешения. Она формирует границы типа. Каждый идентификатор типа включает имя сборки, в которой он расположен. Тип MyType, загруженный в пределах одной сборки, в общем случае может отличаться от одноименного, но загруженного в другую сборку. Она определяет границы видимости ссылок. Сборочный манифест содержит метаданные, которые используются для разрешения ссылок и удовлетворения ресурсных требований. Манифест указывает экспортируемые типы и ресурсы и перечисляет другие сборки, от которых зависит данная сборка. Она определяет границы версионного контроля. Сборка представляет собой минимальный блок в CLR, все типы и ресурсы которого имеют ту же версию, что и версия блока. Она определяет единицу распространения. В момент старта должны быть загружены только первоначально вызванные сборки. Другие сборки, такие как ресурсы локализации или сборки, содержащие вспомогательные классы, могут загружаться по требованию. Это упрощает приложения и делает их меньше, что облегчает загрузку из Интернета. Она является единицей, для которой поддерживается параллельное выполнение (side-by-side, см. ниже).
Если вы уже имели опыт работы с Delphi или Turbo Pascal, то заметите, что многие свойства сборок соответствуют свойствам модулей этих систем программирования.
Сборки могут быть статическими и динамическими. Статические сборки включают типы .NET Framework (интерфейсы и классы), а также нужные ресурсы. Статические сборки сохраняются на диске в виде РЕ-файлов. Вы можете использовать .NET Framework для создания динамических сборок, которые не сохраняются на диске и создаются (и запускаются) непосредственно в памяти. После выполнения динамическую сборку можно сохранить на диске.
Сборки созданы для упрощения распространения программ и для решения проблем версионного контроля, которые возникают в приложениях, основанных на компонентах.
В платформах Win32 возникают две проблемы совместимости версии.
Невозможно выразить версионные правила между частями приложения и обеспечить их реализацию силами ОС. Текущий подход руководствуется правилом обратной совместимости, которое часто трудно гарантировать. Определения интерфейсов должны быть статическими, раз и навсегда опубликованными и фрагменты кода должны поддерживать обратную совместимость. Более того, код обычно разрабатывается так, что в каждый момент времени на данном компьютере может быть установлена и запущена единственная версия объекта или DLL. Нет путей согласования между наборами компонентов, которые собраны совместно, и набором, представленным в момент запуска.
Объединение этих двух проблем порождает конфликты DLL, когда инсталляция одного приложения может нарушить функциональность другого из-за того, что вновь установленный компонент или DLL не совместим с предыдущей версией. При возникновении такой ситуации у ОС нет средств обнаружения и устранения проблемы.
Windows 2000 (ХР) частично решает проблему с помощью двух приемов.
Windows 2000/ХР разрешает размещать вспомогательные DLL в ту же папку, что и исполняемый файл. Такие компоненты отыскиваются первыми и поэтому игнорируются другие версии. Windows 2000/ХР блокирует файлы, которые помещаются в системную папку System32 при установке ОС, и не позволяет другим приложениям замещать их. Для решения проблемы версионности сборки делают следующее.
Разрешают разработчику указывать версионные правила между различными компонентами. Реализуют инфраструктуру поддержки версионности. Реализуют параллельное (side-by-side) выполнение разных компонентов.
Параллельное выполнение объектов и приложений означает, что CLR дает приложению средства вызова той или иной версии DLL (объекта) для использования ее специфических возможностей. CLR разрешает параллельное исполнение произвольного количества разных версий одного объекта одновременно в рамках одной сборки.
Создание сводного отчета в Excel
Владимир Федченко,
В списке обсуждаемых тем на Круглом столе Королевства Delphi часто возникает вопрос о построении сводных таблиц. Сводная таблица представляет собой очень удобный инструмент для отображения и анализа данных, возвращаемых запросом к базе данных. Можно, конечно, для этой цели использовать различные пакеты для построения отчетов (вроде FastReport). Но с генераторами отчетов возникает масса вопросов (отсутствие каких либо библиотек, проблемы с экспортом, отсутствие необходимой документации и т.д.). А начальник требует выдать ему отчет приблизительно такого вида: чтобы были видны все продажи, по всем сотрудникам, по всем регионам, по всем товарам за указанный период времени (скажем, за два года), но денег на покупку генератора отчетов не дает. А как бы было хорошо выдать что-нибудь типа вот такой формы:
![](index_files/pivottable.png)
Что тут остается делать. Варианта только два: либо пытаться создавать что-то свое, либо увольняться. Альтернативное решение проблемы предоставлено фирмой Microsoft уже очень давно. Называется оно PivotTable (Сводная таблица) и доступно в меню "Данные" приложения Excel. Осталось только научиться пользоваться этой возможностью. Для этого нам понадобиться:
Delphi 7 (проект создан именно в этой версии);
Установленный M$ Excel;
Учебная база M$ Access Norhwind.mdb (прилагается в архиве);
Немного свободного времени;
Много желания понять как это делается.
Итак, начинаем. Существует два типа связи с Excel - раннее и позднее. Об их отличиях речь неоднократно шла на Королевстве. Будем использовать раннее связывание, т.к. при позднем компьютер впадает в состояние комы. О том как подключиться к Excel и добавить книгу подробно описано в материалах Королевства. Объявим следующие переменные:
WB:_WorkBook;//рабочая книга
WS:_WorkSheet;//лист Excel куда помещается сводная таблица PC:PivotCache;//кеш для данных сводной таблицы PT:PivotTable;//собственно сама сводная таблица i:byte;
Отключим реакцию Excel на события (для ускорения работы): XLS.EnableEvents:=False;
После предварительной подготовки создаем сводный отчет. Для этого необходимо создать кэш для хранения данных: PC:=WB.PivotCaches.Add(xlExternal,emptyparam)
Этот метод имеет два параметра SourceType и SourceData. Но так как мы используем внешние данные (SourceType = xlExternal), то второй параметр нужно оставить пустым. Кэш создан, но не подключен к источнику данных. Надо восполнить этот пробел. Укажем строку подключения, тип подключения и зададим сам запрос:
PC.Connection:=Format('OLEDB;Provider=Microsoft.Jet.OLEDB.4.0;Data Source=%snorthwind.mdb', [ExtractFilePath(ParamStr(0))]);
В строке подключения указываем, что база данных находится в одном каталоге с проектом. PC.CommandType:=xlCmdSQL; PC.CommandText:='select salesperson, country, city, productname,'+ 'orderdate, year(orderdate) as yy, month (orderdate) as mm, '+ 'quantity, extendedPrice from invoices';
Данные определены и находятся практически в боевой готовности. Попытаемся их визуализировать. Как говорилось выше, визуализировать будем в PivotTable. Для начала создадим сводную таблицу, привязав ее к кэшу с данными, и получим ссылку на интерфейс. Делается это все очень элегантно:
PT:=PC.CreatePivotTable(WS.Range['A3',emptyparam], 'PivotTable1',emptyparam,xlPivotTableVersionCurrent).
Три заданных параметра означают следующее: ячейка в которую поместим сводную таблицу, имя сводной таблицы и версия сводной таблицы (зависит от установленной версии M$ Office, в данном случае установлена текущая версия). Пустой параметр называется ReadData. Он указывает на то, читать ли в кэш все данные из внешнего источника (нам это не надо). Вот шаблон и готов. Но что такое шаблон без данных?
В сводной таблице существует несколько типов полей данных: поля колонок, поля строк, поля данных, поля страниц (в данной статье не рассматриваются).
Надо их разместить. Начнем с полей (колонок) таблицы. Тут стоит оговориться, что Excel имеет ограничения на количество полей на одном листе (255). Поскольку данные берутся из базы за период в три года, то количество полей будет существенно больше этого ограничения. Отсюда ясно, почему в запросе был выделен год и месяц. Наши данные будут группироваться сначала по году, затем - по месяцу, затем - по дате. Для того чтобы не возникло ошибки в связи в вышеуказанным ограничением будем прятать детализацию для каждого уровня группировки в цикле по всем полям детализации (кроме последнего, т.к. детализация по нему не предусмотрена):
with (PT.PivotFields('yy') as PivotField) do
begin Caption:='Год'; Orientation:=xlColumnField; for i:=1 to PivotItems(emptyparam).Count do PivotItems(i).ShowDetail:=False;
end;
with (PT.PivotFields('mm') as PivotField) do begin Caption:='Месяц'; Orientation:=xlColumnField; for i:=1 to PivotItems(emptyparam).Count do PivotItems(i).ShowDetail:=False;
end;
with (PT.PivotFields('orderdate') as PivotField) do begin Caption:='Дата'; Orientation:=xlColumnField;
end;
Аналогично заполним строки. В них ограничения составляют 65535 записей на лист. По этой причине можно не сворачивать детализацию:
with (PT.PivotFields('salesperson') as PivotField) do begin
Caption:='Сотрудник'; Orientation:=xlRowField; end;
with (PT.PivotFields('country') as PivotField) do begin
Caption:='Страна'; Orientation:=xlRowField; end;
with (PT.PivotFields('city') as PivotField) do begin
Caption:='Город'; Orientation:=xlRowField; end;
with (PT.PivotFields('productname') as PivotField) do begin
Caption:='Товар'; Orientation:=xlRowField; end;
Осталось поместить сами данные в отчет:
PT.AddDataField(PT.PivotFields('quantity'),'Кол-во',xlSum);
with PT.AddDataField(PT.PivotFields('extendedPrice'),'Продано на сумму',xlSum) do
begin //слегка отформатируем вывод суммы на экран if not XLS.UseSystemSeparators then NumberFormat:='#'+XLS.ThousandsSeparator+'##0'+XLS.DecimalSeparator+'00'
else NumberFormat:='#'+ThousandSeparator+'##0'+DecimalSeparator+'00'; end;
Ну и наконец, вернем к жизни сам Excel. PT.ManualUpdate:=True;
Вот, собственно, и все. Осталось нажать кнопочку F9, немного подождать и порадовать начальника новой формой отчета. Пусть сидит и забавляется. Стоит отметить, что данный отчет абсолютно независим от данных из БД, т.к. все, что вернул запрос, храниться в самой книге Excel. Отчет можно отправить по сети, по электронной почте или перенести любым доступным способом. Сворачивать/разворачивать детализацию по дате можно двойным кликом по данным колонки/строки (только не по серым кнопочкам с заголовками полей). Нажатие на заголовок поля приводит к появлению фильтра по данным выбранной колонки/строки. Ниже приведен код на C# (перевод с Delphi сделал Shabal, за что ему большое спасибо):
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using System.Threading; using System.Globalization; using Excel = Microsoft.Office.Interop.Excel;
namespace WinApp1 { public partial class Form1 : Form { public Form1() { InitializeComponent(); }
private void button1_Click(object sender, EventArgs e) { const string cmdSelect = "select OrderDate, Year(OrderDate) as yy,\n" + "Month(OrderDate) as mm, Country, City, ProductName,\n" + "SalesPerson, Quantity, ExtendedPrice from Invoices";
Excel.PivotCache pivotCashe; Excel.PivotTable pivotTable; Excel.PivotField pivotField; Excel.Worksheet oSheet; Excel.Application xlApp = new Excel.Application();
string dataSource = Application.StartupPath + @"\..\..\Northwind.mdb";
button1.Enabled = false; label1.Visible = true; try { xlApp.Workbooks.Add(Type.Missing); xlApp.Visible = true; xlApp.Interactive = false; xlApp.EnableEvents = false; oSheet = (Excel.Worksheet)xlApp.ActiveSheet; oSheet.get_Range("A1", Type.Missing).Value2 = "Сводный отчет"; oSheet.get_Range("A1", Type.Missing).Font.Size = 12; oSheet.get_Range("A1", Type.Missing).Font.Bold = true; oSheet.get_Range("A1", Type.Missing).Font.Italic = true; oSheet.get_Range("A1", Type.Missing).Font.Underline = true;
// создаем запрос pivotCashe = ((Excel.PivotCaches)xlApp.ActiveWorkbook.PivotCaches()). Add(Excel.XlPivotTableSourceType.xlExternal, Type.Missing); pivotCashe.Connection = string.Format("OLEDB;Provider=Microsoft.Jet.OLEDB.4.0;Data Source={0}", dataSource); pivotCashe.CommandType = Microsoft.Office.Interop.Excel.XlCmdType.xlCmdSql; pivotCashe.CommandText = cmdSelect;
// создаем сводную таблицу на основе запроса (пока без полей) pivotTable = pivotCashe.CreatePivotTable(oSheet.get_Range("A3", Type.Missing), "MyPivotTable1", Type.Missing, Excel.XlPivotTableVersionList.xlPivotTableVersionCurrent);
pivotTable.DisplayImmediateItems = false; pivotTable.EnableDrilldown = true; pivotTable.ManualUpdate = true; // настраиваем поля // поля колонок pivotField = (Excel.PivotField)pivotTable.PivotFields("yy"); pivotField.Orientation = Microsoft.Office.Interop.Excel.XlPivotFieldOrientation.xlColumnField; pivotField.Caption = "Год"; // сворачиваем данные по годам, чтобы влезли все данные for (int i = 1; i <= ((Excel.PivotItems)pivotField.PivotItems(Type.Missing)).Count; i++) { ((Excel.PivotItem)pivotField.PivotItems(i)).ShowDetail = false; }
pivotField = (Excel.PivotField)pivotTable.PivotFields("mm"); pivotField.Orientation = Microsoft.Office.Interop.Excel.XlPivotFieldOrientation.xlColumnField; // сворачиваем данные по месяцам, чтобы влезли все данные for (int i = 1; i <= ((Excel.PivotItems)pivotField.PivotItems(Type.Missing)).Count; i++) { ((Excel.PivotItem)pivotField.PivotItems(i)).ShowDetail = false; } pivotField.Caption = "Месяц";
pivotField = (Excel.PivotField)pivotTable.PivotFields("OrderDate"); pivotField.Orientation = Microsoft.Office.Interop.Excel.XlPivotFieldOrientation.xlColumnField; pivotField.Caption = "Дата заказа";
// поля строк pivotField = (Excel.PivotField)pivotTable.PivotFields("SalesPerson"); pivotField.Orientation = Microsoft.Office.Interop.Excel.XlPivotFieldOrientation.xlRowField; pivotField.Caption = "Продавец";
pivotField = (Excel.PivotField)pivotTable.PivotFields("Country"); pivotField.Orientation = Microsoft.Office.Interop.Excel.XlPivotFieldOrientation.xlRowField; pivotField.Caption = "Страна";
pivotField = (Excel.PivotField)pivotTable.PivotFields("City"); pivotField.Orientation = Microsoft.Office.Interop.Excel.XlPivotFieldOrientation.xlRowField; pivotField.Caption = "Город";
pivotField = (Excel.PivotField)pivotTable.PivotFields("ProductName"); pivotField.Orientation = Microsoft.Office.Interop.Excel.XlPivotFieldOrientation.xlRowField; pivotField.Caption = "Изделие"; // // поля данных pivotField = pivotTable.AddDataField(pivotTable.PivotFields("Quantity"), "Кол-во", Microsoft.Office.Interop.Excel.XlConsolidationFunction.xlSum); //pivotField.Function = Microsoft.Office.Interop.Excel.XlConsolidationFunction.xlSum; // возможна персональная настройка формата вывода данных (не забываем о "культуре") // pivotField = pivotTable.AddDataField(pivotTable.PivotFields("ExtendedPrice"), "Сумма продаж", Microsoft.Office.Interop.Excel.XlConsolidationFunction.xlSum); // настроим "культуру" на англ., чтоб не зависить от локальных настроек int savedCult = Thread.CurrentThread.CurrentCulture.LCID; Thread.CurrentThread.CurrentCulture = new CultureInfo(0x0409, false); Thread.CurrentThread.CurrentUICulture = new CultureInfo(0x0409, false); try { // установим "американский" формат данных pivotField.NumberFormat = "#,##0.00"; // возможно задать формат сразу всей области даных! //pivotTable.DataBodyRange.NumberFormat = "#,##0.00"; } finally { // восстановим пользовательскую "культуру" для отображения всех данных в // привычных глазу форматах Thread.CurrentThread.CurrentCulture = new CultureInfo(savedCult, true); Thread.CurrentThread.CurrentUICulture = new CultureInfo(savedCult, true); }
// убираем спиcок полей с экрана xlApp.ActiveWorkbook.ShowPivotTableFieldList = !(pivotTable.Version == Microsoft.Office.Interop.Excel.XlPivotTableVersionList.xlPivotTableVersion10); // рассчитаем таблицу pivotTable.ManualUpdate = false; xlApp.ActiveWorkbook.Saved = true; } finally { // отсоединяемся от Excel'я pivotField = null; pivotTable = null; pivotCashe = null; oSheet = null; xlApp.Interactive = true; xlApp.ScreenUpdating = true; xlApp.UserControl = true; xlApp = null; button1.Enabled = true; label1.Visible = false; } }
private void Form1_FormClosing(object sender, FormClosingEventArgs e) { e.Cancel = !button1.Enabled; } } }
Статья показывает лишь небольшие возможности Сводного отчета. Незатронутыми остались вопросы по созданию расчетных полей, сводных диаграмм и т.д.
Проект создавался и тестировался на Delphi 7, BDS 2006 и Excel2003. Исходные тексты программы на Delphi, база данных и пример отчета находятся в архиве PivotTable.zip. Исходные тексты на C# (VS2005) и база данных находятся в архиве WinApp1.zip. Более детальную информацию можно получить из файла vbaxl9.chm для Microsoft Excel 2000 или vbaxl10.chm для Microsoft Excel 2002, или с сайтов:
http://exceltip.com/excel_tips/Excel_Pivot_Tables/32.html
http://msdn2.microsoft.com/ru-ru/library/microsoft.office.interop.excel.pivottable.aspx
http://msdn2.microsoft.com/ru-ru/library/microsoft.office.interop.excel.pivotcache.aspx
http://msdn2.microsoft.com/ru-ru/library/microsoft.office.interop.excel.pivotfields.aspx
К материалу прилагаются файлы:Тестовый проект на Delphi (659 K)Тестовый проект на С# (500 K)
Создание Web-приложений в среде Delphi
В. Ковалев,
Эпоха web-дизайна, когда наиважнейшим считалось возможность запихнуть на страницу как можно больше графики и поразить пользователя широтой фантазии дизайнера, канула в лету. В моде - информационный дизайн и всяческие удобности для пользователя. Плохим тоном уже считается одно только то, что на сайте нет форума или гостевой книги, и самое ужасное - о, боже - вы до сих пор верстаете каждую страницу вручную, вместо того, что бы поручить сеё скучное и утомительное занятие скрипту.
Время первопроходцев HTML в скором времени закончится и вовсе, и их место займут профессионалы-программисты. Именно программисты, ибо уже в большей степени от их умения создать удобный в использование сайт зависит его успех. Кроме прочего, теперь сайты создают совсем уж как программные продукты: тем кто занят наполнением содержанием, уже может не опасаться запутаться в html, java, cgi и прочих ипостасях интернет-технологий - администрирование сайта становится таким же привычным и удобным, как работа с текстовыми процессорами. Пока, правда, ощущается явный недостаток законченных продуктов, на плечи которых можно было бы возложить все функции по поддержанию сайта в актуальном состоянии, оставив себе лишь вопросы по наполнению его содержанием.
Пока же тяжкое бремя по созданию данных систем, на основе которых будет функционировать сайт, ложится на плечи программистов, только вот-вот успевших изумится возможностями языка Perl или Java. И многие из них, сказать без преувеличения, вышли, словно из гоголевской "Шинели", из Delphi. Действительно, данная среда разработчика, предоставляющая удобный интерфейс для визуального программирования и широкие возможности Object Pascal, столь мила сердцу российских программистов.
Но, похоже, что времена меняются, и Delphi из среды создания обычных настольных приложений, может сгодиться и в другом плане: для написания прикладных web-программ. И действительно, что может быть лучше: уже знакомая среда разработки, вдоль и поперек изученный язык, да и достаточно широкий круг специалистов по программированию в Delphi - это ли не плюсы создания web-приложений на Delphi. Есть конечно и минусы: созданные программы вряд ли смогут удовлетворить тех, кто считает, что лучший web-сервер, это сервер не от Microsoft. Но что поделаешь - версия Delphi под Unix отложена пока до лучших времен. Зато посудите сами: перенос программ в будущем светлом будущем можно будет совершить с малой кровью.
Но отложим пока теорию в сторону, и обратимся к практике. Итак, создание web-приложения в среде Delphi, что называется шаг за шагом.
Пример из учебника
В отличие от прочих аспектов создания приложений в Delphi, о создании приложений для web написано мало. Так ужасающе мало, что из книги в книгу, из учебника в учебник путешествует один и тот же пример. Не будем оригинальничать и мы - чем проще, тем лучше.
Вообще, простейшее web-приложение на Delphi мало чем отличается, а точнее ничем не отличается от создания программы для старой доброй DOS. Это - простейшее консольное приложение, запускаемое на стороне сервера и взаимодействующие с пользователем (в случае необходимости) через броузер.
Создайте в Delphi новый проект - ту самую уже давно забытую всем Console Application. Вы получите знакомый со школьной скамьи, текст обычной паскалевской программы:
program primer;
{$APPTYPE CONSOLE} uses SysUtils;
begin // Insert user code here end.
Далее еще проще. Организуем вывод кода HTML с помощью команды writeln.
writeln ('CONTENT-TYPE: TEXT/HTML'); writeln; writeln ('<html>'); writeln ('<head>'); writeln ('<meta HTTP-EQUIV="Content-Type" Content="text-html; charset=windows-1251">'); writeln ('<title>Delphi the best facility for making web-publications!</title>'); writeln ('</head>'); writeln ('<body bgcolor="white">'); writeln ('Hello, world!'); writeln ('</body>'); writeln ('</html>');
Обратите внимание на строку CONTENT-TYPE: TEXT/HTML, которая определяет описание последующего содержимого, а именно кода HTML. После CONTENT-TYPE: TEXT/HTML, необходимо вывести пустую строку иначе броузер может выдать сообщение об ошибке.
Теперь, когда приложение закончено, осталось его скомпилировать и проверить. Для проверки работоспособности программы вам понадобиться веб-сервер. Можно особо не утруждаться, подойдет любой, даже стандартный домашний веб-сервер от Microsoft. Приложение надо будет разместить в папке публикаций сервера (обычно это - C:\Inetpub\ wwwroot) и запустить сам сервер. Теперь, если вы перейдете по адресу http://localhost/primer.exe в броузере, вы должны увидеть результат действия данной программы - строку Hello, world! Вот и всё, простейшее web-приложение на Delphi готово.
Передача параметров
На самом деле нам бы вряд ли понадобилось Delphi, для создания подобных программ. Конечно, можно генерировать страницы исходя из различных условий, но вопрос в том, как данные условия передать программе. И здесь оказывается не всё так сложно, достаточно вспомнить передачу параметров приложению с помощью командной строки и поступить соответствующе. Хотя это, разумеется, хитрость. Это для приложения Delphi мы оперируем командной строкой, для пользователя же это адресная строка в броузере, то есть url.
Попробуем на примере. Необходимо создать приложение, которое выдает различную информацию (к примеру, время, дату или то и другое вместе) в зависимости от параметров, указанных в адресной строке броузера. Как известно, за данные в параметрах отвечают такие функции, как ParamCount и ParamStr. Их-то мы и будем использовать.
program CgiDate;
{$APPTYPE CONSOLE}
uses SysUtils;
begin writeln ('CONTENT-TYPE: TEXT/HTML'); writeln; writeln ('<HTML><HEAD>'); writeln ('<TITLE>Cgidate</TITLE>'); writeln ('</HEAD><BODY>>'); writeln ('<H1>Пример передачи параметров</H1>'); writeln ('<HR>'); writeln ('<H4>
if ParamCount >0 then begin if ParamStr (1) = 'date' then writeln (FormatDateTime('"Сегодня " dddd, mmmm d, yyyy', Now)) else if ParamStr (1) = 'time' then writeln (FormatDateTime('"Время" hh:mm:ss AM/PM', Now)) else if ParamStr (1) = 'both' then writeln (FormatDateTime('"Сегодня " dddd, mmmm d, yyyy,' + '"<p> и время" hh:mm:ss AM/PM', Now)) else writeln ('Ошибка! Неверный параметр: ' + ParamStr (1) + '.') end else writeln ('Параметр отсутствует.'); writeln ('</BODY></HTML>'); end.
Не правда ли просто? Теперь, если в адресной строке броузера вы наберете, например http://localhost/cgidate/exe?time, будет сгенерирована страница, отображающая текущее время, http://localhost/cgidate/exe?date - соответственно дата, а при передаче параметра both - текущая дата и время. В случае если никакой из параметров передан не был или он был ошибочен - возникнет сообщение об этом.
Данные адреса и параметры можно непосредственно указать в коде HTML и генерировать необходимые изменения на странице либо другие страницы переходя по соответствующим ссылкам.
Следует обратить внимание на то, как передавать данные через url. Знак вопроса отделяет параметр от адреса файла, с помощью знака равенства web-приложению передается значение данного параметра. Так как в адресной строке нельзя использовать пробел, он заменяется на шестнадцатеричный код в таблице ASCII, то есть %20.
Но на самом деле, если некие данные передаются от пользователя web-приложению, то обычно для этого используют формы, а не url (хотя одно другому не мешает). Попробуем и мы создать приложение, которое бы получало данные от пользователя, занесенные им в форму.
Для начала, естественно, нужно создать сам код HTML в котором бы присутствовала форма с полями ввода, кнопкой отправки и прочими необходимыми атрибутами. При этом form action должен содержать адрес программы, которая будет получать данные. Значение method может быть равно как GET, так и POST. На самом деле GET - это и есть передача параметров через url, добавляя их к адресной строке, так как POST передает их приложению посредством стандартного потока ввода. Какой из них лучше и удобней - решать вам, но чаще всего метод GET используется именно для генерации страниц (достаточно взглянуть на url который возникает при работе на поисковых серверах), тогда как второй для - передачи данных, отображать которые в адресной строке было бы весьма накладно.
Затем необходимо создать приложение, которое бы адекватно смогло воспринять все эти данные со стороны пользователя. В случае если данные передавались с помощью метода GET проблем не будет - можно действовать как в предыдущем примере. Если же приложение получает данные от пользователя с помощью POST, всё несколько иначе.
Необходимо будет считывать данные из переменной окружения, а для этого мы, естественно, должны знать данные переменные.
Гюльчитай, открой личико
Нет в мире тайн. Особенно их мало у пользователя от web-приложения. И если мы не знаем о пользователе кое-что личное, все прочее броузеры с легкостью отдают web-серверу, нисколько не заботясь о приватности и желаниях того самого пользователя. Это, конечно, нехорошо для пользователя, но хорошо для разработчика web-приложения, поскольку для него знания - великая вещь.
Итак, данные от пользователя web-приложению можно передать через переменные окружения. Вот список наиболее часто употребляемых:
GATEWAY_INTERFACE
Поддерживаемая версия CGI.
REQUEST_METHOD
Метод запроса, может быть как GET так и POST.
HTTP_REFERER
Адрес страницы (url), активирующей текущее приложение на web-сервере.
PATH_INFO
Путь переданный приложению расположенный между именем приложения и строкой запроса.
QUERY_STRING
Строка запроса, если метод - GET, добавляеться к url.
REMOTE_HOST
Имя хоста удаленного пользователя.
REMOTE_USER
Имя удаленного пользователя.
REMOTE_IDENT
IP-адрес удаленного пользователя.
HTTP_USER_AGENT
Имя и версия броузера удаленного пользователя.
С помощью данных переменных можно получить исчерпывающую информацию о пользователе и передаваемых данных для верного проектирования вашего web-приложения. Конечно, этого хватит в том случае, если вы не собираетесь подобно Большому Брату следить за каждым телодвижением пользователя.
Но вернемся к поставленной задаче - передаче данных приложению от пользователя через форму. Данные, которые передаются через QUERY_STRING в приложение с помощью метода POST, достаточно просто извлечь для использования.
Ниже листинг программы, выдающий список некоторых переменных окружения и их значения. Узнайте кое-что о своем броузере и web-сервере.
program CgiVars;
{$APPTYPE CONSOLE}
uses Windows; сonst VarList: array [1..17] of string [30] = ('SERVER_NAME', 'SERVER_PROTOCOL', 'SERVER_PORT', 'SERVER_SOFTWARE', 'GATEWAY_INTERFACE', 'REQUEST_METHOD', 'PATH_TRANSLATED', 'HTTP_REFERER', 'SCRIPT_NAME', 'PATH_INFO', 'QUERY_STRING', 'HTTP_ACCEPT', 'REMOTE_HOST', 'REMOTE_USER', 'REMOTE_ADDR', 'REMOTE_IDENT', 'HTTP_USER_AGENT');
var I: Integer; ReqVar: string; VarValue: array [0..200] of Char;
begin writeln('Content type: text/html'); writeln; writeln('<HTML><HEAD>'); writeln('<TITLE>CGI Variables</TITLE>'); writeln('</HEAD><BODY>'); writeln('<H1>CGI Variables</H1>'); writeln('<HR><PRE>');
for I := Low (VarList) to High (VarList) do begin ReqVar := VarList[I]; if (GetEnvironmentVariable (PChar(ReqVar), VarValue, 200) > 0) then else VarValue := ''; writeln (VarList[I] + ' = ' + VarValue); end; writeln('</PRE></BODY></HTML>'); end.
За кадром
В этой статье мы не коснулись другой и уж наверняка более обширной и сложной темы, как создание ISAPI-приложений на Delphi. Вышеприведенные способы создания приложений годны лишь в том случае, если вам необходимо быстрое, компактное и не слишком сложное web-приложение. Если же вам необходим, к примеру, доступ к базам данным, то подобный путь неприемлем.
Для создания полномасштабных приложений для интернета в Delphi существует специальный помощник - Web Server Application. С его помощью можно создать приложение генерируещее динамические web-страницы, основанные на CGI, NSAPI или ISAPI. Единственное накладываемое ограничение - непосредственно web-сервер должен работать на базе Windows.
Одним из главных преимуществ создания подобных приложений именно в среде Delphi является то, что вы продолжаете работать с визуальными компонентами - это значительно проще, чем создание приложений в других средах - возможность ошибки в больших проектах, где используется визуальное проектирование меньше, чем в тех, где всё описывается исключительно кодом. Кроме того, средства создания web-приложений позволяют импортировать уже существующие приложения в интернет-среду, что, согласитесь, немаловажно. Пока, конечно, Delphi не обладает большим набором компонентов для web-приложений, но, видимо, уже следующая версия этой среды обзаведется необходимыми. Но и сейчас Delphi можно считать достаточно удобным инструментом для создания приложений, взаимодействующих с интернетом. В. Ковалев / ©
Суть технологии
В основе технологии .NET лежит идея использования некоторого промежуточного машинно-независимого языка. В обычном программировании (с использованием любых существующих вне технологии .NET языков - от Ассемблера до ранних версий Delphi) программа, написанная в понятной для программиста нотации, компилировалась в последовательность машинных инструкций, "понятных" процессору. В новой технологии программа компилируется в термины машинно-независимого языка CIL (Common Intermediate Language - общий промежуточный язык) и сопровождается метаданными - подробными инструкциями как о самой программы, так и о всем необходимом для ее успешного выполнения. В момент, когда коды промежуточной программы (они называются управляемыми кодами) ставятся на исполнение, в дело вступает среда CLR (Common Language Runtime - общеязыковая среда исполнения), которая с помощью встроенного JIT-компилятора (JIT - just-in-time - вовремя, по мере надобности) переводит управляемые коды в набор исполняемых машинных инструкций и обеспечивает разнообразные вспомогательные действия для исполняемой программы.
Идея использования машинно-независимого промежуточного языка не нова. Впервые она была высказана еще в 1958 г. американским программистом Мелвином Е. Конвеем (Conway) в журнальной статье "Proposal For An UNCOL" ("Предложение по универсальному компьютерно-ориентированному языку"). Двухфазное кодирование имеет два существенных преимущества. Во-первых, предельно упрощается распространение программ. Переведенная в СIL программа может выполняться на любом компьютере, имеющем соответствующую среду исполнения. Причем в управляемый код включается вся системная информация, необходимая для нормального функционирования программы, так что отпадает необходимость в регистрации отдельных частей программы (объектов, модулей, динамических библиотек) в системном реестре.
Замечание:
В настоящее время технология .NET реализована для ОС семейства Windows. Существуют проекты переноса технологии в ОС FreeBSD, Mac OC X 10.2 и Linux. Однако распространение .NET на другие платформы затруднено, в основном, проблемами воспроизведения пользовательского интерфейса: экраны настольного компьютера, наладонного компьютера или мобильного телефона совершенно отличны.
Во-вторых, повышается защищенность программ и файлов: в управляемых кодах нет информации о файловой системе компьютера, на котором исполняется программа, или способах запуска программ, а среда исполнения сконструирована так, чтобы всемерно уберечь программно-аппаратные средства компьютера от атак вирусов, других злонамеренных программ, а также от программных ошибок.
однопользовательская реляционная СУБД, причем база
TjanSQL - однопользовательская реляционная СУБД, причем база данных представлена в виде плоских текстовых файлов, где разделителем между столбцами служит точка с запятой. TjanSQL поддерживает следующие команды языка SQL: SELECT (с возможностью объединения таблиц, вычислений и псевдонимов полей), UPDATE, INSERT (значения полей и подзапросы), DELETE, CREATE TABLE, DROP TABLE, ALTER TABLE, CONNECT TO, COMMIT, WHERE, IN (список или подзапрос), GROUP BY, HAVING, ORDER BY (ASC, DESC), а также вложенные подзапросы, статистические функции (COUNT, SUM, AVG, MAX, MIN), операторы (+, -, *, /, and, or, >, >=, <, <=, =, <>, Like), функции UPPER, LOWER, TRIM, LEFT, MID, RIGHT, LEN, FIX, SOUNDEX, SQR, SQRT и др.
Все это я узнал из аннотации к архиву. Начало интригующее… После распаковки zip-архива объемом 425 Кб обнаружилось еще несколько интересных особенностей. В первую очередь, порадовало наличие в образовавшемся каталоге таких поддиректорий, как db, demosource и sql, а также файла janSQL.hlp. Появилась надежда, что не придется исследовать исходные коды компонентов для определения набора и назначения их свойств и методов.
Увы, она не оправдалась. К сожалению, более тесное знакомство с TjanSQL вызвало больше отрицательных эмоций, нежели положительных. Первым разочарованием стало отсутствие законченных компонент, которые можно было бы использовать по аналогии с Data Access. В частности, для подключения к текстовой базе данных необходимо в режиме runtime создавать (и ликвидировать) объект класса TjanSQL, используя примерно такой код:
var janSQLDemoF: TjanSQLDemoF; appldir:string; thefile:string; db:TjanSQL; procedure TjanSQLDemoF.FormCreate (Sender: TObject); begin db:=TjanSQL.create; end; procedure TjanSQLDemoF.FormDestroy (Sender: TObject); begin db.free; end;
Таким образом, в TjanSQL отсутствует одно из основных, на мой взгляд, преимуществ компонентного Delphi-программирования: для доступа к базе данных необходимо прописывать всю настроечную информацию в тексте программы. Конечно, это тоже своего рода design-time, но помилуйте - что стоило разработать невизуальный компонент, аналог какого-нибудь TDataBase или TAdoConnection?
Второй минус - демонстрационный проект, поставляемый вместе с классами Tjan* (язык не поворачивается назвать их компонентами), просто так не работает. Я впервые столкнулся с ситуацией, когда демо-проект не скомпилировался при попытке запуска. Делать нечего - начал разбираться. Как выяснилось, проблема устранима - достаточно закомментировать те строки, которые вызывают "раздражение" у модулей проекта Delphi, а также изменить пути к модулям с описанием классов доступа к базе данных - они почему-то "зашиты" в проект с точностью до имени каталога и буквы диска. Так я узнал, что у разработчика на компьютере как минимум три логических диска (пути начинались с Е:\…).
В файле помощи тоже ряд пробелов. Зачем было помещать в оглавление ссылки на страницы, содержащие только заголовок? Впрочем, возможно, я придираюсь...
Но главным, на мой взгляд, недостатком TjanSQL является невозможность непосредственной связи между TJanSQL и компонентами Data Aware, как это делается, в частности, с TQuery, TDataSource и TDBGrid. В демонстрационном проекте для отображения информации, получаемой с помощью SQL-запросов, используется обычный TStringGrid. В следующем примере приводится та часть кода, которая относится к обработке и визуализации результатов запроса:
unit janSQLDemoU; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, FileCtrl,Grids, ExtCtrls, ComCtrls, ToolWin, Menus, janSQL, StdCtrls, Buttons;
type TjanSQLDemoF = class (TForm) MainMenu1: TMainMenu; ToolBar1: TToolBar; StatusBar1: TStatusBar; Panel1: TPanel; Splitter1: TSplitter; viewgrid: TStringGrid; sqlmemo: TMemo; cmdExecute: TSpeedButton; edmessage: TEdit; Insert1: TMenuItem; ApplicationFolder1: TMenuItem; SelectedFolder1: TMenuItem; Help1: TMenuItem; Contents1: TMenuItem; procedure cmdExecuteClick (Sender: TObject);
private procedure showresults (resultset:integer); Private declarations
public Public declarations end;
var janSQLDemoF: TjanSQLDemoF; appldir:string; db:TjanSQL;
implementation {$R *.DFM}
procedure TjanSQLDemoF.cmdExecuteClick (Sender: TObject); var sqlresult:integer; sqltext:string; begin sqltext:=sqlmemo.text; sqlresult:=db.SQLDirect (sqltext); if sqlresult<>0 then begin edmessage.Text:='OK'; sqlmemo.text:=''; if sqlresult>0 then begin showresults (sqlresult); db.ReleaseRecordset (sqlresult); end; end else edmessage.Text:=db.Error; sqlmemo.SetFocus; end;
procedure TjanSQLDemoF.showresults (resultset:integer); var r1:integer; i,arow,acol,c,rc,fc:integer; begin r1:=resultset; rc:=db.RecordSets [r1].recordcount; if rc=0 then exit; fc:=db.RecordSets [r1].fieldcount; if fc=0 then exit; viewgrid.RowCount:=rc+1; viewgrid.ColCount:=fc; for i:=0 to fc-1 do viewgrid.Cells [i,0]:=db.recordsets [r1].FieldNames [i]; for arow:=0 to rc-1 do for acol:=0 to fc-1 do viewgrid.cells [acol,arow+1]:=db.RecordSets [r1].records [arow].fields [acol]; end;
Только не подумайте, что я собрался обругать всё и вся. Я всего лишь считаю необходимым предупредить вас о подводных камнях, на которые вы можете напороться при использовании TJanSQL.
Вообще TJanSQL - очень полезный набор разработок. Но только если рассматривать его не как конечный продукт, а как базу для написания собственных BDE-независимых компонентов для работы с плоскими текстовыми файлами, организованными в реляционные базы данных.
Действительно, возможность обращения к структурированным текстовым файлам с использованием SQL - ценное преимущество. Можно совместить помещенные в TJanSQL функции, например, со стандартными компонентами Data Access или воспользоваться другими разработками - компонентами для организации временных таблиц в памяти. Это обеспечит доступ к плоским текстовым файлам на уровне SQL-запросов - сервис, аналогичный стандартным Delphi-компонентам, но без зависимости от такого монстра, как Borland Database Engine. Стоит ли говорить, какие перспективы тогда откроются?
Усложняем проект
Теперь можно внести некоторое разнообразие в структуру базы данных testdbExpress и добавить еще одну таблицу, SQL-скрипт создания которой ниже.
CREATE TABLE orders ( OrderNo INT (4) NOT NULL PRIMARY KEY, CustNo INT (4) NOT NULL, Name VARCHAR (24), Price DOUBLE (8,2))
Подобные SQL-инструкции могут быть выполнены с помощью метода TConnection.ExecuteDirect:
procedure TMainForm.Button1Click (Sender: TObject); var SQLScript:String; begin SQLScript:= 'CREATE TABLE orders (OrderNo INT (4) NOT NULL PRIMARY KEY,' +' CustNo INT (4) NOT NULL, Name VARCHAR (24), Price DOUBLE (8,2))'; SQLConnection.ExecuteDirect (SQLScript); end;
Таким образом, нам больше не нужны дополнительные средства для изменения состава таблиц базы данных MySQL - у нас появилась возможность менять ее структуру "на лету", в процессе выполнения приложения. Для того чтобы еще более упростить создание требуемой структуры таблиц в тестовой базе данных, я поместил в демонстрационный проект кнопку "Создать таблицы" - ее нажатие приводит к выполнению команд SQL, создающих эти структуры данных.
Таблица orders позволяет просматривать и модифицировать информацию не только о клиентах, но и об их заказах. Организация связи между этими таблицами по ключевому полю достаточно очевидна. Пользователю предоставляется возможность просмотра детальной информации по заказам при выборе клиента. Как известно, BDE позволяет достаточно просто организовать такого рода связи. DbExpress предоставляет, как минимум, два способа решения этой задачи.
Проще всего будет загрузить содержимое компонентов TSQLTable, соответствующих таблицам customer и orders, в компоненты ClientDataSet (посредством TDataSetProvider). Затем можно организовать связи по ключевому полю между этими наборами данных, используя штатные средства соответствующих компонентов. Однако содержимое обеих MySQL-таблиц размещается в оперативной памяти клиентского ПК, что чревато нехваткой ресурсов в случае больших объемов данных. С другой стороны, это обстоятельство обеспечивает высокую скорость работы. В приведенном ниже листинге описаны ключевые параметры создаваемых при использовании такого подхода компонентов.
object SQLTableOrders: TSQLTable SQLConnection = SQLConnection TableName = 'orders' end object dspOrders: TDataSetProvider DataSet = SQLTableOrders end object cdsOrders: TClientDataSet IndexFieldNames = 'CustNo' MasterFields = 'CustNo' MasterSource = dsCustomer ProviderName = 'dspOrders' end object dsOrders: TDataSource DataSet = cdsOrders end
В данном случае связь между таблицами организована по ключевому полю на уровне компонентов ClientDataSet. Но есть альтернативный подход, реализуемый двумя способами. Речь идет о формировании детализирующего набора данных по факту перехода с одной записи на другую в master-компоненте. Таким образом мы сокращаем расход оперативной памяти за счет скорости работы. Реализовать такой механизм можно с помощью как TSQLTable, так и TSQLQuery. В первом случае используется связь между главным и подчиненным наборами данных по полям MasterSource и MasterFields:
object SQLTableOrdersByCustomer: TSQLTable IndexFieldNames = 'CustNo' MasterFields = 'CustNo' MasterSource = dsCustomer SQLConnection = SQLConnection TableName = 'orders' end object dspOrdersByCustomer: TDataSetProvider DataSet = SQLTableOrdersByCustomer end object cdsOrdersbyCustomer: TClientDataSet ProviderName = 'dspOrdersByCustomer' end object dsOrdersbyCustomer: TDataSource DataSet = cdsOrdersbyCustomer end
Во втором случае применяется параметр, передаваемый в SQL-запрос:
object SQLQueryOrders: TSQLQuery DataSource = dsCustomer Params = < item DataType = ftInteger Name = 'custno' ParamType = ptInput end> SQL.Strings = ( 'select * from orders where (orders.custno =:custno)') SQLConnection = SQLConnection end object dspQOrders: TDataSetProvider DataSet = SQLQueryOrders end object cdsQOrders: TClientDataSet ProviderName = 'dspQOrders' end object dsQOrders: TDataSource DataSet = cdsQOrders end
Оба способа требуют явного указания обработчика события OnDataChanged компонента dsCustomerDataChange:
procedure TMainForm.dsCustomerDataChange (Sender: TObject; Field: TField); begin if cdsOrdersByCustomer.Active then cdsOrdersByCustomer.Refresh; if cdsQOrders.Active then cdsQOrders.Refresh; end;
![](image/bde3.jpg)
![1](image/bde4.jpg)
Установка и управление MySQL
Размер дистрибутива (mysql-4.0.12-win.zip) составляет примерно 21 Мб (прим.ред.: текущая версия на момент публикации статьи на CITForum.ru - , 24.7 Мб). Установка не требует особых усилий и занимает несколько минут. Если раньше вы не имели дела с MySQL, то рекомендую не менять настройки, предлагаемые по умолчанию.
Полная установка СУБД не превышает 72 Мб - согласитесь, по сравнению с теми сотнями мегабайт, которые требуются для продуктов IBM, Oracle, Microsoft и др., впечатляет. В каталоге C:\MySQL\Docs вы найдете руководства по MySQL (на английском языке) - однако организация их, к сожалению, оставляет желать лучшего. Тем не менее, вся необходимая информация там есть.
В каталоге C:\mysql\bin размещен ряд программ и библиотека libmySQL.dll, которая потребуется нам для Delphi-проектов. Для манипуляции структурой базы и самими данными на первых порах воспользуемся утилитой mysql.exe, которая предоставляет нам интерфейс командной строки. Это не единственный способ работы с MySQL. Есть и другие приложения, в том числе, входящие в стандартную поставку,- например, WinMySqladmin. Обзор таких приложений может послужить темой отдельной статьи, мы же пока воспользуемся скромной mysql.exe.
![](image/bde5.jpg)
Каждый из подкаталогов, расположенных в C:\mysql\data, соответствует базе данных. При инсталляции MySQL там формируются две БД - mysql и test. Для того чтобы создать собственную базу данных - например, с одной таблицей из трех полей, в которой будет храниться информация о заказчиках,- следует осуществить общение с mysql.exe примерно следующим образом:
mysql> CREATE DATABASE testdbExpress; Query OK, 1 row affected (0.01 sec) mysql> USE testdbExpress; Database changed; mysql> show tables; Empty set (0.02 sec) mysql> CREATE TABLE customer (CustNo INT (4) NOT NULL PRIMARY KEY, Name VARCHAR (50), Company VARCHAR (100)); Query OK, 0 rows affected (0.18 sec) mysql> exit Bye
Результат описанной выше сессии - создание новой базы данных testdbExpress и таблицы customer.
Устаревшие и новые средства Delphi
Для того, чтобы Delphi соответствовал требованиям к языкам, вырабатывающим CIL, была проведена его модификация. В ходе модификации из языка убраны средства, которые не поддерживаются CLR, и добавлены новые.
Устаревшие типы
Это, пожалуй, самая болезненная проблема для совместимости с ранними версиями. Прежде всего, речь идет об указателях. Указатели считаются небезопасным типом, так как код, содержащий указатели, нельзя проверить на безопасность. Запрещена любая арифметика указателей, а также обращение к функциям и процедурам New, Dispose, GetMem, FreeMem и ReallocMem. Вместо концепции указателей программы должны использовать два класса из CTS: IntPtr и Marshal. Первый - суть универсальный платформеннонезависимый указатель, открывающий доступ к механизму межплатформенного взаимодействия P/Invoke. Второй осуществляет маршализацию данных, то есть низкоуровневое взаимодействие процессов, включая упаковку/распаковку передаваемых данных.
В следующем примере создается и используется указатель для размещения в нем целого числа. uses System.Runtime.InteropServices; var
X: IntPtr; begin
X := Marshal.AllocHGlobal(SizeOf(Integer)); // Создаем указатель try // на 4 байта Marshal.WriteInt32(X, 123456); // Наполняем его Caption := IntToStr(Marshal.ReadInt32(X) * 2); // Используем finally
X.Free; // Освобождаем end
end;
Запрещены типизированные и нетипизированные файлы. Безопасный код может использовать только текстовые файлы типа FileVar: TextFile. Для работы с не текстовыми файлами рекомендуется использовать объекты класса TFileStream. Например, следующая программа создаст файл, содержащий 5 случайных вещественных чисел.
procedure TForm3.Button1Click(Sender: TObject); var
A: Real; k: Integer; F: TFileStream; begin
F := TFileStream.Create('data.dat', fmCreate); try
for k := 1 to 5 do
begin
A := Random; F.Write(A, SizeOf(Real)); end; finally
F.Free end
end; Записи не могут содержать вариантную часть, но могут - методы (см. ниже). В .NET Framework используются "широкие" символы (2 байта на символ). В связи с этим небезопасным считается тип PChar, который используется как ссылка на массив однобайтных символов. В то же время формат типов String в Delphi и CTS совпадает.
Поскольку тип PChar в программах Delphi используется, в основном, при обращении в функциям API, вместо PChar следует использовать класс StingBuilder. Следующая программа прочитает заголовок активного окна:
function GetText(Window: HWND; BufSize: Integer = 1024): String; var
Buffer: StringBuilder; begin
Buffer := StringBuilder.Create(BufSize); GetWindowText(Window, Buffer, Buffer.Capacity); Result := Buffer.ToString; end;
Устаревшие возможности кода
Компилятор Delphi отныне не поддерживает встроенный ассемблер и директивы asm.
Запрещено использовать функции прямого доступа к памяти, такие как BlockRead, BlockWrite, GetMem, FreeMem, ReallocMem, а также директиву absolute и функцию addr. Поддерживается операция @ (получение адреса).
Материал является отрывком из готовящейся
Валерий Васильевич Фаронов, сайт "Королевство Delphi"
Материал является отрывком из готовящейся книги В.В. Фаронова по Delphi 8. А именно, глава первая - "Знакомство с Delphi 8".
Система программирования Borland ® Delphi™ For Microsoft ® .NET Framework - сложный программный продукт, дающий программисту все необходимые средства для создания программ любой сложности и назначения. Характерной особенностью системы является органичная поддержка новой технологии .NET. В этой главе приводится краткий обзор Delphi и технологии .NET.
у dbExpress есть ряд очевидных
Как видно из рассмотренных примеров**, у dbExpress есть ряд очевидных отличий по сравнению с BDE. В частности, появилась необходимость в использовании компонентов DataSetProvider и ClientDataSet, явный вызов метода ApplyUpdatets; кроме того, имеют место различные способы организации связи между таблицами. Тем не менее, использование технологии dbExpress не только приводит к дополнительным усилиям при разработке ПО, но и дает ряд преимуществ: проекты, разработанные с применением технологии dbExpress, более производительны и менее требовательны к ресурсам по сравнению с BDE-приложениями.
и не использоваться. Версия Delphi
Ограниченная поддержка технологии .NET была реализована еще в предыдущей версии Delphi 7 Studio. Однако в этой версии .NET могла и не использоваться. Версия Delphi 8, напротив, не может не использовать эту технологию. Для совместимости с предыдущими версиями в ней используются пространства имен Borland.VCL.XXXX, позволяющие ценой небольших изменений исходного кода в версии 8 компилировать и исполнять программы, написанные в предыдущих версиях. Однако, такая совместимость - мнимая, так как компилятор новой версии порождает инструкции языка СIL, которые могут исполняться только под управлением CLR.
В этом разделе кратко рассматриваются новые возможности Delphi, связанные с переходом на новую технологию .NET.
Знакомство с технологией .NET
Технология .NET - это сравнительно недавнее изобретение программистов корпорации Microsoft. Ее разработчики поставили перед собой задачу создания единой универсальной платформы (базы) программирования, равно годящейся для разработки любых программ - будь то обычные Windows-приложения, приложения для работы с базами данных, Web- и Windows-службы, приложения для мобильных и переносных устройств и т. д.
Создание заставок для ваших программ
,
Наверно, каждый программист на Дельфи хоть раз хотел создать к какой-нибудь своей программе заставку. В этой статье мы рассмотрим создание заставок в Дельфи. Тому кто умеет работать более чем с одной формой в приложении, будет очень легко это понять. Чтобы не вдаваться в теорию, начнем сразу с практики.
Откройте какое-нибудь свое приложение, к которому вы хотите добавить заставку, или создайте новое (на чистом проще разбираться). Теперь необходимо добавить в наш проект еще одну форму, которая будет заставкой. Для этого нажмите File->New Form и Дельфи создаст вам новую форму. Измените ее размеры как вам хочется. Потом установите свойство Border Style вашей формы в bsNone (у формы не будет заголовка и системных кнопок), установите свойство Visible в false. Свойтсво Position должно быть poScreenCenter - это значит, что форма появится по центру экрана. И чтобы не перепутать эту форму ни с какой другой задайте ей имя Logo.
Настройка формы заставки произведена, теперь необходимо сделать, чтобы по щелчку мышкой по этой форме или после нажатия клавиши или по истечении 5 секунд форма-заставка закрывалась. Для этого установите на форму Timer, его свойству Interval задайте значение 5000 (форма будет закрываться через 5 секунд). В обработчик события OnTimer напишите всего одно слово: Close;
В обработчик события OnClick для формы-заставки напишите тоже самое. Установите свойство формы KeyPreview в true (это делается для того, чтобы при нажатии любой клавиши вначале реагировала форма, а затем тот элемент, который был в фокусе в момент нажатия). А в обработчик события OnKeyPress (для формы-заставки конечно же) опять-таки напишите close;
Форма-заставка готова полностью и теперь необходимо, чтобы она запускалась перед главной формой. Для этого сделайте активной вашу главную форму, перейдите на вкладку Events в Object Inspector'e и выберите событие OnShow. В обработчике этого события надо написать следующее:
logo.showmodal;
Меня иногда спрашивают, чем отличаются процедуры show и showmodal. У них только одно принципиальное различие: если форма открылась методом Showmodal, то пока она не закроется пользователь не сможет взаимодействовать с остальными формами приложения. А если форма была открыта методом Show, то пользователь легко может перейти к любой форме приложения.
Итак, форма-заставка готова. Теперь мы слегка ее усовершенствуем. Добавим такую же штуку, как в формах-заставках Microsoft Office, а именно на форме будет показываться имя пользователя и организация. Для этого разместите на форме-заставке две метки (Label). Первую назовите UserName, а вторую - Organization. Чтобы это сделать мы воспользуемся реестром (тас вообще очень много интересного можно найти). Теперь откройте обработчик события OnCreate для формы-заставки и объявите там переменную R типа TRegistry, а в раздел Uses всей программы добавьте Registry. Теперь нам нужно создать объект R :
R:=TRegistry.Create;
R.RootKey:=HKEY_LOCAL_MACHINE;
R.OpenKey('SOFTWARE\Microsoft\Windows\CurrentVersion', False); //именно здесь эта информация хранится
Теперь необходимо прочитать нужные строки из этого раздела реестра и присвоить их соответствующим меткам:
UserName.Caption:=r.readstring('RegisteredOwner');
Organization.Caption:=r.readstring('RegisteredOrganization');
r.Free; //надо уничтожить этот объект, так как он нам больше не нужен
Таким образом весь этот обработчик должен иметь примерно такой вид:
procedure TLogo.FormCreate(Sender: TObject);
var R:Tregistry;
begin
R:=TRegistry.Create;
R.RootKey:=HKEY_LOCAL_MACHINE;
R.OpenKey('SOFTWARE\Microsoft\Windows\CurrentVersion', False);
UserName.Caption:=r.readstring('RegisteredOwner');
Organization.Caption:=r.readstring('RegisteredOrganization');
r.Free;
end;
Ну вот собственно и все что я хотел вам рассказать о заставках. Теперь ваши программы будут выглядеть более солидно. Но помните, что при создании небольших, вспомогательных программ, объем которых не превышает килобайт 100-150 заставки лучше не использовать.
(С) Автор статьи: // ().
P.S.
Для получения доступа к физическому диску мы открывали устройство \\.\PHYSICALDRIVE<n>, далее разбирали его структуру. Можно было поступить проще - открывать сразу логические диски (\\.\C:,\\.\D: и т.д.), но при таком варианте мы бы упустили из виду некоторые области диска, такие как, MBR и неразмеченные области. Какой вариант предпочтительнее, зависит от задачи.
Редактор диска своими руками
,
Многие помнят легендарный Norton DiskEditor - утилиту, дающую огромный простор для исследовательской и прочей деятельности. И сейчас есть множество аналогов. WinHex, например.
В этой статье я расскажу как написать свой простой редактор диска. Нужную функциональность каждый сможет добавить сам, я покажу основы.
Для начала разберемся как происходит само чтение диска. Проще всего это делать в Windows 2000/XP (с правами администратора, конечно). Работа с жестким диском в этих операционных системах производится путем открытия диска как файла с помощью функции CreateFile и указания диска или раздела по схеме Device Namespace
(открывается физический диск - '\\.\PHYSICALDRIVE<n>'), полученный хэндл в дальнейшем используется для работы с диском с помощью функций ReadFile, WriteFile и DeviceIoControl.
// Drive - номер диска (нумерация с нуля).
hFile := CreateFile(PChar('\\.\PhysicalDrive'+IntToStr(Drive)), GENERIC_READ, FILE_SHARE_READ or FILE_SHARE_WRITE,nil,OPEN_EXISTING,0,0); if hFile = INVALID_HANDLE_VALUE then Exit;
Таким образом, мы можем воспринимать физический диск как единый файл. Второе важное, что стоит сделать - это получить информацию о геометрии диска.
const
IOCTL_DISK_GET_DRIVE_GEOMETRY = $70000;
type
TDiskGeometry = packed record
Cylinders: Int64; // количество цилиндров
MediaType: DWORD; // тип носителя
TracksPerCylinder: DWORD; // дорожек на цилиндре
SectorsPerTrack: DWORD; // секторов на дорожке
BytesPerSector: DWORD; // байт в секторе
end;
Result := DeviceIoControl(hFile,IOCTL_DISK_GET_DRIVE_GEOMETRY,nil,0, @DiskGeometry,SizeOf(TDiskGeometry),junk,nil) and (junk = SizeOf(TDiskGeometry));
Функция возвращает True, если операция прошла успешно, и False в противном случае.
Теперь уже можно приступить к определению местоположения логических дисков на винчестере. Начать это нужно с чтения нулевого сектора физического диска. Он содержит MBR (Master Boot Record), а также Partition Table. Кстати, думаю, будет интересно сохранить содержимое MBR в файл и посмотреть программу загрузки каким-нибудь дизасмом. Но в данный момент нас интересует только Partition Table.
Эта таблица располагается в секторе по смещению $1be и состоит из четырех одинаковых элементов, каждый из которых описывает один раздел:
TPartitionTableEntry = packed record
BootIndicator: Byte; // $80, если активный (загрузочный) раздел
StartingHead: Byte; StartingCylAndSect: Word; SystemIndicator: Byte; EndingHead: Byte; EndingCylAndSect: Word; StartingSector: DWORD; // начальный сектор
NumberOfSects: DWORD; // количество секторов
end;
Соответственно, саму Partition Table можно представить как массив:
TPartitionTable = packed array [0..3] of TPartitionTableEntry;
Подробнее остановлюсь на этой структуре. Как видно из описания структур, Partition Table может содержать только четыре раздела. А так как, возможно, пользователю необходимо большее количество, было введено понятие "Extended Partition" (таким образом, разделы бывают Primary и Extended). Extended Partition - это раздел, который имеет свою собственную Partition Table (и, соответственно может содержать в себе еще четыре раздела). Extended Partition содержит логические диски. Тип раздела определяется полем SystemIndicator. Оно содержит информацию о файловой системе логического диска, либо 5 (или $f), если это Extended Partition.
Примеры значений поля SystemIndicator:
01 - FAT12 04 - FAT16 05 - EXTENDED 06 - FAT16 07 - NTFS 0B - FAT32 0F - EXTENDED
Теперь можно приступить к разбору структуры логических дисков. Сейчас уже нам пригодится функция ReadSectors.
// так как диск для нас - это единый файл, то для перемещения по нему // с помощью SetFilePointer понадобится 64хразрядная арифметика
function __Mul(a,b: DWORD; var HiDWORD: DWORD): DWORD; // Result = LoDWORD asm
mul edx mov [ecx],edx end;
function ReadSectors(DriveNumber: Byte; StartingSector, SectorCount: DWORD; Buffer: Pointer; BytesPerSector: DWORD = 512): DWORD; var
hFile: THandle; br,TmpLo,TmpHi: DWORD; begin
Result := 0; hFile := CreateFile(PChar('\\.\PhysicalDrive'+IntToStr(DriveNumber)), GENERIC_READ,FILE_SHARE_READ,nil,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0); if hFile = INVALID_HANDLE_VALUE then Exit; TmpLo := __Mul(StartingSector,BytesPerSector,TmpHi); if SetFilePointer(hFile,TmpLo,@TmpHi,FILE_BEGIN) = TmpLo then
begin
SectorCount := SectorCount*BytesPerSector; if ReadFile(hFile,Buffer^,SectorCount,br,nil) then Result := br; end; CloseHandle(hFile); end;
И, заодно, функция для записи:
function WriteSectors(DriveNumber: Byte; StartingSector, SectorCount: DWORD; Buffer: Pointer; BytesPerSector: DWORD = 512): DWORD; var
hFile: THandle; bw,TmpLo,TmpHi: DWORD; begin
Result := 0; hFile := CreateFile(PChar('\\.\PhysicalDrive'+IntToStr(DriveNumber)), GENERIC_WRITE,FILE_SHARE_READ,nil,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0); if hFile = INVALID_HANDLE_VALUE then Exit; TmpLo := __Mul(StartingSector,BytesPerSector,TmpHi); if SetFilePointer(hFile,TmpLo,@TmpHi,FILE_BEGIN) = TmpLo then
begin
SectorCount := SectorCount*BytesPerSector; if WriteFile(hFile,Buffer^,SectorCount,bw,nil) then Result := bw; end; CloseHandle(hFile); end;
Функции возвращает количество прочитаных (или записаных) байт. Для хранения информации о разделах объявим дополнительную структуру:
PDriveInfo = ^TDriveInfo; TDriveInfo = record
PartitionTable: TPartitionTable; LogicalDrives: array [0..3] of PDriveInfo; end;
Ну а теперь сам код разбора структуры диска:
const
PartitionTableOffset = $1be; ExtendedPartitions = [5,$f];
var
MainExPartOffset: DWORD = 0;
function GetDriveInfo(DriveNumber: Byte; DriveInfo: PDriveInfo; StartingSector: DWORD; BytesPerSector: DWORD = 512): Boolean; var
buf: array of Byte; CurExPartOffset: DWORD; i: Integer; begin
SetLength(buf,BytesPerSector); // читаем сектор в буфер
if ReadSectors(DriveNumber,MainExPartOffset+StartingSector,1,@buf[0]) = 0 then
begin
Result := False; Exit; end; // заполняем структуру DriveInfo.PartitionTable
Move(buf[PartitionTableOffset],DriveInfo.PartitionTable,SizeOf(TPartitionTable)); Finalize(buf); // буфер больше не нужен
Result := True; for i := 0 to 3 do // для каждой записи в Partition Table
if DriveInfo.PartitionTable[i].SystemIndicator in ExtendedPartitions then
begin
New(DriveInfo.LogicalDrives[I]); if MainExPartOffset = 0 then
begin
MainExPartOffset := DriveInfo.PartitionTable[I].StartingSector; CurExPartOffset := 0; end else CurExPartOffset := DriveInfo.PartitionTable[I].StartingSector; Result := Result and GetDriveInfo(DriveNumber,DriveInfo.LogicalDrives[I], CurExPartOffset); end else DriveInfo.LogicalDrives[I] := nil; end;
Функция заполняет структуру DriveInfo и возвращает True, если операция прошла успешно, или False в противном случае.
Теперь у нас есть такая полезная информация о разделах как начальный сектор, общее количество секторов, а также файловая система.
В нулевом секторе каждого основного раздела находится BIOS Parameter Block, содержаший такую информацию как название файловой системы, количество секторов в кластере и т.д. А также программа-загрузчик (сохраняем сектор в файл и смотрим дизасмом).
Теперь, когда мы закончили с теоретической частью, можно приступить к реализации редактора.
С чтением и записью информации мы уже разобрались. Теперь займемся ее отображением. Отображать содержимое выбранного сектора удобнее всего в компоненте TStringGrid.
Так как TStringGrid отображает в своих ячейках текст, а мы имеем в буфере двоичные данные, нам понадобятся функции для преобразования.
К счастью, в Delphi они уже есть (IntToHex и StrToInt) и остается их только правильно использовать. StrToInt можно использовать для преобразования строки, содержащей шестнадцатиричное число в Integer, если дописать впереди символ $.
Например, StrToInt('$FF');
Полный код программы, демонстирующей описанное в статье . Программа умеет показывать структуру логических дисков, выводить на экран содержимое указанного сектора, а также позволяет сохранять дамп выделенного сектора в файл. Реализация возможности редактирования диска в программе особого труда не составит.
DLL и Дельфи
,
Думаю многие знают, что такое DLL (dynamic link library - динамические библиотеки). У библиотек есть немало преимуществ, достаточно веских, что бы их использовать. В этой статье мы научимся создавать и использовать динамические библиотеки в своих проектах.
Зачем они нужны
А зачем эти самые библиотеки мне нужны? - спросите вы. Ну я не знаю, может они вам вообще не нужны. А может и жизненно необходимы. Перечислю возможности и преимущества библиотек:
Универсальность. Любой программист, зная описания и имена функций, находящихся в библиотеке, может использовать их.
Удобность отладки. Вы можете разместить несколько важных функций в библиотеке и обьявить их в программе. При этом вы будете работать с библиотекой, отлаживая эти функций, не трогая основную программу.
Хранилища ресурсов.. В DLL можно хранить ресурсы, такие как рисунки, формы, иконки меню, и т.д.
Взгляд на будущее. С помощью библиотек можно легко создавать плагины, расширяющие стандартные возможности программы. Т.е. можно не выпускать различные версии программы, а выпускать плагины или модифицированные (например с исправленными ошибками) версии библиотек.
Совместное использование. Если библиотека загружена, то её могут использовать и другие приложения.
Экономия ресурсов. Библиотеку можно загрузить и выгрузить тогда, когда это действительно необходимо. Например, программа в нужный момент загрузила DLL, вызвала функцию, сделала работу и выгрузила библиотеку до следующего раза. Налицо экономия памяти
Вобщем DLL - зверь полезный и очень даже дружелюбный.
Структура динамической библиотеки
Что бы создать библиотеку в Delphi6 выберите File -> New -> Other и в появившемся окне выберите DLL Wizard. Дельфи сгенерирует шаблон для библиотеки:
library Project;
{ 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.
В комментарии указывается на необходимость вставить ссылку на модуль ShareMem, если библиотека экспортирует длинные строки в параметрах обращения к подпрограммам или как результат функций. Эта ссылка должна быть первой как в предложении uses библиотеки, так и в uses файла проекта программы, которая использует эту библиотеку. Если подпрограммы библиотеки экспортируют строки ShortString или PChar, ссылаются на ShareMem не обязательно. Что бы не возникало недоразумений в своих библиотеках я рекомендую вместо типа String пользоваться PChar, а по необходимости конвертируйте типы функциями PChar (конветирует из String в PChar) и StrPas (конвертирует из PChar в String).
Структура библиотеки похожа на структуру обычного модуля. Теперь создайте библиотеку с таким текстом:
library Project2;
uses
SysUtils,
Classes;
function MyFunc(num1, num2, Errcode : Integer; Operation : PChar) : Integer; stdcall;
begin
try
if Operation="plus" then
Result := num1+num2;
if Operation="minus" then
Result := num1-num2;
if Operation="multiply" then
Result := num1*num2;
if Operation="div" then
Result := num1 div num2;
if Operation="mod" then
Result := num1 mod num2;
except Result := Errcode;
end;
end;
exports
MyFunc INDEX 1 NAME "MathFunc";
begin
end.
Сохраните это все куда нибудь и скомпилируйте (Ctrl+F9)
Это будет демонстрационная библиотека, на которой я буду показывать различные приемы работы с DLL. Но для начала давайте рассмотрим текст этой библиотеки.
function MyFunc(num1, num2, Errcode : Integer; Operation : PChar) : Integer; - это обычная функция, возвращающая целое число. Основываясь на параметре Operation функция решает, какую операцию сделать над операндами num1 и num2. В случае ошибки она возвращает переданный ей параметр Errcode. Т.е. в программе можно будет проанализировать, возникла ли ошибка во время исполнения функции.
stdcall указывает на то, что функция будет вызываться "обычным" способом, т.е. программы, написанные на других языках тоже смогут пользоваться библиотекой. Можно использовать - "register", предназначенным только для использования программами, написанными в среде дельфи, но тогда программы, написанные не в дельфи не смогут обращаться к этой функции.
exports
MyFunc INDEX 1 NAME "MathFunc";
Раздел Exports помогает компилятору и компоновщику создать специальный заголовок DLL-модуля, в котором перечисляются имена подпрограмм и адреса их точек входа. В DLL может быть несколько списков Exports, но перечисляемые в них подпрограммы должны быть описаны где-то выше по тексту библиотеки. Помимо имени подпрограммы в заголовок DLL помещается также ее порядковый
номер (INDEX), точнее, присвоенный ей целочисленный индекс. Это позволяет вызывающей программе ссылаться не на имя, а на индекс подпрограммы и тем самым уменьшить затраты времени на установление с ней связи. Индекс присваивается подпрограмме по порядку ее появления в списках Exports: первая подпрограмма в первом списке получает индекс 0, следующая 1 и т. д.
Программист может изменить умалчиваемую индексацию и явно указать индекс подпрограммы, добавив за ее именем в списке Exports слово index и целое число в диапазоне от 0 до 32767. Помимо индекса можно указать также и произвольное (NAME) имя функции.
Надеюсь, я понятно обьяснил ;) Вобщем наша демонстрационная библиотека готова. Теперь давайте научимся пользоваться библиотечными функциями
Использование библиотечных функций
Использовать функции из библиотеки можно двумя способами:
1. Привязка библиотеки к программе (статическая загрузка)
Недостатки:
- нет эффекта экономии ресурсов (библиотека загружается при запуске программы и выгружается при завершении программы)
- при отсутствии хотя бы одной из необходимых библиотек в папке с программой, либо в папке $windir$/system программа не запускается и выдает сообщение об ошибке
- при отсутствии хотя бы одной из необходимых функций в библиотеке при запуске программа выдает сообщение об ошибке и не запускается
Преимущества:
- легкость использования
У этого способа много недостатков. Но все же он будет полезен начинающим программистам. Для использования функций или процедур из библиотеки таким способом нужно всего лишь в разделе implementation указать имя функции или процедуры примерно так:
//если функция
function FunctionName(Par1: Par1Type; Par2: Par2Type; ParN : ParNType): ReturnType; stdcall; external "MyDLL.dll" name "FunctionName" index FunctionIndex;
//если процедура
procedure ProcedureName(Par1: Par1Type; Par2: Par2Type; ...); stdcall; external "MyDLL.dll" name "ProcedureName" index ProcIndex;
Рассмотрим обьявление функции.
function FunctionName(Par1: Par1Type; Par2: Par2Type; ParN : ParNType): ReturnType; - Это собственно обьявление функции
external "MyDLL.dll" эта директива указывает на имя библиотеки, из которой будет вызвана функция (в нашем случае это MyDLL.dll)
name "FunctionName" необьязательная директива, которая указывает на имя функции в библиотеке; используется для повышения скорости доступа к функциям (имя определяется внутри библиотеки)
index FunctionIndex тоже необьязательная директива, использующаяся для ускорения доступа к функциям; указывает на индекс функции (индекс обьявляется в самой библиотеке).
Рассматривать обьявление процедуры не имеет смысла, т.к. процедурв вызывается точно так же (за исключением того, что у процедура ничего не возвращает). Вот и все! Теперь можно пользоваться обьявленой функцие в пределах модуля, в котором она была обьявлена.
Рассмотрим пример на основе нашей демонстрационной библиотеки, которую мы скомпилировали выше.
Создайте новый проект Project1 и на его форму поместите четыре поля Edit. Присвойте им такие имена: Num1Edit, Num2Edit, OpEdit, ResultEdit. Так же поместите одну кнопку, имя которой значения не имеет. В разделе implementation обьявите функцию:
implementation
function MyFunc(num1, num2, Errcode : Integer; Operation : PChar) : Integer; stdcall; external "Project2.dll" name "MathFunc" index 1;
А обработчик единственной кнопки приведите к примерно такому виду:
procedure TForm1.DoItButtonClick(Sender: TObject);
const
Errcode : Integer=978987;//код ошибки - может быть абсолютно любым.
var
Num1, Num2, Result_ : Integer;//для проверки чисел
Operation : String;//операция, для передачи параметра функции
begin
try //прежде чем передать числа
Num1 := StrToInt(Num1Edit.Text); //функции проверим их
Num2 := StrToInt(Num2Edit.Text);
except
Num1Edit.Text := "0";
Num2Edit.Text := "0";
ResultEdit.Text := "Введите целые ЧИСЛA";
EXIT;
end;
Operation := OpEdit.Text; //также проверим, введена ли правильная команда.
if (Operation<>"plus")and(Operation<>"minus")and(Operation<>"multiply")
and(Operation<>"div")and(Operation<>"mod") then
begin
ResultEdit.Text := "Введите корректную команду";
Exit;
end;
Result_ := MyFunc(Num1, Num2, Errcode, PChar(Operation)); //использование библиотечной функции
if Result_=Errcode then //если функция возвратила код ошибки то
begin //то сообщаем об этом.
ResultEdit.Text := "ОШИБКА";
EXIT;
end
else //а если результат отличный от кода ошибки
ResultEdit.Text := IntToStr(Result_);//то выводим его
end;
В комментариях к коду все подробно расписано и вопросов я думаю не возникнет. А если же возникли, то пишите мне: или обращайтесь на форуме сайта .
Обратите внимание, что мы используем функцию из библиотеки так же, как и если она была бы написана в модуле. Ещё раз повторяю, что при привязке библиотеки к программе функцию можно использовать только в тех модулях, в которых она была обьявлена. Вот вам мини калькулятор, который работает на (хотел было сказать на батарейках) DLL.
2. Динамическая загрузка
Недостатки:
- громоздкость и сложность кода
- функции библиотеки доступны только тогда, когда библиотека загружена в память
Преимущества:
- начисто лишен всех недостатков первого способа + некоторые другие преимущества перед первым способом
Этот способ довольно сложен, особенно для новичков. Но преимуществ перед первым способом у него куда больше. Для работы с динамически загружаемыми библиотеками просто необходимо знать три WinAPI функции: LoadLibrary, GetProcAddress И FreeLibrary.
LoadLibrary(LibFileName: PChar) - загружает библиотеку LibFileName в память. Если библиотека загружена удачно, то функция возвращает дескриптор (THandle) DLL в памяти.
GetProcAddress(Module: THandle; ProcName: PChar) - находит точку входа в функцию ProcName. Внимание! Здесь нужно указать NAME функции, а не её название. Если функция найдена, то функция GetProcAddress возвращает дескриптор (TFarProc) функции в загруженной DLL.
FreeLibrary(LibModule: THandle) - выгружает библиотеку LibModule. При этом вся занятая этой библиотекой память освобождается. Следует заметить, что после вызова этой процедуры функции данной библиотеки больше недоступны и обращение к ним вызовет исключение.
Для того, что бы динамически загрузить функцию из библиотеки, то необходимо её обьявить в разделе var:
MyFunc: function(num1, num2, Errcode : Integer; Operation : PChar) : Integer; stdcall;
Также нужно обьявить переменную типа THandle. "На пальцах" не обьяснишь, поэтому давайте рассмотрим пример динамической загрузки DLL на основе нашей демонстрационной библиотеки.
Откройте предыдущий проект с демонстрацией статическо загрузки. В разделе var обьявите пару новых переменных:
LibHandle: THandle;
MyFunc: function(num1, num2, Errcode : Integer; Operation : PChar) : Integer; stdcall;
Обработчик кнопки приведите к такому виду:
procedure TForm1.DoItButtonClick(Sender: TObject);
const
Errcode : Integer=978987;//код ошибки - может быть абсолютно любым.
var
Num1, Num2, Result_ : Integer;//для проверки чисел
Operation : String;//операция, для передачи параметра функции
begin
try //прежде чем передать числа
Num1 := StrToInt(Num1Edit.Text); //функции проверим их
Num2 := StrToInt(Num2Edit.Text);
except
Num1Edit.Text := "0";
Num2Edit.Text := "0";
ResultEdit.Text := "Введите ЧИСЛA";
EXIT;
end;
Operation := OpEdit.Text; //также проверим, введена ли правильная команда.
if (Operation<>"plus")and(Operation<>"minus")and(Operation<>"multiply")
and(Operation<>"div")and(Operation<>"mod") then
begin
ResultEdit.Text := "Введите корректную команду";
Exit;
end;
//до этого момента код остался без изменений.
@MyFunc := nil; //очищаем адрес функции
LibHandle := LoadLibrary("Project2.dll");//пытаемся загрузить библиотеку
if LibHandle >= 32 then
begin //если все прошло успешно то
@MyFunc := GetProcAddress(LibHandle, "MathFunc");//пытаемся найти адрес функции
if @MyFunc <> nil then //если адрес найден (функция существует в библиотеке)
Result_ := MyFunc(Num1, Num2, Errcode, PChar(Operation)); //использование библиотечной функции
if Result_=Errcode then //если функция возвратила код ошибки то
begin //то сообщаем об этом.
ResultEdit.Text := "ОШИБКА";
EXIT;
end
else //а если результат отличный от кода ошибки
ResultEdit.Text := IntToStr(Result_);//то выводим его}
end;
end;
Изменившуюся часть кода я обильно полил комментариями, так что думаю вопросов не возникнет. Но если же они и здесь все таки возникли, то советую купить книжку по дельфи и написать письмо мне: или обращайтесь на форуме сайта . Исходник этого проекта с откомпилированными библиотекой и программой можно скачать
Заключение
В этой статье мы коснулись лишь основных аспектов программирования с применением динамически-подключаемых библиотек. А ведь в DLL можно хранить всякие картинки и даже формы! С помощью них удобно создавать всякие плагины. Но это уже совсем другая история...
Цвет в формате
К сожалению, не лучше обстоит дело и с цветом в форматах. Т.е. цвет в Delphi можно задавать только по-русски: R.NumberFormat := 'Основной;[красный]-Основной';
Перечень цветов по-русски, которые можно задавать в формате: черный, красный, зеленый, синий, фиолетовый, желтый, белый. Список небогатый.
Формат чисел. Разделители. (DecimalSeparator, ThousendSeparator)
Почитайте "диалог" на Круглом столе - вроде бы все понятно ("а все и делов то в запятой")! А нет, не все! В "International" (в русском "Язык и стандарты") можно установить любые DecimalSeparator и ThousandsSeparator, отличные от принятых по-умолчанию фирмой Microsoft для русской версии Windows. Я, например, всегда меняю принятые по-умолчанию десятичную точку "," на "." и разделитель тысяч с " " (пробел) на "'" (апостроф, как в калькуляторе). Так формат "# ##0,00" у меня работать не будет...
И это еще не все! Заходим в настройки Excel'я "Сервис/Параметры" переходим на закладку "Международные" и видим опять "Разделитель целой и дробной части", "Разделитель разрядов" и чекбокс "Использовать системные разделители". Т.е. использование системных разделителей не может гарантировать правильного применения при форматировании чисел в Excel'е. Решение: использовать свойство ExcelApplication.International (о нем дальше). Причем, даже при установленном свойстве ExcelApplication.UseSystemSeparators = False и отличных от системных ExcelApplication.DecimalSeparator и ExcelApplication.ThousandsSeparator, ExcelApplication.International отработает корректно.
Далее рассмотрим примеры работы (или не работы), приняв "стандартные" настройки для русских Windows:
Код на VBA (эталон):
Sub Test2() Dim R As Range Set R = Range("a1") R.Clear R.Value = 1234567.89
R.NumberFormat = "#,##0.00" ' работает
R.NumberFormatLocal = "# ##0,00" '
работает для стандартных настроек
R.NumberFormat = "# ##0,00" ' не работает
R.NumberFormatLocal = "#,##0.00" ' не работает
Set R = Nothing End Sub
Код на Delphi:
R := ASheet.Range['A1', EmptyParam]; R.Value2 := 1234567.89; R.NumberFormat := '#,##0.00'; // не работает
R.NumberFormatLocal := '# ##0,00'; //
работает для стандартных настроек
R.NumberFormat := '# ##0,00'; //
работает для стандартных настроек
Примечание:
в примерах значения записываются в Value2 для предотвращения форматирования "на лету" самим Excel'ем. Так число 123.45, записанное в Value будет автоматически отформатировано Excel'ем в формат валюты, а присвоение Value = Date будет автоматически переведено в формат даты. Запись в Value2 "воспринимает" значение как Double. Подробнее смотрите в справке VBA для Excel'я.
Решения (с использованием ExcelApplication.International):
Для получения формата даты можно написать функцию:
function XL_GetShortDateFormat(XLApp: ExcelApplication): String; var d, m, y: Integer; begin
if XLApp.International[xlDayLeadingZero, lcid] then d := 2 else d := 1; if XLApp.International[xlMonthLeadingZero, lcid] then m := 2 else m := 1; if XLApp.International[xl4DigitYears, lcid] then y := 4 else y := 2; Result := Format('%1:s%0:s%2:s%0:s%3:s', [ DateSeparator, StringOfChar(VarToStr(XLApp.International
[xlDayCode, lcid])[1], d), StringOfChar(VarToStr(XLApp.International
[xlMonthCode, lcid])[1], m), StringOfChar(VarToStr(XLApp.International
[xlYearCode, lcid])[1], y) ]); end;
Для формата чисел:
function XL_GetNumberFormat
(XLApp: ExcelApplication): String; begin
Result := Format('#%s##0%s%s', [ XLApp.International[xlThousandsSeparator, lcid], XLApp.International[xlDecimalSeparator, lcid], StringOfChar('0', Integer
(XLApp.International[xlCurrencyDigits, lcid])) ]); end;
Для формата валюты:
function XL_GetCurrencyFormat(XLApp: ExcelApplication): String; begin
Result := Format('%s "%s"', [ XL_GetNumberFormat(XLApp), XLApp.International[xlCurrencyCode, lcid] ]); end;
Тот же принцип можно применить к времени и другим типам. Также смотрите другие индексы для свойства International (их там много) в справке VBA. Например, получить "основной" (general) формат можно так:
GenFmt := XL.International[xlGeneralFormatName, lcid];
Примечание:
установить основной формат еще можно установить, записав в NumberFormat "пустую" строку, т.е. указать, что нет форматирования для чисел (даты):
Range.NumberFormat := '';
Формат даты
Код на VBA (эталон):
Sub Test1() Dim R As Range Set R = Range("a1") R.Clear ' очистим формулы и форматы
R.Value2 = Date ' запишем текущую дату
R.NumberFormat = "d/mm/yy" ' работает
R.NumberFormatLocal = "ДД.ММ.ГГ" ' работает
' дальше не работает
R.NumberFormat = "ДД.ММ.ГГ" ' не работает
R.NumberFormatLocal = "d/mm/yy" ' ОШИБКА!
Set R = Nothing End Sub
Код на Delphi:
R := ASheet.Range['A1', EmptyParam]; R.Value2 := Date; R.NumberFormat := 'd/mm/yy'; // ОШИБКА!
R.NumberFormat := 'ДД.ММ.ГГ'; // работает
R.NumberFormatLocal := 'ДД.ММ.ГГ'; // работает
R.NumberFormatLocal := 'd/mm/yy'; // ОШИБКА
Формулы на листе
К счастью, работа со свойствами Formula и FormulaLocal в VBA и Delphi идентична и соответствуют своим названиям. Хочется отметить только один нюанс (это, кстати, действительно и для VBA) - при написании "русских" формул нужно учитывать системную переменную ListSeparator. Так, если на другом компьютере пользователь изменит его со стандартного для русской версии Windows символа ";" на "," (например, как это делаю я :), то присвоение Range.FormulaLocal := '=округл(A1*B1; 2)'; вызовет ошибку! Поэтому, с учетом "разделителя элементов списка" нужно писать так:
Range.FormulaLocal := Format('=округл(A1*B1%s 2)',
[ListSeparator]); или Range.Formula := '=round(A1*B1, 2)';
Здесь приятней и проще пользоваться английскими формулами. Но, иногда, существует необходимость писать формулы из вариантного массива…
Примечание:
системные переменные ListSeparator, DateSeparator описаны в модуле System.
Особенности работы с "русским" Excel'ем
Александр Шабля,
Написанное приложение, прекрасно работающие с Excel'ем на собственном компьютере, часто, после переноса приложения на другой компьютер, оказывается неработоспособным! Отчего так происходит? В этой статья я собираюсь описать разницу в работе русской версии Excel'я из VBA и через COM интерфейс (библиотеку типов, TLB) из Delphi. Почему возникли расхождения? Ответа на эти вопросы у Microsoft я не нашел…
Примечание:
сравнивались только русская и английская (American English) версии Excel с номером версии 9.0 (MS Office 2000) и выше. Другие версии не рассматривались.
Описание типов объектов, применяемых в примерах:
XL: TExcelApplication; WB: TExcelWorkbook; ASheet: TExcelWorksheet; R: Range; // ExcelRange - для Delphi7
Используемые в примерах "дополнительные" модули:
OleServer, Excel2000, Office2000 из стандартной поставки Delphi Enterprise версии 6 и выше.
Работа со свойством объекта Range NumberFormat
NumberFormat и NumberFormatLocal четко работают в VBA и полностью соответствуют своему содержанию в названиях, но только не при работе из Delphi. В Excel2000.pas (D7) они описаны как
ExcelRange = dispinterface
['{00020846-0000-0000-C000-000000000046}'] ... property NumberFormat: OleVariant dispid 193; property NumberFormatLocal: OleVariant dispid 1097;
Но, при попытке записи форматов из Delphi, выясняется, что NumberFormat и NumberFormatLocal ведут себя идентично, причем NumberFormat соответствует NumberFormatLocal (лучше было бы наоборот :). Т.е. в русской версии все форматы нужно писать "по-русски" (можно прямо в NumberFormat, в VBA - нельзя).
Создание колонтитулов
Давайте запустим запись макроса создания колонтитула (меню в Excel "Сервис/Макрос/Начать запись…"). Теперь откроем параметры страницы (меню "Файл/Параметры страницы…"). Создадим центральный нижний колонтитул "Лист &[Страница] из &[Страниц]" шрифтом "Arial", "полужирный" и размером 8pt. Слова "Лист" и "из" с начертанием "обычный". После "сокращения" макроса получим:
Sub Макрос1() ' ActiveSheet.PageSetup.CenterFooter = _ "&""Arial""&8Лист &""Arial,полужирный""&P" & _ "&""Arial,обычный"" из &""Arial,полужирный""&N" End Sub
Т.е. при выводе на печать мы хотим, чтоб в нижний колонтитул по центру выводился текст, к примеру "Лист 1 из 5".
Примечание:
если вы хотите увидеть работу вашего макроса в действии (чтоб работал PrintPreview), обязательно внесите на лист хоть какие-нибудь данные.
Внимание! Суммарная длина текста в нижнем или верхнем (левый + по_центру + правый) колонтитулах не должна превышать 250 символов (как и в ячейке).
Вроде бы все ясно, осталось только переписать его под Delphi:
ASheet.PageSetup.CenterFooter := '&"Arial"&8Лист &"Arial,полужирный"&P' + '&"Arial,обычный" из &"Arial,полужирный"&N';
Проверяем в Excel'е "Предварительный просмотр" - оба, и не работает! А как же должно работать?
Припоминая русификацию еще Excel'я 4-й версии, напишем русские эквиваленты:
ASheet.PageSetup.CenterFooter := '&"Arial"&8Лист &"Arial,полужирный"&С' + //
Страница - Page
'&"Arial,обычный" из &"Arial,полужирный"&К'; //
Количество - Number
Сработало! Ну, и теперь добавим распознавание русской версии:
if XL.LanguageSettings.LanguageID[msoLanguageIDUI] = $0419
then ASheet.PageSetup.CenterFooter := //
русские коды форматирования
'&"Arial"&8Лист &"Arial,полужирный"&С' + '&"Arial,обычный" из &"Arial,полужирный"&К'
else ASheet.PageSetup.CenterFooter := //
английские коды форматирования
'&"Arial"&8Лист &"Arial,bold"&P' + '&"Arial,normal" из &"Arial,bold"&N';
Вывод: при вставке кодов форматирования из Delphi в русский Excel должны вставляться только русские коды форматирования. А где их взять? Вот список кодов форматирования, полученных методом пробы:
Format code | Русский код форматирования | Описание |
&L | &Л | Выравнивает последующие символы влево. |
&C | &Ц | -"- по центру. |
&R | &П | -"- вправо. |
&E | &Й | Двойное подчеркивание (double-underline) вкл. или выкл. |
&X | &Р | Верхний индекс (superscript) вкл. или выкл. |
&Y | &И | Нижний индекс (subscript) вкл. или выкл. |
&B | &Ж | Жирный (bold) вкл. или выкл. |
&I | &Н | Наклонный (italic) вкл. или выкл. |
&U | &Ч | Подчеркнутый (underline) вкл. или выкл. |
&S | &З | Зачеркнутый (strikethrough) вкл. или выкл. |
&D | &Д | Текущая дата. |
&T | &В | Текущее время. |
&F | &Ф | Имя документа (книги). |
&A | &Я | Имя листа. |
&P | &С | Номер страницы. |
&P+number | &С+число | Номер страницы + указанное число. |
&P-number | &С-число | Номер страницы - указанное число. |
&& | && | Одиночный ampersand. |
& "fontname" | &"ИмяШрифта[,начертание]" | Печать указанным шрифтом [и начертанием] (не обязательно). Обязательно указывать в двойных кавычках. |
&nn | &nn | Печать шрифтом указанного размера. |
&N | &К | Общее количество страниц. |
И еще один опыт:
ASheet.PageSetup.CenterFooter := '&"Arial"&8Лист &"Arial,bold"&С&"Arial,normal"
из &"Arial,bold"&К';
Работает! Т.е. начертания (Style у класса TFont в Delphi) шрифтов можно уверенно писать по-английски. Или заменить на коды форматирования:
ASheet.PageSetup.CenterFooter :=
'&"Arial"&8Лист &Ж&С&Ж из &Ж&К';
Примечание:
для перевода строки в колонтитуле или ячейке используйте симол LF, ASCI код 10 (
#10):
ASheet.PageSetup.CenterFooter := 'Первая строка'
#10'Вторая строка'; ASheet.Range['A1', EmptyParam].Value := 'Первая строка'
#10'Вторая строка';
У вас русская версия Excel?
Определить наличие русской версии Excel возможно так:
if XL.LanguageSettings.LanguageID[msoLanguageIDUI] = 1049
{или $0419}
then { Excel имеет русский интерфейс пользователя };
Английская версия Excel (English United States) вернет 1033 (или $0409), немецкая (German Standard) - $0407. Значения соответствуют LCID, описанным в MS SDK Help "Language Identifiers". LCID интерфейса пользователя и файла Excel.exe файла может быть неодинаковым (например, после установки MUI). Константа msoLanguageIDUI находится в модуле Office2000.pas и описана так:
const
msoLanguageIDUI = $00000002;
Примечание:
в Office97 свойство LanguageSettings отсутствует
Далее мы рассмотрим приемы работы с "русским" Excel'ем.
ем из Delphi необходимо соблюдать
При работе с русским Excel' ем из Delphi необходимо соблюдать следующие правила:
при задании форматов использовать только русские форматы чисел и даты;
при цветном форматировании чисел указывать цвета только на русском языке;
при записи формул из вариантного массива использовать только русские формулы;
при создании колонтитулов использовать только русские коды форматирования;
для совместимости с английской версий необходимо проверять LCID интрефейса пользователя Excel'я и действовать соответственно.
Мне не удалось найти документацию, касающуюся моментов описанных выше. Весь материал построен чисто на собственном опыте. И еще: не было возможности проверить на полностью английских версиях Windows и Office.
Скачать проект: RusExcel.zip (4,6К)
Все примеры тестировались на Delphi 6, Delphi 7, на русских версиях WindowsXP + OfficeXP, Windows98SE + Office2000 SR?1.
Запись формул из Variant-ного массива
Запись в свойство Formula, FormulaLocal, Value, Value2 из Variant-ного массива идентична в русском Excel'е и при работе из Delphi. Но, если мы хотим вставлять формулы прямо из массива, все они должны быть только русскими! Вот здесь то и всплывает необходимость определения наличия русской версии Excel'я (впрочем, это уже касалось задания цвета в свойстве NumberFormat).
Код на VBA: Sub TestVariant()
Dim MyVar(2, 2) As Variant ' 3 строки, 3 колонки Dim R As Long, C As Byte
' первая строка MyVar(0, 0) = 10.72 MyVar(0, 1) = 3.05 ' MyVar(0, 2) = "=round(RC[-1]*RC[-2], 2)" ' ошибка #ИМЯ? MyVar(0, 2) = "=округл(RC[-1]*RC[-2]; 2)" '
работает для стандартных настроек ' вторая строка MyVar(1, 0) = 4.57 MyVar(1, 1) = 7.23 ' MyVar(1, 2) = "=round(A2*B2, 2)" ' ошибка #ИМЯ? MyVar(1, 2) = "=округл(A2*B2; 2)" '
работает для стандартных настроек ' итог ' MyVar(2, 2) = "=sum(C1:C2)" ' ошибка #ИМЯ? ' MyVar(2, 2) = "=сумм(C1:C2)" ' работает MyVar(2, 2) = "=сумм(R[-2]C:R[-1]C)" ' работает
With Range("A1:C3") .Clear ' чистим область ячеек
от формул и форматов .Value = MyVar ' работает ' .Value2 = MyVar ' работает ' .Formula = MyVar ' работает ' .FormulaLocal = MyVar ' работает End With
Код на Delphi (тут мы применим знание написания русских формул, описанный выше, а именно ListSeparator):
var
MyVar: Variant; IsRusXL: Boolean; begin
... MyVar := VarArrayCreate([0, 2, 0, 2], varVariant); //
3 строки, 3 колонки
// определим, русский ли у нас Excel
IsRusXL := XL.LanguageSettings.LanguageID[msoLanguageIDUI]
= $0419;
// первая строка массива
MyVar[0, 0] := 10.72; MyVar[0, 1] := 3.05; if IsRusXL // стиль R1C1
then MyVar[0, 2] := Format('=округл(RC[-1]*RC[-2]%s 2)',
[ListSeparator]) else MyVar[0, 2] := '=round(RC[-1]*RC[-2], 2)'; // вторая строка массива
MyVar[1, 0] := 4.57; MyVar[1, 1] := 7.23; if IsRusXL then MyVar[1, 2] := Format('=округл(A2*B2%s 2)',
[ListSeparator]) // стиль A1
else MyVar[1, 2] := '=round(A2*B2, 2)'; // итог
if IsRusXL then MyVar[2, 2] := '=сумм(C1:C2)' // '=сумм(R[-2]C:R[-1]C)'
else MyVar[2, 2] := '=sum(C1:C2)';
with ASheet.Range['A1:C3', EmptyParam] do begin
Clear; // Formula := MyVar; // работает
// FormulaLocal := MyVar; // работает
// FormulaR1C1 := MyVar; //
не работает, если есть ссылки в стиле A1
// FormulaR1C1Local := MyVar; //
не работает, если есть ссылки в стиле A1
// Value := MyVar; // работает
Value2 := MyVar; // работает
end; ...
Примечание:
из примера видно, что при записи из Variant-ного массива в Formula, FormulaLocal, Value, Value2 не имеет значения, какой стиль ссылок используется: A1 и R1C1 работают идентично. Но это не относится к свойствам FormulaR1C1 и FormulaR1C1Local, которые принимают формулы ТОЛЬКО в стиле R1C1.