21 Създаване на собствени класове, поддърж. сериализация.
Ако създаваме собствени класове и ако искаме те да поддържат променливи от този клас и ако направим този клас наследник на CObject, тогава не е неоход. да предеф. командите, компилатора автом. го прави.
- Новосъздаденият клас наследява CObject;
- В декларацията на класа DECLARE_SERIAL( име на класа)
- Предефинирвате Serialize() на базовия клас и сериализирате данновите членове на производния клас (ще поясним по-късно).
- Подразбиращ се конструктор на класа.
- В частта имплементация на класа IMPLEMENT_SERIAL
Конструктор по подразбиране – това е конструктор без параметри. Когато класът поддържа сериализация този конструктор задължително трябва да го има. Динамичното създаване на oбекти използва винаги подразбиращият конструктор.
>>оператор за сесериализиране, <<оператор за сериализиране.
Кога се използва Serialize():
- При десериализиране на конструиран обект, не знаем името и сме заделили памет.
- Ако обектът е сериализиран със Serialize() трябва да се десериализира със Serialize().
- Ако създаваният клас има даннов елемент проявление или наследник на класа CОbject също се използва Serialize(), защото операциите << >> са дефинирани в CОbject да работят с указатели към обекти, а не с обекти.
- Ако сериализираният клас има даннов елемент указател към Cоbject или негов наследник, тогава имаме 2 възможности
1. Първо трябва да се конструира данновият обект във функцията за сериализация и след това да се използва указателя. Използването става по 2 начина:
PObject // указател
- PОbject ->Serialize();
- ar>>m.mydata>>p.Object
IMPLEMENT_SERIAL – този макрос иска три параметъра.
пример:
class CLine : public CObject
{DECLARE_SERIAL
(CLine)
protected: CPointm_ptFrom; CPointm_ptTo;
public: CLine(){}
CLine( CPoint from, CPoint to)
{m_ptFrom = from; m_ptTo = to;}
void Serialize (Carchive&ar );};
Kато дефинираме ф-ията Serialize така:
void CLine::Serialize( CArchive& ar)
{CObject::Serialize( ar);
if ( ar.IsStoring())
ar << m_ptFrom << m_ptTo;
else ar >> m_ptFrom >> m_ptTo;}
Добавяме и някъде в имплементацията:
IMPLEMENT_SERIAL( CLine, CObject, 1)
IMPLEMENT_SERIAL( CLine, CObject, 1 | VERSIONABLE_SCHEMA)
В примера са спазени всички правила за сериализация описани по-горе.
22 Версии при сериализация.
Ако след време искаме да променим нашия клас, тогава ! да преопишем Serialize, но пък написните преди това програми ! да се информират че версията е различна. Това става като се сравнят номерата на проектите: IMPLEMENT_SERIAL(cLine, CObject, 1| VERSIONABLE_SHEMA)
Ако открием разлика тогава се генерира exeption. Но ако искаме да подтиснем се използва VERSIONABLE_SHEMA и се приема че може да се работи с различни версии. Когато работим с различни версии ни трябва да предвидим код в Serialize(). Предефинирането работи за указатели към такива обекти-наследници, а не за самите обекти. Така че:
CLine* pLine = new CLine( CPoint( 0,0), CPoint( 40,40));
ar << pLine; //работи добре
CLine line( CPoint( 0,0), CPoint( 40,40));
ar << line; // няма да работи
Aко искаме да сериализираме по стойност, а не по указател, е добре така:
CLine line( CPoint( 0,0), CPoint( 40,40));
ar << &line; //ще работи за сериализация
при десериализация:
CLine* pLine;
ar >> pLine; //всичко е добре
CLine line = *pLine;
// операция “=” е предеф. в CLine като копиране;
delete pLine; // вече ненужен
Когато се обръщаме директно към Serialize се отказваме от проверка за проекти(само една версия) Операторите за сериал. преоб-ват CSrting в UniCode.
23. Управление на паметта в Win16.
1. Когато имаме много instance на едно приложение всички използват общ код и ресурси, само данните са различни.
2. Елементите в паметта са преместваеми по подразбиране
3. Сегментите както и ресурсите най-често се зареждат при повикване.
4. Работи механизма за отстраняване на блоковете от паметта.
Сегментна организация на Windows приложение:
В рамките на един сегмент указателите са near, а в различните сегменти са far (далечни). Ако работим с указатели трябва сегмента да го фиксираме и след като свършим работа да го освободим. Приложенията, като са сегментно организирани имат поне следните сегменти:
- един кодов и един даннов(поне)
- поне един служебен сегмент с някакви служебни таблици – този сегмент е общ за всички копия
- по един за всеки ресурс
Имаме 5 модела памет: small, medium, compact, large, huge.
Small – с 1 кодов и 1 даннов сегмент- модел за малки програми.
Medium – бързи програми с много кодови и един даннов сегмент,работи се с близки указатели към данни.
Compact – 1 кодов и много даннови сегменти, но той не е много добър
Large – модел с много кодови и с много даннови сегменти
Huge - много кодови и даннови сегменти, но работи с huge указтели. Това са далечни указатели след нормализация. Това позволява да се деф. даннови обекти с произволна дължина, има възможност за автом. нормализация.
1. Ако използваме обем неинициализирани данни, добре е те да са заделят динамично от глобалния Help.
2. Ако програмата се нуждае от голям брой инициализирани данни нищо не пречи да ги заделим като ресурс.
Правила:
Препоръчва се small и medium модела за 16 битова среда. Да се използват файл-указатели към данни. Да не се използват далечни указатели към функции, освен такива, които Windows си задава и тези , които се съставят в Windows програма. Разместване на сегментите в Windows програма.
Dos програмите съдържат в началото си код от вида:
mov ax, DGROUP
mov ds, ax
Данновият сегмент сочи данните на програмата и понеже DOS не размества, тогава този сегментен регистър позволява да се намират данните.
Освен това компилатора вмъква “пролог” и “епилог” части:
push bp
mov bp, sp
sub sp,x //име за локални променливи
Прологът и епилогът на всяка Windows far функция:
push ds //дата сегмента е единствения който сочи данните
pop ax
nop //празна операция
inc bp //basepointer
push bp
mov bp, sp
push ds
mov ds, ax
sub sp, x
Всяка far функция в паметта има един от следните видове пролози:
1.mov ax, ds
nop
За ф-ии викани от същата програма, но не от Windows
2. nop
nop
nop
За експлоатирани функции, които ще се викат само от Windows.
За пролога:
Когато линкера направи exe за всяка ф-ция стартовата точка се определя като преместваема. За всяка такава точка попълва служебна таблица, няколко байта служебна инф., к. се запазва с exe-то. Когато стартираме exe-то за изпълнението тази служебна инфо. се прочита и се създава шлюз на зареждане за всяка входна точка. Ако ф-ята е качена или не в паметта , а в този шлюз и на зареждане са намира, винаги има актуален адрес на ф-ята в паметта. Windows сам го обновява. Ако не е в паметта вместо jump, следва прекъсване на процесора и функцията се качва в паметта. Който вика само far във Windows, трябва да зареди в “ах” данните.MakeProcInstance cъздава втори шлюз Instance Thunk. В него ах се зарежда с данните и се реализира преход към Reload thunk.
reload thunk.
Instance thunk mov ax,..
DLL ф-те имат само 1Instance и тогава първите 3 байта се заменят с една 3 байтова ф-я. DLL нямат собствен стек, а ползват този на викащата страна
inc bp – целта е да можем чрез обратно прохождане на стека да се намери цяла поредица от викащи far ф-ии. Щом са far =>са различни сегменти=>може някоя от викащите ф-ии вече да не е в стека. За да се определи дали е в стека или не се инкрементира bp четен =>far нечетен, near – при обратно прохождане.
3. mov ax, xxx
За функциите от библиотечните модули на Windows (DLL)(USER, KERNEL, GDI,както и драйверите).
24. Виртуална памет
Осн. х-ка е изплзването и въвеждането на на вирт. памет. Всеки просец използва собствено адресно пространство 2(32)=4GB. Те се разделят поравно по default на 2 части- потребителски и системна. В рамките на това адр. Простр. Се отказваме от сегментите. Това променя работата с у-тели.Виртуалното адр. Пространство се образува като част от HDD. За да се ускори адр.простр се разделя на страници с фикс. Дължина 4К. Определя се от архитектурата на процесора. Страница е базовата ед. Памет ,к. се обменя м/у процесора и вирт. памет. В тези страници има флагове за различни нужди(дали стр. е в паметта). Една стр. може да е protected или shared. Динамичното заделяне също става на 3 стъпки:1. Резервиране 2. Ангажира. З.Заделя
Целта- ускоряване: 1. Информираме mem manager-a na win, че резервираме дадена памет и др.процеси не може да я ползват. 2. Съвместяваме табл.с адреси. 3. Обръщаме се към нея.
Резервирането става по страници. Има guard page и exeption: EXEPTION GUARD PAGE,к. се генерира при първият опит за достъп до охранявана станица.. Memory се алокира с shared( блока физически е на едно място, но е в адр. Пространство на 2 процеса)
Copy-on-write- при общ блок в адр.простр. на 2 процеса, за времето когато четат този блок е единствен, ако единия пробва да пише win managera прави копие на блока и насочва промените към копието.
HEAP: В 16Bit среда има локален и глобален heap. В 32 Heap-a e общ.
2 подхода:
1. Използване С С++ класове за работа с heap-a.
2. Използване API ф-ии на средата. Пример:
HANDLE hfile=::CreateFile(..)
HANDLE hMAP=::CreateFileMapping(hfile,…)
LPVOID lpvFile=::MapViewofFile(hMap,..)
DWORD dwfilesize=::GetFileSize(hfile,..)
::UnmapViewofFile(lpvFile);
::CloseHandle(hMap)
::CloseHandle(hFile)
Можем да създадем heap като укажем синхронен достъп на повече от една thread..
Във всеко проявление на класа ни да се структурира собствен heap. Удачно е щото при него се изолираме от влияние на други threat-ове. Може да предефинираме new() delete() ако заделяме памет за специфични обекти. Собствения heap авт. се унищожава като се унищож. общата за класа.(така гарантираме че няма да останат неизползвани хип-ове)
New()- проверява създаден ли е до момента private heap. Ако не-създава и инициализира брояч. Заделя памет в хип-а. Инкрементира брояча.
Delete()- декрементира. Освобождава памет от хип-а.
25. Работа с динамични блокове памет в многозадачна среда.
В 16 битова среда имаме локално и глобално адресно пространство.
При 32 битова среда поради линейното пространство няма локално и глобално адресно п-во, а то е едно (2GB потребителско адресно п-о). Има 2 функции за работа с хипове:
- С++ функции MFC- по – универсални
- с API функции на ОС – специфични и когато се гони бързо действие и големи обеми.
Има 2 вида хип:
- автоматично зареждане на приложението(по подразбиране)
- динамично зареждане – формиране на собствен потребителски хип.
Има API ф-ии heapCreate, с к. може да се съдаде heap. След което може да се заделя дин. област.
Препоръки за работа със собствения хип:
* в рамките на своя клас се създава собствен хип, така че методите на класа да работят със собствения хип и той да се унищожи от дестр-ра. Предимствата:
- безопасност – другата нишка да се намеси в хипа.
- до голяма степен се избягва фрагментацията.
- в С++ може да се предефинира New и Delete - чрез предефинирането на New може да се направи оптимално заделяне на памет. Схемата реализираща предефинирането на New е следната:
1) проверява дали е заделен локален хип с New ако не е го заделяме.
2) заедно със създаването се задава и брояч на New и Delete.
3) заделя се необходимия брой байтове, които се заделени
4) инкрементираме брояча
Схема реализираща предефинирането на Delete е следната:
1) освобождава блока
2) декремнтира брояча
3) проверява ако е 0 унищожава handler към файла
Има функция – heapwin за създаден вече (от нас) хип. Големите блокове алокират собствен хип и връщат указател към тях. За малки блокове използват памет от вече алокиран хип.
За големи блокове след интензивно използване на Delete трябва ни чести интервали за викане – heapwin, който всъшност освобождава тези блокове от хипа.
На етапа Commit паметта е заделена в Paging файла, а в паметта се заделя в ОП, едва когато се опитаме да запишем.
Права на достъп: само за четене, само за запис или др. Една страница в паметта може да е охраняема:
EXCEPTION_GUARD_PAGE
Ако имаме опция guard то трябва четене от паметта. MemoryManager отделя последната част от ОП като guard. Ако се опитаме да четем охраняемата граница, това става , когато е свършило останалото място и генериме EXEPTION_GUARD_PAGE, която може да се прихване и обработи, т.е. ако ни трябва още памет, то може за да заделим и да преместим охраняемата страница назад.
Една страница (част от паметта) може е shared, т.е да се използва от повече от 1 процес:
П-с1 Физ. памет П-с2
Share блок се помества в адресното пространство на 2 или повече процеса, но фактически е задлено само едно физическо адресно пространство.
Друг механизъм е Copy_On_Write
П-с1 Физ. памет П-с2
Едно .exe стартирано става процес. Ако 2 процеса само четат паметта то тя е една. Ако единият процес запише данни в този блок: то за другият процес се появяват нови данни. В този момент Copy_On_Write заделя нова ОП и копира там блока, тогава вече и двата процеса работят с различни блокове.
Memory Manager постоянно размества страници в паметта, но може да заключим страници за разместване и те тогава не могат да се разместват. Адреса е 32 – битов,отказваме се от 16 – битовата сегментна организация, (в 16 – битовата сегмента е 2.., а тук е 2..=4GB). Имаме по–големи сегменти.
Отделните процеси са в отделни сегменти. Друга новост тук е виртуалната организация на паметта. Тя се реализира чрез мощен SWAP механизъм. Трета новост е страничната организация на адресното пространство, т.е. работи се с блокчета, а не с цели сегмнети.
4GB адресно пространство може да се счита за разделено на 2 по 2GB.
- за потребителски програми
- за системни програми
Организация на адресното пространство:
Виртуален адрес
|
съдържание
|
0-64КВ
|
Служебни
|
Над 64КВ
|
За модули на изпълнимия файл
|
Над горния
|
за heaps и threads stacks
|
Над тях
|
За DLL
|
Над тях
|
С-мни DLL: Kernel32, User32, GDI32 и ОС
|
25В-45В
|
За нуждите на ОС
|
Резервирането заделя блок с определена големина:
pMem = VirtalAlloc(<начален адрес на блока NULL>, <бр.страници на резервиране>,<права на достъп>):
За виртуалната памет се прави файл върху диска, който съдържа страници. Обмена м/у вирт. и ОП памет става по страници.Един процес може да има най – много 4КВ обем страници. Една страница е валидна ако е в ОП. Ако страницата я няма в ОП , тя се намира в swap файла и се качва в паметта. Ако не достига памет, то страницата се сваля от ОП, но всъщност тя се припокрива. Има доста механизми и особености при припокриване. Съществува страница, маркирана за shared стр. с ограничено ниво на достъп и т. н. и всичко това ! да се провери.
Заделянето на памет за нуждите на процес е двустъпков процес:
1.Резервиране – Reserved
2.Ангажиране - Commit
Смисълът е да се отложи за колкото се може по – късен момент заделянето на памет, но възможно непосредствено преди записване. Резервирането е указание от MemoryManager за нуждата от такъв обем памет за дад. п-с.
26 Memory mapped file – средство за оптимално управление на паметта. Състой се от асоцииране на файла или част от файл с ОП. Файла или част от файл се асоциира с виртуалното адресно пространство и тогава над него могат да се правят определени операции без да се качва.
HANDLE hFile=::CreateFile(...)//създаваме Handle
HANDLE hMap=::CreateFileMapping(hFile,...);
LPVOID IpvFile=::MapViewoffile(hMap,...);//мапва целия файл
DWORD dwFilesize=::GetFileSize(hFile,...)//използваме файл
::UnmapViewofFile(IpvFile);
::CloseHandle(hMap);
::CloseHandle(hFile);
Два процеса могат да ползват общ hMap т.е. те имат обща памет:
GlobalAlloc(...,GMEM_SHARED...);
В Win32 не прави shared блок, както в Win16. Обща памет, но не общ файл както по – горе без CreateFile(...) и с подаване на параметри 0хFFFFFFFF вместо hFile.
GlobalAlloc е наследена в Win16. Тук в 32 битовата среда всъщност не действа , но може да се запише.
Съвет при работа с динамична памет:
- хипът се фрагментира при продължителна работа. Решението е по – често да се унищожава и да се създава нов или да се прекомпонира.
- викайте heapmin в NT за освобождаване на големи блокове
- не викайте HeapFree за малки блокове заделени с new.
- стекът вече не е ограничен до 64К и става толкова голям, колкото е необходимо.
Как да пестим пространство от swap файла?
- EXE и DLL файлове не са в swap file – те се включват във виртуалното адресно пространство на всеки процес. Добре е и константните данни по някакъв начин да се прикачат към тях, за да не попадат в swap file.
Низове :
Ако те са непроменяеми за цялото изпълнение , декларирайте const charmystr[]=”my string”;
Низът се съхранява заедно с кода – в секцията за инициализирани константни данни . тя се съхранява заедно с EXE файла. В тази секция не можем да слагаме само С++ обекти, създадени чрез конструктор. Например:
const Crect my_rect(0,0,100,100); се поставя в .bss, който се вкарва в swap file и всеки процес има копие на този обект. По – лошо е:
const CString my_str(“new instance”); което води до :
-
поставяне на CString обект в .bss секцията
-
масивът от символите в .data (иниц. неконст. данни )
-
заемане памет за копие на символите за всеки стартиран процес.
Нищо не попада в EXE и всичко харчи памет.
27.Стратегии на управление на памет и събиране на боклук в .net платформа
* бъгове
* управляван heep в Common Language Runtime средата. Последоват. заемане, първо в кеш.
* heep на С runtime библ.:поддържа свързан списък на блокове памет.
Алгоритъм за събиране на боклук
OutOfMemoryException
корени (roots)- пример
граф на достижимете обекти от корените
# няма неизползвани обекти; # освободен недостижим обект.
пр: class My {
static void XX() {
ArrayList x=new ArrayList(); //а е корен
for(Int32 x=0;x<1000;x++)
{
a.Add(new Object());
} //а реферира 1000 обекта
Console.WriteLine( a.Lenght); // използваме а
//липсва друго използване на а#### започва почистване на боклука
// 1001 обекта могат да се почистят
……}}
28.Финализиране на обекти в .NET
Типове, които обвиват ресурси, освен памет (файл, mutex, сокет..)изискват явно финализиране – Finalize() за почистването си. Пример:
protected override void Finalize()
{
try { CloseHandle(handle); }
finally { base.Finalize(); }
В C#, Managed C++ (всички __gc класове наследяват Finalize() от Object). Това става в синтаксиса на деструктора:
Ако имаме: ~MyClass()
{ Console::WriteLine(S”Finalizing..”);}
компилаторът го разширява до: MyClass::Finalize()
{Console::WriteLine(S”Finalizing..”); MyBaseClase::Finalize();}
virtual ~MyClass()
{System::GC::SuppressFinalize(this); A::Finalize();}
//”А” обединява всички обръщения към обекти, реферирани в конструктора
недостатъци на Finalize():
*заделяне на финализирани обекти е по-бавно
*осводождаването се забавя, особено при масиви от такива обекти
*финализиран обект, рефериращ други обекти забавя финализирането им
*няма контрол кога се изпълнява Finalize()
*няма гаранция за реда на изпълнения на Finalize(). във Finalize() да не се викат вътрешни обекти.
Кога се вика Finalize():
-
поколение 0 се е запълнило;
-
явно обръщение към System.GC.Collect()
-
процес завършва – вика се Finalize() за всички обекти в него.
29. Унищожение – КОГА (модел на ликвидиране – експлицитно ). Интегриране на Finalize() и Dispose()
__gc class Tester : public IDisposable
{ bool bDisposed;
public:
Tester()
{ Console::WriteLine(S"Tester constructor"); bDisposed = false; }
~Tester()
{ if (bDisposed == false)
{ Console::WriteLine(S"Tester finalizer"); } }
void Dispose()
{ Console::WriteLine(S"Tester dispose"); bDisposed = true; }
void aMethod()
{ if (bDisposed) throw new ObjectDisposedException(S"Tester");
Console::WriteLine(S"Tester aMethod"); }};
// This is the entry point for this application
int main(void)
{Console::WriteLine(S"Finalization Test"); Tester* pt = new Tester();
/* Call Dispose */ pt->Dispose();
/* Try calling a method on the object...*/ pt->aMethod();
Console::WriteLine(S"End of Test"); return 0;}
// ресурс трябва да бъде освободен (Dispose) и след това изтрит (напр.
// File.Delete(..)). Иначе се счита че той се използва от друг процес.
// Почистване с Dispose форсира финализацията, но не унищожава обекта от
//паметта – това прави събирането на боклука.Т.е. могат да се викат
// методи на обекта – стига да не ползват невалидни указатели.
30.Поколения
*нов обект по-кратък живот
*събиране нач аст от heap е по-бързо от събиране на целия heap.
*неизследвани обекти в управлявания heap са от поколение 0.
А B C D E
поколение 0
ABD
пок1 поколение 0
ABDFGHI JK
пок1 пок 0
ABDFGIK
пок 1пок 0
ABDFGIK LMNO
пок1 пок 0
ABDFGIJNO
поколение 0 пок 0
ABDFGIJNOPQRS
поколение 1 пок 0
DFINOQS
пок2 п1 поколение 0
активиране системата за събиране на боклука:
void GC.Collect(Int32 Generation);
void Gc.Collect();
31. Документ – Изображение
В MFC e залегнала концепцията програмата да се създаде с най - малко 2 слоя:
- документен – занимаващ се с функционалното обработ. на данните, възстановяване, сериализация, комуникация и др. Възможно е едно изображение да сменя множество документи или един документ да има много изображения.
- SDI - приложения определена архитектура (класове); В SDI в дадне момент има един документ и в общия случай едно изображение.
В МDI в даден момент се работи с няколко документа от различен тип.
В началото още се указва какво приложение SDI или МDI ще се прави. При двуслойна архитектура се обособяват няколко класове групи:
1)документен клас- обикновено името и класовете включват DOC
2)view class – обикновено името и класовете включват view частицата. Класът View е дъщерен на рамката (Frame) и се явява клиентската част на приложението. То трябва да има производен на СView класа и всички извеждания отиват към методите на тези класове.
3) Frame class – класове на рамката. Това е най-големия прозорец, в който се разполагат изображението. Някои съобщения могат да се пренасочат за обработка от Frame, а не от View.
4)Application class – включва стартова точка на програмата, връзката между Frame/Veiw/Document
5)Document class – отговаря за обработка на данни, сериализация, документация. Документния клас притежава специфичните интерфейси за обработка на данните. Има методи за обхойдане на всички обекти създадени от него или за UpdateAllView …
View class
Изобразява данните. Комуникира с документа. Всеки View се свързва с документ посредством GetDocument().
CViewCountDoc* pDoc = dynamic_cast(GetDocument());
// Добре е в прил. да има собствена реал. на GetDocument():
ViewCountDoc* CViewCountView::GetDocumet() //връзка между Doc и View
{CViewCountDoc* pDoc = dynamic_cast(GetDocument());
ASSERT_VALID( pDoc);
Return pDoc; }
void CMyView::OnDraw(CDC* pDC)
{ CMyDoc* pDoc = GetDocument(); //прави връзка с документа откъдето трябва да се вземе стринга
CString string = pDocGetString();
CRect rect;
GetClientRect(&rect);
pDCDrawText( string, rect,……………..); }
Има два Frame:
- MainFrame
- ChildFrame
Main Frame Windows
Document Template(документен шаблон)
Такъв има всяко приложение. Той е различен в MDI и SDI. Прави връзка между ( при това само по 1) document class, view, frame.
Има CSingleDocTemplate и CMultipleDocTemplate. Съдържа:
-
документен низ (7 полета),
\nMy_app\My_app\n\n\nMy_app.Document\nMy_app Document
-
Иконата на изображението
-
Менюто при активно изображение.
SDI документния шаблон се създава още в InitInstanse():
CSingleDocTemplate * pDocTemplate;
PDocTemplate = new CSingleDocTemplate( IDR_MAINFRAME, RUNTIME_CLASS( CMyDoc), RUNTIME_CLASS( CMainFrame), RUNTIME_CLASS( CMyView));
AddDocTemplate(pDocTemplate);
// следват редове за създаване обектите от шаблона, показване рамката и изгледа. Включва се цикълът за обр. съобщения, като съобщенията се разделят към обектите - приложение, рамка, изглед, документ, прозорец (command routing).
Сподели с приятели: |