Главная Случайная страница


Полезное:

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


Категории:

АрхитектураАстрономияБиологияГеографияГеологияИнформатикаИскусствоИсторияКулинарияКультураМаркетингМатематикаМедицинаМенеджментОхрана трудаПравоПроизводствоПсихологияРелигияСоциологияСпортТехникаФизикаФилософияХимияЭкологияЭкономикаЭлектроника






Создание DLL

Классы

 

В предыдущем примере мы рассмотрели организацию пространства имен на примере пространства город Kirov, который содержал улицы и дома. В пространстве мы объявляли переменные и функции, которые обращались к этим переменным. А что если нам потребовалось бы два таких пространства – например с одним мы бы производили какие либо действия, меняли значения переменных и т.п., а другой namespace Kirov оставался бы неизменным как эталон для последующего сравнения. Конечно, можно объявить два таких пространства, например Kirov1 и Kirov2, но это приведет к дублированию кода при их объявлении. А теперь представьте, что улиц внутри будет не две как у нас, а например 50, а позже при разработке программы пространств Kirov понадобится не два, а 100, или еще больше, что тогда будет? Тысячи и тысячи строк громоздкого однотипного кода.

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

 

ref class Kirov {

public:

int a;

};

 

int main(array<System::String ^> ^args)

{

Kirov k;

Console::WriteLine(k.a);

Console::ReadKey();

return 0;

}

 

Объявление класса в управляемом C++ осуществляется с помощью ключевого слова ref class. В отличие от namespace, у классов есть область видимости своего содержимого для остальной программы: public (доступно всем) и private (доступно только функциям, объявленным в самом классе). По-умолчанию, содержимое класса недоступно коду извне, поэтому использовалось ключевое слово public:

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

Объекты можно создавать статически, т.е. указав имя класса и имя переменной вслед за ним (как у обычных переменных), тогда доступ к полям класса будет осуществляться через точку «.»:

 

Kirov k;

Console::WriteLine(k.a);

 

или же, что предпочтительнее, и наиболее часто используется, создание объектов динамически по ссылке, доступ к полям через оператор ->

 

Kirov ^k = gcnew Kirov();

Console::WriteLine(k->a);

 

Ссылка в управляемом С++\CLI имеет тот же смысл, что и указатель в традиционном С++, только обозначается символом ^ вместо звездочки *, и хранит не адрес объекта в памяти, а идентификатор его расположения в «куче мусора» garbage collection библиотеки.NET. Использование менеджера garbage collector позволяет не заботится об очистке памяти, занятой объектами (в С++ приходилось использовать free() или delete), все объекты система будет освобождать сама.

Основное преимущество классов – совмещение данных и кода внутри одной переменной-объекта:

 

ref class Kirov {

public:

int a;

void print() {

Console::WriteLine(a);

}

};

 

int main(array<System::String ^> ^args)

{

Kirov ^k = gcnew Kirov();

k->print();

Console::ReadKey();

return 0;

}

 

Функции, объявленные внутри класса, называются методами. Если имя функции совпадает с именем класса, то такая функция называется конструктором, и она автоматически вызывается при создании нового объекта класса. Конструктор не возвращает никакого значения, даже void, но может принимать несколько значений как обычная функция, и используется для инициализации переменных в классе при создании объекта. Добавим в наш класс конструктор, позволяющий инициализировать переменную а заданным значением при создании объекта:

 

ref class Kirov {

public:

int a;

 

Kirov(int startvalue) {

a = startvalue;

}

 

void print() {

Console::WriteLine(a);

}

 

};

 

int main(array<System::String ^> ^args)

{

Kirov ^k = gcnew Kirov(777);

k->print();

Console::ReadKey();

return 0;

}

 

Следует отметить, что оператор gcnew как раз вызывает конструктор класса при создании объекта.

Часть переменных и методов в классе можно закрыть от доступа извне с помощью использования секции private:

 

ref class Kirov {

private:

String ^Closet;

public:

int Dom;

 

Kirov(int d, String ^s) {

Dom = d;

Closet = s;

}

 

void print() {

Console::WriteLine("Дом №{0}, тип санузла:{1}", Dom, Closet);

}

 

};

 

int main(array<System::String ^> ^args)

{

Kirov ^k = gcnew Kirov(5, "Фаянс");

// Console::WriteLine(k->Closet); ошибка доступа

k->print();

Console::ReadKey();

return 0;

}

 

Если попытаться напрямую обратиться к переменной Closet, объявленной как private, то компилятор выдаст ошибку «cannot access private member declared in class». Например, при создании новой формы Windows Form дизайнер Visual Studio помещает объекты, расположенные на форме (кнопки, поля ввода, метки и т.п.), в секцию private, поэтому доступ к элементам формы невозможен из других форм, или кода, не являющегося методом класса самой формы. Секции private и public можно чередовать в любой последовательности при объявлении класса и в любом количестве. Данные ключевые слова также можно указывать непосредственно перед именем класса, чтобы дать понять компилятору, возможно ли создания объектов этого класса из других модулей:

 

public ref class Kirov

 

По-умолчанию используется private, и объекты данного класса можно, проще говоря, создавать только в этом же CPP файле.

Зачастую классы содержат огромное количество переменных, объектов других классов и методов (например, те же формы), и, чтобы не загромождать объявление класса большим количеством кода, допускается описывать методы класса вне его самого:

 

ref class Kirov {

private:

String ^Closet;

public:

int Dom;

 

Kirov(int d, String ^s) {

Dom = d;

Closet = s;

}

 

void print();

};

 

void Kirov::print() {

Console::WriteLine("Дом №{0}, тип санузла:{1}", Dom, Closet);

}

 

 

int main(array<System::String ^> ^args)

{

Kirov ^k = gcnew Kirov(5, "Фаянс");

k->print();

k->a = 6;

k->print();

Console::ReadKey();

return 0;

}

 

Внутри самого класса остается только прототип метода void print();, а сам метод описывается за его пределами как обычная функция, но с указанием имени класса и символов:: перед ее именем void Kirov::print().

Оператор:: может использоваться не только для доступа к пространствам имен, но и для доступа к статическим переменным и методам классов без создания объектов этих классов. «Статическое» означает отсутствие выделения памяти, например статические переменные с заранее заданным значением:

 

ref class Kirov {

public:

static int a = 666;

static void method1() {

Console::WriteLine(a+1);

}

};

 

int main(array<System::String ^> ^args)

{

Console::WriteLine(Kirov::a);

Kirov::method1();

Console::ReadKey();

return 0;

}

 

К слову, функции WriteLine() и Readkey() являются статическими методами класса Console, описанного в пространстве имен System, и мы их тоже используем напрямую без неспосредственного создания объекта класса Console.

Разговор о классах в начале раздела начинался с того, что можно создавать большое количество объектов одного класса если потребуется:

 

ref class Kirov {

private:

String ^Closet;

public:

int Dom;

Kirov(int d, String ^s) {

Dom = d;

Closet = s;

}

void print();

};

 

void Kirov::print() {

Console::WriteLine("Дом №{0}, тип санузла:{1}", Dom, Closet);

}

 

typedef array<Kirov^> Towns; // новый тип данных

 

int main(array<System::String ^> ^args)

{

Towns ^T = gcnew Towns(20);

for (int i = 0; i < T->Length; i++) {

T[i] = gcnew Kirov(i, i%2?"Фаянс":"Фарфор");

}

for each (Kirov ^k in T) {

k->print();

}

Console::ReadKey();

return 0;

}

 

Сначала объявляется новый тип данных Towns с помощью ключевого слова typedef (аналогично стандартному С++) – массив объектов класса Kirov. Заметьте, что мы указали тип Kirov^, т.е. не просто объекты, а ссылки(указатели) на объекты этого класса. Затем объявляем переменную данного типа и выделяем память под массив в 20 элементов с помощью gcnew. После этого в цикле создаем объекты, меняя при этом в зависимости от четности номера тип «санузла» (оператор «условие? истина: ложь» здесь работает как и в языке С), и выводим на экран, с использованием цикла for each для итерации по элементам массива (нововведение в С++\CLI, в стандартном С++ такого оператора нет). Так как и объекты, и сам массив были созданы посредством gcnew, то очищать память не требуется.

Объектно-ориентированное программирование ООП в управляемом С++ поддерживает множество других приемов при работе, свойственных ООП: абстрагирование, наследование, перегрузка, полиморфизм и т.д., но рассмотрение данных вопросов выходит за рамки этого раздела. Дополнительную информацию можно найти в справочной литературе по программированию.

В заключение стоит отметить, что в управляемом С++\CLI платформы.NET все переменные и массивы являются объектами: int, String, double, array и т.д., у них есть свои методы для работы (например, мы использовали метод T->Length класса array чтобы получить размер массива в примере выше), и все они унаследованы от единого базового объекта System::Object. Поэтому если в описаниях стандартных функций во входных параметрах встречается тип Object^, то это означает, что фактически можно подставить на это место переменную любого типа. Например если посмотреть описание функции Console::Write() или просто набрать в коде

 

Console::Write(

 

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

 

 

Создание DLL

 

Dinamic-link library это библиотека, состоящая из кода, данных и ресурсов в отдельном файле с раширением dll, которая используется основной программой во время работы. Динамачиские библиотеки используют, например, для выделения части часто используемого различными программами кода в отдельный файл, как это сделано в случае с Win32 API. Также используется в больших проектах, когда частым изменениям подвержен лишь небольшой участок кода, и каждый раз перекомпилировать весь проект, а потом заново распространять его между пользователями нецелесообразно.

В платформозависимых языках программирования (C++, Delphi, Visual Basic и.т.п.) в dll файл помещаются функции, которые затем загружаются основной программой во время выполнения.

В платформонезависимой среде.NET внутри dll находится отдельный класс, объявленный в пространстве имен, и имя пространтсва имен является именем dll файла.

Сам DLL модуль запустить непосредственно нельзя, для этого используется хост-программа, которая подзагружает требуемый модуль и использует содержащиеся в нем код или данные.

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

Начнем с хост-программы. Создайте новый проект CLR \ Windows Forms:

 

 

В качестве имени проекта укажем «myprog», для имени группы проектов (solution name) «dlldemo».

Для ввода числа А воспользуемся элементом управления MaskedTextBox, позволяющим осуществлять ввод в заданном формате. Поместите его на форму, выделите указателем мыши, и нажмите на небольшую стрелку в правом верхнем углу, чтобы вызвать меню Set Mask.

 

 

В появившемся окне выберите формат Numeric для ввода 5-значных цифр:

 

 

 

После установки маски ввода, необходимо добавить еще одно такое же поле для ввода второго числа В. Можно проделать те же действия, что и с первым полем, или просто сделать копию из уже имеющегося поля. Для этого снова выделите его указателем мыши, нажмите Ctrl+C, затем Ctrl+V, и перетащите новое поле в заданную позицию (в этом случае маску устанавливать не надо, так как она уже будет скопирована).

Далее добавьте обычное поле TextBox для вывода результата, и кнопку:

 

 

Присвойте имена компонентам: поле ввода числа А «mtbA», числа В «mtbB», результата «tbResult». Для наименования компонентов часто используется сокращенная транскрипция, например MaskedTextBox как mtb, TextBox как tb, Button как btn, Label как lbl и.т.д. Это позволяет позже, при написании кода программы, точно знать с каким компонентом вы работаете. На кнопку пока действий назначать не будем. Запустите программу и удостоверьтесь, что она работает.

Хост-программа готова, приступаем к созданию библиотеки DLL, которая будет являться отдельным проектом внутри группы проектов solution. Нажмите правой кнопкой на имени группы проектов «dlldemo» и выберите меню Add \ New Project.

 

 

Как уже упоминалось выше, DLL модулями являются отдельные классы, поэтому в качестве типа проекта выберите CLR \ Class Library, назовите проект «mydll»:

 

 

Visual Studio для нового проекта сгенерирует следующий код:

 

// mydll.h

 

#pragma once

 

using namespace System;

 

namespace mydll {

 

public ref class Class1

{

// TODO: Add your methods for this class here.

};

}

Внутри пространства имен mydll, которое также является и именем создаваемого DLL файла, объявляется пустой класс Class1 с модификатором public, чтобы другие внешние модули (хост-программа) получили доступ к этому классу.

Комментарий «Add your methods for this class here» (добавьте методы для этого класса здесь) ясно дает понять, где нам надо писать свой код. Изменим имя класса на «MyClass» и добавим метод для расчета, для начала просто сумма (не забудьте про ключевое слово public перед методом, иначе он будет недоступен внешнему коду):

 

namespace mydll {

 

public ref class MyClass

{

// TODO: Add your methods for this class here.

public:

int Calculate(int a, int b) {

return a+b;

}

};

}

Откомпилируйте программу и удостоверьтесь что все в порядке.

Теперь, когда у нас в группе проектов не один проект, а два, активный проект выделяется жирным шрифтом. Активный (Startup) проект – тот, что будет запускать Visual Studio. Если в качестве активного выбрать проект, не являющийся исполняемым EXE файлом (правой кнопкой мыши на имени проекта, пункт меню Set as Startup Project), то будет выведено соответствующее сообщение.

 

Чтобы подключить DLL к основному проекту, щелкните правой кнопкой мыши по имени проекта «myprog» в окне Solution Explorer (рисунок выше), и выберите меню References:

 

Нажмите кнопку Add New Reference, и в появившемся окне на закладке Projects выберите mydll.

 

Осталось добавить код для кнопки вычисления в хост-программе:

 

private: System::Void button1_Click(System::Object^ sender, System::EventArgs^ e) {

using namespace mydll;

MyClass ^C = gcnew MyClass();

try {

int a = Int32::Parse(mtbA->Text);

int b = Int32::Parse(mtbB->Text);

int r = C->Calculate(a, b);

tbResult->Text = String::Format("{0}", r);

} catch (Exception ^ex) {

MessageBox::Show(ex->Message, "Ошибка", MessageBoxButtons::OK, MessageBoxIcon::Error);

}

}

 

Так как класс MyClass объявлен внутри пространства имен mydll, мы воспользовались ключевым словом using, иначе для создания класса пришлось бы писать mydll::MyClass ^C = gcnew mydll::MyClass();

Для преобразования строк из полей ввода в числа используется статический метод Parse() класса Int32, для обратного преобразования результата в строку статический метод Format() класса String (функция Console::WriteLine() также использует эту функцию, поэтому задание строки форматирования аналогичное).

Если в поля не будут занесены числа, то возникнет ошибка преобразования типов и функция Parse создаст исключение, которое будет перехвачено конструкцией try {} catch {} и выведено сообщение об ошибке (подробнее об исключениях в следующих разделах).

Запустите программу на выполнение для проверки.

 

DLL может содержать не только код, но также и формы. Доработаем программу так, чтобы перед вычислением появлялось окно с выбором действия – сложить или вычесть. Щелкните правой кнопкой мыши по имени проекта mydll в окне Solution Explorer, выберите Add \ New Item:

 

 

Назначим имя новой форме «frmDialog1».

На эту форму поместите компонент GroupBox (закладка Containers в панели ToolBox), а на него два компонента RadioButton. Имя первого «rbAdd», второго «rbSub». В каждый момент времени может быть отмечен только один Radio button, что как раз подходит для нашей задачи выбора действия. Также добавьте кнопку:

 

 

В обработчике кнопки просто будем закрывать окно методом Close():

 

private: System::Void button1_Click(System::Object^ sender, System::EventArgs^ e) {

Close();

}

 

Модифицируем код функции Calculate() чтобы сначала отображалась форма:

 

#include "frmDialog1.h";

 

namespace mydll {

 

public ref class MyClass

{

// TODO: Add your methods for this class here.

public:

int Calculate(int a, int b) {

frmDialog1 ^f = gcnew frmDialog1();

f->ShowDialog();

return a+b;

}

};

}

Не забудьте про директиву #include "frmDialog1.h"; для подключения формы.

Запустите программу. Несмотря на то, что будет появляться окно для выбора действия, по-прежнему считается только сумма.

Теперь надо преобразовать метод MyClass::Calculate() так, чтобы он проверял, какое действие было выбрано в окне. Мы не можем непосредственно обратиться к компонентам rbAdd и rbSub из этого метода, так как они принадлежат другому классу (классу формы), и объявлены внутри него в секции private. Можно передать их состояние через public переменные внутри класса формы (как мы это уже делали ранее при создании новой формы и передавали ее заголовок через переменную Stroka), или же произвести вычисление в самом классе формы frmDialog1, где все компоненты формы будут доступны. Воспользуемся вторым способом.

В окне Class View добавим новую функцию в класс формы frmDialog1:

 

 

Функция будет принимать 2 числа типа int, возвращать тип int, иметь уровень доступа (access) public, имя CalcInForm.

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

 

 

Код для функции будет проверять состояние Checked компонентов RadioButton и выполнять соответствующе действие:

 

public:

 

int CalcInForm(int a, int b)

{

if (rbAdd->Checked) return a+b;

if (rbSub->Checked) return a-b;

}

 

Осталось модифицировать функцию Calculate в классе MyClass так, чтобы она вызывала функцию CalcInForm и возвращала в хост-программу то, что ей вернет эта функция:

 

public ref class MyClass

{

// TODO: Add your methods for this class here.

public:

int Calculate(int a, int b) {

frmDialog1 ^f = gcnew frmDialog1();

f->ShowDialog();

return f->CalcInForm(a, b);

}

};

 

 

В папке, где располагается группа проектов dlldemo, в директории Debug находятся файлы myprog.exe, mydll.dll и временные файлы *.ilk и *.pdb, которые для работы программы не требуются. Удалите файл mydll.dll, и запустите myprog.exe. Программа по-прежнему будет работать, запрашивать ввод чисел, но не вычислять, что подтверждает факт динамической загрузки DLL файла только в тот момент, когда он необходим.

 

 


<== предыдущая | следующая ==>
 | Непрерывность функции N переменных

Date: 2015-09-03; view: 366; Нарушение авторских прав; Помощь в написании работы --> СЮДА...



mydocx.ru - 2015-2024 year. (0.007 sec.) Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав - Пожаловаться на публикацию