C builder ошибка access violation

I’m trying to do:

try{
    int * i = NULL;
    *i = 3;
}catch(Exception &Err){
    ShowMessage(Err.Message);
}

I though that this should catch access violation exception and handle it by displaying an error message.

But for some reason I get simple

Access Violation

message instead of full one

Access Violation XXX in module YYY. Writing at address ZZZ.

BTW, ExceptObject() routine returns NULL for some strange reason.

What am I missing here?

bluish's user avatar

bluish

26k27 gold badges120 silver badges179 bronze badges

asked Jun 5, 2009 at 11:35

1

In BCB5, catching an EAccessViolation works, e.g.:

 #define AV_TRY { try {

 #define AV_CATCH } catch(EAccessViolation &av) {Application->MessageBox((("Access Violation caught: " + string(__FILE__) + "; " + string(__FUNC__) + "; " + IntToString(__LINE__) + "nn") + av.Message.c_str()).c_str(), ("Program Error in " + string(class_name.c_str())).c_str(), MB_OK);} }

Note that class_name is specific to this project and should probably be replaced by AnsiString(this->ClassName) or left out. Also I’ve switched this code from logging silently to a database, to showing a MessageBox. I just wrap code where I’ve observed AVs in an AV_TRY … AV_CATCH.

answered Sep 14, 2010 at 18:51

insignis's user avatar

See the MSDN blog entry on Mixing SEH and C++ Exceptions. These are two different types of exceptions. Trying to catch a structured exception, generated by the OS, as a C++ exception isn’t the correct way out of the box.
Temper this bit with this posting on not doing that.

Catching access violations can be a nice goal — but something you may want to do within the context of debugging only. Catching access violations (or other major exceptions) in production code and trying to handle them is seldom going to result in correct operation.

tibx's user avatar

tibx

81213 silver badges19 bronze badges

answered Jun 5, 2009 at 11:45

Kris Kumler's user avatar

Kris KumlerKris Kumler

6,2973 gold badges23 silver badges27 bronze badges

Standard C++ does not specify that dereferencing a NULL pointer throws an exception — it says it results in undefined behaviour. On Windows platforms, the water is muddied somewhat by Windows Structured Exception Handling. This has nothing to do with C++ exception handling, except that some C++ run-times may translate these excptions into C++ exceptions. However, code that depends on such translations is not portable.

answered Jun 6, 2009 at 13:55

try {
    int * i = NULL;
    *i = 3;
}
catch (...) {
    // This would catch the access violation but you don't have any more
    // information of what has gone wrong
}

You can however, use structured exception handling (SEH) to catch all C++ exceptions. Since C++ exceptions are just a class based implementation based on SEH.

answered Jun 6, 2009 at 13:34

ralphtheninja's user avatar

ralphtheninjaralphtheninja

126k20 gold badges111 silver badges121 bronze badges

1

Студворк — интернет-сервис помощи студентам

Здравствуйте.

Случилась такая беда — написал небольшую программку, она работала-работала, я решил её немного изменить и переделал её так, что бы она работала в 1-м окне, т.е. раньше было 2 формы, меняющие друг друга, а сейчас 2 TPanel, которые скрываются по очереди.

После такого объединения, программа стала выдавать такую ошибку: http://выкладывайте вложения на форум . При компиляции — ни одной ошибки, никаких алертов или ворнингов. Ошибка вылезает после того, как я закрываю программу.

Нет события OnClose или OnCloseQuery у формы, поэтому ошибка явно не там… Подскажите, пожалуйста, как выявить ошибку.

Добавлено через 1 минуту
ЗЫ: ошибка никак не влияет на работу программы, мб можно как-то просто отключить сообщения об ошибках доступа?

Примечание для людей, заходящих сюда из поисковика: эта статья написана для разработчиков программ. Если вы не программист и не пытаетесь исправить ошибку в СВОЕЙ программе, эта статья — не для вас. До свидания. Извините, что потратил ваше время.

Примечание для студентов/новичков, пишущих на Delphi/C++ Builder: эта статья написана для диагностики исключений в вашей программе. Если вместо этого вы получаете ошибки от самой IDE (а не от вашей программы), например, access violation в пакете dclite60.bpl, то эта статья — не для вас. Чтобы решить проблемы с IDE — идите сюда. Краткий ответ: не надо использовать динозавров (Delphi 5/6/7), используйте современные IDE (Delphi XE и выше). Если всё же хочется динозавров, то часто причиной является DEP. Т.е. нужно добавить Delphi/Builder в исключения DEP. Ну или на крайний случай — отключить/удалить конфликтующий пакет.

Итак, для всех прочих (а именно: разработчиков Delphi/C++ Builder, пытающихся решить проблему возникновения исключения Access Violation в своей программе) — приступим!

Исключение класса EAccessViolation — это самое частое исключение в Delphi-программах. Я хотел бы рассмотреть, что это такое, когда возникает, и как с ним бороться. Этот пост скорее для начинающих, поэтому данные могут излагаться с упрощением.

Примечания:

  • если вы совсем начинающий или студент/студентка и получили Access Violation — первым делом включите опцию Range Check Errors (Project/Options, вкладка Compiler) и сделайте Project/Build.
  • если вы плохо или совсем не понимаете, что такое указатели и/или объекты — рекомендую сначала прочитать эту статью.
  • если вы плохо или совсем не умеете работать с отладчиком IDE (или даже не знаете, что это такое) — прочитайте сначала эту статью.

Каждая программа использует при работе память (*). Память занимает любая переменная в вашей программе. Будь это форма, компонент, массив, запись, строка или же простой Integer. Под некоторые переменные память выделяется автоматически (например, под переменные типа Integer и статические массивы), под другие — вы должны выделять её сами, явно (например, динамические массивы). Собственно, с точки зрения операционной системы каждая переменная характеризуется адресом в памяти (местоположением) и размером. Понятно, что обычно данные разных переменных не пересекаются — за исключением случаев обращением к одной области памяти через разные имена с помощью указателей.

Грубо говоря, обычно в программе используется три типа памяти: область памяти для глобальных переменных, стек и куча.

Память для глобальных переменных выделяется загрузчиком ОС при загрузке исполняемого модуля программы в память и освобождается при выгрузке модуля (выходе из программы). Глобальные переменные — это любые переменные, объявление которых располагается вне класса или процедуры. Стек используется для размещения локальных переменных (объявленных в процедуре/функции) и служебных данных (типа адресов возврата и адресов обработчиков исключений). Куча же используется для размещения динамических данных.

Подробнее.

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

Вне зависимости от того, кто выделяет память для переменной (вы вручную или компилятор автоматически), память для любой переменной должна быть выделена перед использованием, а потом, когда переменная уже не будет нужна — освобождена.

Иногда из-за ошибок в коде программы происходит ситуация, когда программа при выполнении пытается получить доступ к памяти, которая не была выделена или уже была освобождена. Когда такое происходит, процессор возбуждает исключение класса EAccessViolation. Обычный текст ошибки в приложении Delphi — «Access violation at address XXX in module ‘YYY’. Write/read of address ZZZ» («Нарушение доступа по адресу XXX в модуле ‘YYY’. Попытка записи/чтения в ZZZ»). Хотя причина этого исключения всего одна (попытка обращения к недействительной памяти), но эта ошибка может проявлять себя в весьма разном виде и коде.

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

Ищем место возникновения Access Violation

Как, собственно, бороться с этими ошибками? Ну, если вы получили EAccessViolation под отладчиком:


То нужно просто нажать на «Break» («Ok» в старых версиях Delphi) и отладчик сразу же ткнёт вас на строчку с ошибкой. Также можно посмотреть стек вызовов (в меню Delphi — View/Debug windows/Call Stack):


В этом окне будет показано, как же вы туда попали. Читается это дело сверху вниз (текущее место помечено стрелочкой). Можно дважды щёлкать по строкам в этом окне для перехода в код, соответствующий этой строке.

Иными словами, отладчик сразу же тыркает вас в строку с ошибкой.

Если же вы используете средства автоматической диагностики типа EurekaLog/madExcept, то вместо обычного сообщения об ошибке вы получите баг-отчёт, в котором будет виден тот же самый Call Stack (вид стека вызова может отличаться из-за различных методов его получения):


Не имеет значения, столкнулись ли вы с проблемой во время отладки или получили баг-отчёт от EurekaLog для уже распространяемой программы — хорошо бы подготовиться к этой ситуации заранее и включить опции проекта, упрощающие отладку. Как правило, это опции «Use Debug DCUs» и «Stack frames».

Окей, найти место ошибки — это только пол-дела. Определить почему же в этой строке возникла ошибка — это вторые пол-дела.

Ищем причину возникновения Access Violation анализом кода

Если ситуация возникла у вас в отладчике, то тут всё относительно просто: вам нужно установить точку останова на проблемную строчку и проверить значения всех переменных и выражений, участвующих в ней — вот вам и причина ошибки, находится сразу же. Я не буду подробно останавливаться на теме отладки здесь, более подробно об этом написано в моей статье, часть 2 (осторожно: большой размер).

В случае, если у вас на руках есть только баг-репорт, а не ситуация под отладчиком, то вам придётся использовать свои телепатические способности, которые обычно развиваются с опытом. Дабы помочь вам в этом, здесь я как-раз и хочу рассмотреть типичные причины возникновения ошибки Access Violation.

1. Во-первых, это всевозможные ошибки выхода за границы массивов. Например, типичная ошибка новичка может выглядеть так:

var
  X: Integer;
...
  for X := 1 to Length(List) do // ошибка! Должно быть: for X := 0 to Length(List) - 1 do
  begin
    // ... делаем что-то с List[X]
  end;

Если в вашей проблемной строке есть скобочки типа [], то у вас есть хороший довод к проверке допустимости выражения в [].

Обычно такие ошибки нужно отлавливать на стадии отладки, включая опцию Range Check Errors. Дело в том, что подобные ошибки весьма опасны тем, что могут пройти незамеченными (и потом редко ловятся при эксплуатации программы), даже более того — они могут разрушить стек, так что нельзя будет получить место возникновения ошибки. Но об этом позже.

2. Различного рода неверные передачи параметров. Обычно эти ошибки отлавливаются во время разработки и тестирования, нежели во время эксплуатации программы. Чаще всего они возникают при использовании процедур с нетипизированными параметрами. Сюда же относятся различные варианты ошибок переполнения буфера, например:

var
  S1: array of Integer;
  S2: String;
...
  // Неверно:
  Stream.ReadBuffer(S1, 256);     // портит указатель S1
  // Правильно:
  Stream.ReadBuffer(S1[0], 256);  // читает данные из потока в массив

  // Неверно:
  FillChar(S2, Length(S2), 0);            // портит указатель S2
  // Правильно:
  FillChar(Pointer(S2)^, Length(S2), 0);  // очищает строку, забивая её данные нулями

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

3. Передачи данных между двумя менеджерами памяти. Обычно ошибки такого плана возникают при передаче данных из DLL в приложение или наоборот. а также между двумя DLL. Чаще всего новички любят передавать из/в DLL строки типа String.

Причины этого я рассматривал ранее. Эти ошибки обычно отлавливаются немедленно во время разработки программы и очень редко доживают до рабочей программы. Решаются эти проблемы правильным проектированием.

4. Неверное объявление функций, импортируемых из DLL. Наиболее часто путают модель вызова. Если у вас получается EAccessViolation при вызове функции из DLL — просто внимательно посмотрите на её объявление и убедитесь, что её сигнатура верна — чаще всего пропускают модель вызова, stdcall или cdecl.

Хотя обычно ошибки такого плана отлавливаются на этапе разработки, тем не менее могут быть ситуации, когда ошибка проползает в готовую программу. Вот увлекательная история Реймонда Чена о том, как программа может работать с неверно объявленным прототипом функции (довольно интересны и посты в серии до и после этого).

5. Отсутствие синхронизации при работе с потоками. Если вы делаете программу с использованием нескольких потоков, то у вас могут быть проблемы, если вы не обеспечили необходимой синхронизации. Например, любые обращения к VCL запрещены из вторичных потоков — вам нужно использовать Synchronize. Собственно, проблемы тут возникают, когда один поток меняет данные с которыми работает второй поток — что для последнего становится полной неожиданностью.

К сожалению, ошибки с синхронизацией потоков наиболее тяжело диагностировать. Лучшее, что вы можете сделать — прогарантировать, что такие проблемы никогда не возникнут: используйте Synchronize и/или заключайте код в критические секции при работе с разделяемыми потоками переменными. Иногда проблемы возникают из-за использования CreateThread вместо BeginThread или TThread (из-за отсутствия установки IsMultiThreaded).

6. Вызовы функций или процедур по процедурной переменной, когда она содержит неверное значение. Например:

var
  Lib1, Lib2: HMODULE;
  Proc: procedure;
...
  Lib1 := LoadLibrary('MyDll.dll');         // один код загрузил библиотеку. Быть может - другой поток
  ...
  Lib2 := GetModuleHandle('MyDll.dll');    
  Proc := GetProcAddress(Lib2, 'MyProc');   // нет проверки на ошибку. Функции может не быть - тогда Proc будет равна nil
  Proc;                                     // Proc может быть равна nil - будет Access Violation
  ...
  FreeLibrary(Lib1);                        // ещё какой-то код выгрузил библиотеку
  ...
  Proc;                                     // хотя Proc <> nil, код, на который она указывает,
                                            // больше не загружен - здесь будет AV.

Ситуация очень сильно напоминает следующий пункт и бороться с нею нужно такими же методами.

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

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

var
  Str: TStringList;
...
  Str.Add('S'); // Ошибка! Мы забыли создать объект вызовом Str := TStringList.Create;
  ...
  Str := TStringList.Create;
  Str.Add('S');
  ...
  Str.Free; // Здесь мы удалили объект, но ссылка Str по-прежнему указывает на ту же область памяти
  ...
  if Str.Count > 0 then // Ошибка! Обращение к уже удалённому объекту

Как мы уже говорили ранее, в приложениях Delphi есть служебный код, называемый «менеджером памяти», который отвечает за выделение и освобождение памяти в вашей программе и служит прослойкой между низкоуровневыми функциями операционной системы и вашим кодом. При всей своей пользе менеджер памяти, однако, добавляет в программу одну проблему: из-за него в программе находится куски памяти, которые выделены с точки зрения операционной системы, но свободны с точки зрения программы. Например, удалили вы компонент, но менеджер памяти не отдаёт память системе немедленно, придерживая её для дальнейшего использования.

Поэтому все ошибки доступа к памяти опасны в первую очередь тем, что могут пройти незамеченными. Например, мы обращаемся к уже удалённому объекту, но поскольку менеджер памяти ещё не отдал эту память системе, то обращение может пройти успешно. Чуть ранее мы говорили, что для предотвращения таких ситуаций вам нужно использовать FreeAndNil и другие механизмы. Ситуация ещё хуже с локальными массивами: дело в том, что локальные массивы размещаются в стеке, в котором обычно есть довольно большие участки размещённой памяти по краям массива. Что ещё хуже, эта память обычно реально используется программой (в отличие от памяти, которую мы освободили при удалении объекта), так что вы можете, спокойно промахнувшись, записать что-то не туда, и в итоге, ошибка всплывёт в совершенно другом месте из-за испорченных данных. Чтобы сделать ситуацию ещё хуже: в стеке хранятся и служебные данные программы, необходимые для её выполнения — это адреса возврата и обработчики исключений.

Например:

procedure TForm13.Button1Click(Sender: TObject);
var
  S: array [0..1] of Integer;
  I: Integer;
begin
  I := 2;           // предположим, что это значение как-то вычисляется и
                    // из-за ошибки в программе получает неверное значение
  S[I] := 0;        // эта строка затрёт адрес возврата из Button1Click в стеке
end;                // в этой строке произойдёт Access Violation, т.к. мы испортили адрес возврата

procedure TForm13.Button2Click(Sender: TObject);
var
  S: array [0..1] of Integer;
  I: Integer;
begin
  I := -6;          // пусть мы снова ошиблись в I
  try
    S[I]     := 1;  // вместо массива мы стираем обработчик исключений, установленный try
    S[I + 1] := 2;
    S[I + 2] := 3;
    Abort;          // полный вылет программы, т.к. менеджер исключений обнаружил испорченный стек
  except
    ShowMessage('Aborted');
  end;
end;

procedure TForm13.Button3Click(Sender: TObject);
var
  S: array [0..1] of Integer;
  I: Integer;
begin
  I := -1;          // пусть мы снова ошиблись в I
  S[I] := 1;        // хотя мы снова портим стек, но нам это сходит с рук
                    // никакого EAccessViolation не будет вовсе!
end;

Весьма коварные ситуации, не правда ли? В зависимости от того, как именно мы ошибёмся в индексе массива, мы можем получить (**):
а). Программу, выдающую правильные результаты.
б). Программу, выдающую неверные результаты.
в). Программу, возбуждающую исключение.
г). Программу, вылетающую вообще.
Причём одна и та же программа с таким багом может показывать любое из этих поведений, смотря по тому, на какой машине она запущена и в каких условиях/окружении выполняется.

Вот почему чрезвычайно важно использовать опцию Range Check Errors во время разработки и тестирования.
Ну, вы можете также включить её и для release-версии кода, если не уверены в качестве своей стадии тестирования.

Итак, что, собственно, нужно сделать, когда мы получили Access Violation? Ну, с помощью предыдущего пункта мы находим строку с ошибкой, а дальше пытаемся по пунктам подставить возможные причины:
— Есть в строке []? — подумаем, а не может ли у нас быть неверный индекс?
— Есть работа с объектами? Проследим, какова логика работы — не удаляется ли объект раньше времени?
— Используем DLL? А правильно ли объявлена функция? А уж не обмениваемся ли мы динамическими данными (строками, там, массивами)?
и т.д.

Существенную помощь в таком анализе нам поможет следующий пункт.

Ищем причину возникновения Access Violation анализом данных

Во-первых, мы можем попытаться вытащить информацию из самого сообщения об ошибке. Напомним его вид:

Access violation at address XXX in module ‘YYY’. Write/read of address ZZZ.

Во-первых, адрес XXX указывает на точное место в программе, где произошла ошибка. Именно по этому адресу отладчик Delphi и EurekaLog ищут строчку для показа её вам. Также модуль, которому она принадлежит, показывается в сообщении как YYY. Обычно это ваша программа, DLL или системная DLL. Однако, иногда это может быть и совершенно левое значение. Например, если в сообщении не указан модуль или значение XXX выглядит подозрительно (меньше $400000 или больше $7FFFFFFF), то у вас либо проблемы с перезаписью стека (пункт «в» в конце предыдущего раздела), либо вызов неверной функции (пункт 6 или, иногда, 4 из предыдущего раздела).

Следующий полезный кусок информации — это слово «write» или «read». Первое означает, что возникла проблема при записи информации, второе — что проблема была при чтении. Соответственно, вам нужно проверять в строке кода либо операции записи, либо операции чтения. Например, если проблемная строка была «P := W;«, то вам нужно обратить внимание на P, если в сообщении стоит «write». Если же там стоит «read», то нужно проверять, что же у нас с W.

И последний кусок информации, который можно извлечь из сообщения — это ZZZ. Собственно, точное значение нас обычно не волнует. Важен только факт — велико оно или мало. Мало — это что-то типа $00000000, $0000000A, $00000010 и т.п. Большие значения — это, например, $00563F6A, $705D7800 и др. Если ZZZ мало, то у вас идёт обращение по ссылке равной nil. Если оно велико, то у вас идёт обращение по ненулевой, но мусорной ссылке. В первом случае вам нужно искать, зачем же вы полезли по ссылке равной nil (или кто же освободил переменную раньше времени), во втором случае вам нужно понять, кто же это такой освободил объект, а ссылку не занулил. Короче говоря, это значение (так же, как и с «write»/»read») помогает сузить область поиска.

Помимо сообщения, если у вас есть баг-репорт, вы можете проанализировать значения регистров и состояние памяти. В этом вам помогут две последние вкладки в отчёте EurekaLog:



На первой вкладке вы можете видеть ассемблерный листинг своей программы. Приводится он здесь только для удобства — чтобы не надо было лезть ещё куда-то, чтобы подсмотреть его. Никакой информации он не несёт. А вот на второй вкладке вы можете видеть состояние регистров, (части) стека и (части) памяти в момент исключения. В данном случае мы смотрим на ассемблерный листинг и видим, что в проблемной команде участвуют регистры eax и edx. По вкладке CPU мы находим, что eax равен 0, что означает, что мы пытаемся присвоить значение по указателю, равному nil. Взглянув на строчку исходника, которую мы узнали из стека вызовов, мы узнаем имя переменной. Вот вам и причина: переменная оказалась равна nil.

Конечно, эта работа с такой информацией требует минимального знания ассемблера, но зато и является довольно мощным инструментом.

В следующий раз мы поговорим о ситуациях, когда у вас в коде есть ошибка, но никакого исключения не возбуждается. Частично мы уже говорили об этом здесь (например, пункт «1» и пункты «а»-«б» в конце второго раздела). Но в следующий раз мы пойдём чуть дальше и посмотрим, что ещё можно сделать для отлова таких ситуаций. И, в любом случае, у вас всегда есть возможность переписать код ;)

Читать дальше.

См. также: как читать баг-отчёты.

Примечания:
(*) Очень подробно о памяти для приложений рассказывает Марк Руссинович.
(**) Вот ещё один пример, как один и тот же код может демонстировать широкий диапазон поведений.

direktorSan

Удачи!
Участник

ru
Offline Offline
Пол: Мужской


Здравствуйте.
Помогите, пожалуйста, решить одну проблему.
Есть написанный в C++ Builder-e проект.

Есть написанная в MS Visual C++ DLL. Библиотека написана без использования MFC.
(Были причины писать в разных средах.)

Библиотека подключается в проекте явно с помощью LoadLibrary.
В ходе работы проекта из функции prjAddShell вызывается библиотечная функция libAddShell динамического (с использованием new) создания объектов самопального класса TShell, которые содержат потоки.

Указатели TShell* на созданные объекты передаются в проект под типом PVOID. Эти переданные указатели в проекте просто хранятся в списке объектов, т.е. никаких действий с ними не производится.

Далее из проекта из другой функции prjShellDirection вызывается очередная библиотечная функция libShellDirection управления объектами, одним из аргументов которой является указатель типа PVOID на один из объектов. В этой библиотечной функции libShellDirection происходит приведение типа PVOID к типу указателя на класс: TShell* shPtr = static_cast<TShell*>(Arg), после чего производятся некоторые действия с объектом (в т.ч., но необязательно, и его удаление вызовом delete).
В пределах функции libShellDirection все работает нормально. И возврат из этой функции в функцию prjShellDirection проета тоже происходит без проблем.

Но как только происходит выход из функции libShellDirection проекта возникает ошибка: «Access violation at address 00000009. Write of address 020C0D15.» либо «Access violation at address 011835D7. Read of address FFFFFFFF.»

Сперва я грешил на new/delete, т.е. на работу с памятью. Версия не подтвердилась.
Затем я просто убрал из передаваемых параметров указатель и ошибки исчезли (а функциональность, естественно, пострадала).
Также было замечено, что если все вызовы библиотечных функций делать из одной функции проекта, то ошибок не возникает (но функциональность страдает не меньше).

Кто-нибудь знает в чем может быть причина?


Записан
Finch

Спокойный
Администратор

il
Offline Offline
Пол: Мужской

Пролетал мимо


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

P.S. Здесь не принято дублировать темы в нескольких подфорумах.

« Последнее редактирование: 08-10-2005 13:27 от Finch »
Записан

Не будите спашяго дракона.
             Джаффар (Коша)

direktorSan

Удачи!
Участник

ru
Offline Offline
Пол: Мужской


2 Finch
Так в том-то и дело, что я не экспортировал класс из библиотеки. Я создал в библиотеке один глобальный объект, управление которым происходит с помощью четырех простых функций не являющикся методами какого-либо класса.
По поводу несоответствия подходов к выделению памяти.
Память выделяется и высвобождается только(!) внутри этого глобального объекта, т.е. в пределах библиотеки. Ситуаций, когда библиотека выделила память, а прога ее освободила (или наоборот) нет! Из библиотеки в программу на хранение передается только указатель. И тип указателя (PVOID) был выбран специально API-шный, ибо и Борланды и Микрософты должны воспринимать его абсолютно одинаково.
А за дублирование прошу прощения. Просто вопрос касается разных сред программирования и я решил, что количество прочитавших этот вопрос будет несколько больше.


Записан
Finch

Спокойный
Администратор

il
Offline Offline
Пол: Мужской

Пролетал мимо


Память выделяется в одной куче программы. Могут быть косяки здесь. Если это твои модули, то попробуй открыть асемблерный дебагер Билдера и отследить, в какой именно точке возникают ошибки. Где именно происходит запись «Access violation at address 00000009. Write of address 020C0D15.» либо «Access violation at address 011835D7. Read of address FFFFFFFF.» Скорее всего при выходе из функции Билдер делает какую либо допись в эти ячейки.


Записан

Не будите спашяго дракона.
             Джаффар (Коша)

direktorSan

Удачи!
Участник

ru
Offline Offline
Пол: Мужской


Дебагер я пользовал от VC++ когда отлаживал библиотеку.
Но и он показал ассемблерный код проги. Я хоть в асме и не силен, но заметил следующее.
Когда происходит выход из функции командой ret дебагер шел по адресам 0000000X, доходил до 00000009 и возникала ошибка. Но причину я не могу понять.
Насколько я помню (но могу и ошибаться) управление после выхода из ф-ции передается в вызвавшую ф-цию с одновременной очисткой стека передаваемых параметров. Но дальше этих воспоминаний никаких умозаключений построить не могу, ибо так глубоко еще никогда не копал.
Где об этом почитать можно? Пишет ли об этом Рихтер или кто-либо другой?


Записан
direktorSan

Удачи!
Участник

ru
Offline Offline
Пол: Мужской


2 Finch
Настолько озабочен был своей проблемой, что забыл Вас поблагодарить.
Спасибо за то, что занимаетесь моей проблемой!


Записан
direktorSan

Удачи!
Участник

ru
Offline Offline
Пол: Мужской


2 Finch
Только что загрузил проект, просмотрел, как Вы советовали, в дебагере.
Но так как для меня асм, как я уже говорил, лес достаточно темный, то я сделал скриншот.
Скриншот показывает состояние проги после выхода из управляющей ф-ции проекта (ф-ции библиотеки отработали нормально).
Посмотрите, пожалуйста. Чего там не так?


* dbg.JPG (81.49 Кб — загружено 844 раз.)


Записан
Finch

Спокойный
Администратор

il
Offline Offline
Пол: Мужской

Пролетал мимо


Примерно я догадываюсь в чем дело. Ты какие применил схемы возовов функций. stdcall? Судя по тому, куда у тебя выбрасывает программа, у тебя происходит или недобор стэка или перебор онного в функции библиотеки. Поэтому при выходе из процедуры не устанавливается точно адрес возврата. В адресе 0x00000009  программа не может функционировать. Так изначально заложено Виндой. Значит схема вызова функции в самой библиотеке и в твоей программе разные. Как правило на библиотечные функции устанавливают схему stdcall. Приведи отрывки программы где идет присваивание к переменной адреса функции. И кусок библиотеки, где идет определние вызываемых функций.

« Последнее редактирование: 09-10-2005 13:26 от Finch »
Записан

Не будите спашяго дракона.
             Джаффар (Коша)

direktorSan

Удачи!
Участник

ru
Offline Offline
Пол: Мужской


Определение ф-ций в библиотеке
//——————————————————————————
#define EXPORT extern «C» __declspec(dllexport)
EXPORT void Init(void);
EXPORT void Done(void);
EXPORT VoidPtr AddFolder(char * Drive, char * Path, void * ExtFunc);
EXPORT void DirectionFolder(VoidPtr SHObject, int DirectCode);
//——————————————————————————

Вызов одной из этих ф-ций в библиотеке
//—————————————————————————
EXPORT void Init(void)
  {
    FolderController = new TController;
  }
//—————————————————————————

Определение типов в проекте
//—————————————————————————
typedef PVOID VoidPtr;
typedef void (__stdcall* TLibInitFunction)(void);
typedef VoidPtr (__stdcall* TAddFolderFunction)(char * /*Drive*/,
                                                                    char * /*Path*/,
                                                                    void * /*ExtFunc*/);
typedef void (__stdcall* TDirectionFolderFunction)(VoidPtr /*SHObject*/,
                                                                       int /*DirectCode*/);
//—————————————————————————

Определение переменных в проекте
//—————————————————————————
TLibFunction Init, Done;
TAddFolderFunction AddFolder;
TDirectionFolderFunction DirectionFolder;
//—————————————————————————

Инициализация ф-ций в проекте:
//—————————————————————————
void __fastcall TForm1::FormCreate(TObject *Sender)
{
  ShellLib = LoadLibrary(«shellib.dll»);
  Init = (TLibFunction)GetProcAddress(ShellLib, «Init»);
  Done = (TLibFunction)GetProcAddress(ShellLib, «Done»);
  AddFolder = (TAddFolderFunction)GetProcAddress(ShellLib, «AddFolder»);
  DirectionFolder = (TDirectionFolderFunction)GetProcAddress(ShellLib, «DirectionFolder»);
  Init();
}
//—————————————————————————

Вызов ф-ций в проекте
//—————————————————————————
VoidPtr SHObject1;
void __fastcall TForm1::Button1Click(TObject *Sender)
{
  SHObject1 = AddWatchedFolder(Edit1->Text, ExtFunc);
  DirectionFolder(SHObject1, 1);
}
//—————————————————————————

ExtFunc — указатель на функцию проекта, которую при определенных условиях должны вызывать потоки объектов самопального класса TShell.
В принципе вызов этой ф-ции происходит тоже нормально.
Но на всякий случай приведу и ее описание.

Описание в проекте
//—————————————————————————
void __stdcall ExtFunc(char * Str, void * SHObject, bool &Abort, ULONG Attrib);
//—————————————————————————

Определение типа в библиотеке
//—————————————————————————
typedef void __stdcall TExternalFunction(char * /*pObjectName*/,
                                                        void * /*SHObject*/,
                                                        bool & /*Abort*/,
                                                        ULONG /*Attrib*/);
//—————————————————————————

Приведение типа в библиотеке
//—————————————————————————
    efExtFunc = reinterpret_cast<TExternalFunction *>(extFunc);
//—————————————————————————

И, соответственно, вызов в библиотеке
//—————————————————————————
efExtFunc(pObjectName, SHObject, Abort, Attrib);
//—————————————————————————


Записан
direktorSan

Удачи!
Участник

ru
Offline Offline
Пол: Мужской


Небольшое уточнение
В ф-ции AddWatchedFolder(Edit1->Text, ExtFunc) сперва происходит расщепление Edit1->Text на подстроки, а потом вызывается AddFolder.


Записан
Finch

Спокойный
Администратор

il
Offline Offline
Пол: Мужской

Пролетал мимо


#define EXPORT extern «C» __declspec(dllexport)
EXPORT void Init(void);
EXPORT void Done(void);
EXPORT VoidPtr AddFolder(char * Drive, char * Path, void * ExtFunc);
EXPORT void DirectionFolder(VoidPtr SHObject, int DirectCode);

typedef void (__stdcall* TLibInitFunction)(void);
typedef VoidPtr (__stdcall* TAddFolderFunction)(char * /*Drive*/,
                                                                    char * /*Path*/,
                                                                    void * /*ExtFunc*/);
typedef void (__stdcall* TDirectionFolderFunction)(VoidPtr /*SHObject*/,
                                                                       int /*DirectCode*/);

У тебя разные определения вызовов функции. Библиотеку по новой откомпилируй. Но напиши так:

#define EXPORT extern «C» __declspec(dllexport)
EXPORT void __stdcall Init(void);
EXPORT void __stdcall Done(void);
EXPORT VoidPtr __stdcall AddFolder(char * Drive, char * Path, void * ExtFunc);
EXPORT void __stdcall DirectionFolder(VoidPtr SHObject, int DirectCode);

Чтобы не было проблем с именами, подключи к проекту библиотеки Def файл

LIBRARY     Temple                       

DESCRIPTION ‘Probe DLL’

EXPORTS
          Init                  @1
          Done               @2
          AddFolder        @3
          DirectionFolder  @4

Имя файла должно быть Название_твоей_Библиотеки.def
В поле LIBRARY Укажи имя библиотеки заместо Temple

« Последнее редактирование: 20-12-2007 19:40 от Алексей1153++ »
Записан

Не будите спашяго дракона.
             Джаффар (Коша)

direktorSan

Удачи!
Участник

ru
Offline Offline
Пол: Мужской


Забыл самое главное!
Когда происходит вызов такой:
//——————————————————————————
void __fastcall TForm1::Button1Click(TObject *Sender)
{
  SHObject1 = AddWatchedFolder(Edit1->Text, ExtFunc);
  DirectionFolder(SHObject1, 1);
}
//——————————————————————————
никаких ошибок.

А вот если далее мы сделаем вызов из другой ф-ции
//——————————————————————————
void __fastcall TForm1::Button2Click(TObject *Sender)
{
  if (Folder1 != NULL)
    {
      DirectionFolder(Folder1, 4);
      Folder1 = NULL;
    };
  if (Folder2 != NULL)
    {
      DirectionFolder(Folder2, 4);
      Folder2 = NULL;
    };
}
//—————————————————————————
вылазит ошибка.

Только что сделал так как Вы советуете:
EXPORT void __stdcall Init(void);

Ошибка прет уже на этапе вызова ф-ции Init.

shellib.def файл у меня есть:
//—————————————————————————
LIBRARY      shellib.dll
EXPORTS      Init
                   Done
                  AddFolder
                  DirectionFolder
//—————————————————————————

Там, правда, нет номеров у функций. Но Рихтер тоже не рекомендует их употреблять.


Записан
direktorSan

Удачи!
Участник

ru
Offline Offline
Пол: Мужской


Я, конечно, понимаю, что есть обходной вариант.
Сделать в библиотеке небольшой динамический список.
При создании объектов с потоками их адреса хранить в этом списке, а во внешний мир передавать какой-нибудь идентификатор тип int, а управление этими объектами производить по этим идентификаторам.
Тогда гарантированно ошибок не будет.
Но, блин, интересно почему после так происходит именно со ссылками?


Записан
direktorSan

Удачи!
Участник

ru
Offline Offline
Пол: Мужской


Однако, погорячился я на счет «гарантированно»!
Выскакивает ошибочка!

PS
Фразу «Но, блин, интересно почему после так происходит именно со ссылками?»
надо читать так «Но, блин, интересно почему так происходит именно со ссылками?»


Записан
Finch

Спокойный
Администратор

il
Offline Offline
Пол: Мужской

Пролетал мимо


Таже самая ошибка выскочила? Или другая когда вызываеш Init. Жалко нету сейчас билдера под рукой. На растоянии ничего сказать не смогу.

« Последнее редактирование: 09-10-2005 16:56 от Finch »
Записан

Не будите спашяго дракона.
             Джаффар (Коша)

direktorSan

Удачи!
Участник

ru
Offline Offline
Пол: Мужской


Адрес стал 00000013. А в остальном все так же не хорошо.


Записан
direktorSan

Удачи!
Участник

ru
Offline Offline
Пол: Мужской


Вообще лучше расскажу предысторию!
Мне нужно было, чтобы несколько потоков следили за несколькими папками (по правилу «один поток — одна папка»). Хотел было сделать потоки в проекте. Но потом вспомнил об организации подключения библиотек (в частности об общем адресном пространстве). И понял, что все потоки будут создавать классы, которые будут теряться и память из-под них высвобождаться не будет.
Потом у Рихтера прочитал про TLS (Thread Local Storage {или как-то так}). Но идея мне по вкусу не пришлась и решил я делать вот как.
Прога создает несколько объектов. Каждый объект вызывает библиотечную ф-цию по созданию еще одного объекта, который, в свою очередь, владеет объектом-потоком! Адреса этих объектов с потоками передаются из библиотеки в прогу и сохраняются в инициировавших всю эту процедуру объектах.

Сказано — сделано.
Есть библиотека. Есть тестовая прога.
При создании библиотечного объекта с потоком и запуске его:
//——————————————————————————
void __fastcall TForm1::Button1Click(TObject *Sender)
{
  SHObject1 = AddWatchedFolder(Edit1->Text, ExtFunc);
  DirectionFolder(SHObject1, 1);
}
//——————————————————————————
ошибок не возникает — поток поточит и следит за папками.

//——————————————————————————
Правда здесь тоже не без вывертов.
Функция потока — это статичный метод объекта-владельца.
Параметр ф-ции потока — ссылка на сам объект владелец.
Вот как это происходит:

DWORD WINAPI c_Shell::ThreadFunc(void * pArg)
  {
    TShellPtr tsShell = reinterpret_cast<TShellPtr>(pArg);
    tsShell->Monitoring();
    tsShell = NULL;
    return 0; //пусть всегда будет ноль
  }

Ф-ция tsShell->Monitoring() это цикл, выход из которого происходит по некоторому Event-у.

//——————————————————————————

Но как только хочется мне поток убить нажатием другой кнопки — поток убивается и вылезает ошибка!

Я, знаете ли, как Сухов: «Хотелось бы помучиться!» Улыбаюсь))


Записан
Finch

Спокойный
Администратор

il
Offline Offline
Пол: Мужской

Пролетал мимо


Процессор наверно у тебя загружен на все 100. У винды есть славная функция по мониторингу папок FindFirstChangeNotification которая выдаст Handle. Затем только поток останется повесить на функцию WaitForSingleObject. Которая прекратит работу потока, до первого изменения. Затем только останется Вызвать FindNextChangeNotification чтобы возобновлять мониторинг. Ну естественно FindCloseChangeNotification чтобы прекратить мониторинг и закрыть Хэндл.

Да кстати тоже самое можно сделать и на Билдере. У него тоже есть объекты-потоки. Предположение, что косяк с разделением памяти смутно остается.
В асме подсчитай количество байт загнаных Push и взятых Pop из стэка. При входе в функцию, в самой функции и при выходе. Если оно равно. Значит стэк кто то у тебя бомбит. Если библиотека не отлажена в VC может быть ты переходиш граници диапазонов.

« Последнее редактирование: 09-10-2005 18:10 от Finch »
Записан

Не будите спашяго дракона.
             Джаффар (Коша)

direktorSan

Удачи!
Участник

ru
Offline Offline
Пол: Мужской


Это ф-ция мониторинга.
Здесь и используются эти нотификации.
Кстати. Пример взят из MSDN и просто доработан.
//——————————————————————————
void c_Shell::Monitoring(void)
  {
    DWORD dwWaitStatus;
    bool flag = true;
    // Предварительно должен быть инициализирован массив dwChangeHandles[]
    while ((flag) && (!IfTerminateMonitoring))
      {
        // Wait for notification.
        dwWaitStatus = WaitForMultipleObjects(3,
                                              dwChangeHandles,
                                              FALSE,
                                              INFINITE); //в момент изменения
        switch (dwWaitStatus)
          {
            case WAIT_OBJECT_0:
              { // произошло изменение среди файлов
                GetFolderItems(NULL,
                                Pointers.lpsfFolder,
                                (SHCONTF)(SHCONTF_NONFOLDERS
                                          | SHCONTF_INCLUDEHIDDEN));
                if (!FindNextChangeNotification(dwChangeHandles[0]))
                  flag = false;
              }; break;
            case WAIT_OBJECT_0 + 1:
              { // произошло изменение среди папок
                GetFolderItems(NULL,
                                Pointers.lpsfFolder,
                                (SHCONTF)(SHCONTF_FOLDERS
                                          | SHCONTF_INCLUDEHIDDEN));
                if (!FindNextChangeNotification(dwChangeHandles[1]))
                  flag = false;
              }; break;
            case WAIT_OBJECT_0 + 2:
              { // пришло сообщение о завершении работы
                flag = false;
                IfTerminateMonitoring = true;
                ExitThread(0);
              }; break;
        };
      };
  }
//——————————————————————————

Загрузку проца я проверял — при работе двух потоков увеличивается всего на два процента!
Тестирую программу на компутере Pentium-233 MMX (разогнан до 250MHz), 128 Мб ОЗУ, Винда 98-я.
В среднем при обычной работе загрузка проца составляет процентов 15-20.
Компутер такой слабенький для того, чтобы чувствовалась различного рода оптимизация по скорости работы.

Правда вся славность функции FindFirstChangeNotification ограничивается ее ограничениями: я никак (пока) не добился того, чтобы мне возвращалось имя измененного объекта папки.
Вот в WinNT есть для такого рода занятий действительно славная ф-ция: ReadDirectoryChangesW (или как-то похоже). На нее в MSDN-е даже отдельный пример дан. Работает она с помощью CreateCompletionPort (или как-то похоже).
Но, блин, в Вин’98 она не поддерживается, а вот на CreateCompletionPort компилятор под Вин’98 не ругнулся. Хотя MSDN говорит, что и она поддерживается только в NT.

« Последнее редактирование: 20-12-2007 19:42 от Алексей1153++ »
Записан
Алик

Постоялец

kz
Offline Offline


Почитал тему, посмотрел приведенные коды.
думаю, проблема (Access Violation) — именно в разных конвенциях вызова: в проекте прототипы функций объявлены _stdcall, а в длл — нет и по умолчанию подчиняются правилам _cdecl

direktorSan, разобрался с проблемой?


Записан
direktorSan

Удачи!
Участник

ru
Offline Offline
Пол: Мужской


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


Записан
Алик

Постоялец

kz
Offline Offline


Интересно,
а что за ошибки?
имеется ввиду, GetProcAddress ошибку дает? а GetLastError что говорит?


Записан
direktorSan

Удачи!
Участник

ru
Offline Offline
Пол: Мужской


Да. Ошибку кидал GetProcAddress.
Однако GetLastError-ом не смотрел.
Сейчас я уже не помню что за номер ошибки.
Посмотрю на выходных и тогда отвечу.


Записан
direktorSan

Удачи!
Участник

ru
Offline Offline
Пол: Мужской


Ур-р-р-р-а-а-а! Заработала!

Разобрался!

Дело было так.

Установил я экспорт ф-ций в __stdcall.
При попытке возвратить адрес GetProcAddress выдал ошибку «Не найдено процедуры с таким именем».

Тогда вспомнил, что Рихтер об этом что-то писал.
Поискал и нашел следующее:
«Тогда компилятор Microsoft искажает имя С-функции: впереди ставит знак подчеркивания, а к концу добавляет суффикс, состоящий из символа @ и числа байтов, передаваемых функции в качестве параметров.» (Д.РИХТЕР, «Создание эффективных WIN32-приложений»)

Это он говорил про __stdcall-функции DLL написанных в VC++, которые потом будут вызываться из EXE-шников, созданных другими компиляторами.

Там же предложено два способа борьбы.

1. DEF-файл с разделом EXPORTS.
У меня он почему-то не работал. Хотя проверил везде, где только мог. Наверное там, где не смог проверить и осталась какая-то галочка. Улыбаюсь

2. #pragma comment(linker, «/export:Init=_Init@0»)
Вставляя такую строку в h-файл библиотеки мы указываем компилятору, что нужно экспортировать не только имя _Init@0, но и равное ему имя Init.
Вот с таким способом все заработало!

Всем спасибо.


Записан
zakalibit

Гость


poprobui «implib -m <libfile name to generate> <VC++ DLL name>», ne pomniu tochno esli -m nado, posmotri v help u implib, tam esti switch otveceaiusii za name mangling, potm prosto impotiruies .lib v svoi C++ Builder proect i vse, igolova ne bolit o vseh konversiah


Записан
** pasha

Отправлено: 30.06.2004, 11:34

Не зарегистрирован

Непонятная ситуация.
Есть программа exe, во время работы она может
динамически подгружать dll (до конца работы программы)

Работает все нормально.

Если dll так и не вызывалась — завершение программы проходит
без ошибок.

Если dll вызывалась — зависит от Build:

1. Если Build -> Linker -> стоит галочка в Use Dynamic RTL — программа
завершается без ошибок.

2. Если галочку снять — при завершении программы (закрытии главной
формы по нажатию на [х]), выскакивает непонятный
AccessViolation.
Обработчик (OnClose) отсутствует.

Может у кого-нибудь такое было, что-то посоветуете ?

Asher

Отправлено: 30.06.2004, 12:25

Мастер участка

Группа: Модератор
Сообщений: 550

Скорей всего при выходе не удаляешь объект, созданный в dll
Или удаляешь, но в основной программе.
Воспользуйся CodeGuard’ом
** pasha

Отправлено: 30.06.2004, 12:48

Не зарегистрирован

А как его включить и где ?
В exe или в каждой из подгружаемых dll тоже ?
Asher

Отправлено: 30.06.2004, 13:10

Мастер участка

Группа: Модератор
Сообщений: 550

QUOTE
А как его включить и где ?


Project->Options…->CodeGuard->CodeGuard Validation

QUOTE
В exe или в каждой из подгружаемых dll тоже ?


Для начала можно только в ехе wink.gif

** pasha

Отправлено: 30.06.2004, 13:14

Не зарегистрирован

QUOTE
Error 00047. 0x310000 (Thread 0x0590):
Bad parameter: A bad memory block (0x141E888) has been passed to the
function.
Error 00052. 0x310000 ® (Thread 0x0590):
Bad parameter: A bad memory block (0x31DA650) has been passed to the
function.

Включил.
Во время работы программы ничего не изменилось,
никаких сообщений не появлялось, при закрытии — опять
выскочила та-же ошибка.

Если я правильно понял, то он создал вот такой .cgl файл
Это все, или он может что-то еще мне сказать,
и что делать дальше с этими 2 ошибками ?

Asher

Отправлено: 30.06.2004, 13:28

Мастер участка

Группа: Модератор
Сообщений: 550

Добавте еще
Project->Options…->Linker->Use Debug Librares

И откройте окно
View->Debug Windows->Code Guard Log

** pasha

Отправлено: 30.06.2004, 14:06

Не зарегистрирован

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

В .cgl файле вижу только вот это:

QUOTE
Error 00045. 0x310000 (Thread 0x0454):
Bad parameter: A bad memory block (0x144E960) has been passed to the
function.

Asher

Отправлено: 30.06.2004, 14:22

Мастер участка

Группа: Модератор
Сообщений: 550

Не густо. Надо подумать.
Кстати где вы делаете FreeLibrary ?
** pasha

Отправлено: 30.06.2004, 14:41

Не зарегистрирован

В exe. При закрытии формы (завершении программы)
(см на 3 темы ниже тему «Выгрузка dll» )

Но это ни на что не влияет, делаю я эту выгрузку
или нет, даже если и не делаю,
ошибка вылетает все равно.

И что непонятно, ведь если оставить галочку
Linker -> Use Dynamic Library, никакой ошибки не вылетает.

Пробовал на другом компе (отладочном), без установленного
С++Builder 6 — все то-же самое:

Скопировал программу, она затребовала еще естественно
borlndmm.dll и сс3260mt.dll (c галочкой в Use Dynamic Library)
скопировал их в папку с программой — все работает без ошибок,
теперь
убил эти 2 dll, скопировал и запустил программу (без галочки в Use Dynamic Library), то есть по идее всё включилось в это exe — работает
с той-же самой ошибкой.

Что подозреваю — в своих динамически загружаемых dll
создаю формы:

CODE
#include «UFDLL.h»
extern «C» void __declspec(dllexport) myFunc(char* sTbl);

void myFunc(char* sTbl)
{
 char tmp[100];
 for(int i=0; i<100; i++) tmp[i] = »;

 TFDLL* myForm = new TFDLL(Application);
 strcpy(tmp, sTbl);
 myForm->sTbl = AnsiString(tmp);
 myForm->ShowModal();
}

Эту форма при закрытии должна удаляться:

CODE
void __fastcall TFDll::FormClose(TObject *Sender,
     TCloseAction &Action)
{
    Action = caFree;
}

Может тут что не так ?
(Пробовал и без Action = caFree, то есть
в myFunc делал: delete myForm;
да и просто не удалял — не помогает.)

Asher

Отправлено: 30.06.2004, 14:53

Мастер участка

Группа: Модератор
Сообщений: 550

Когда стоит Linker -> Use Dynamic Library они работают в одном адресном пространстве. Поэтому ошибки и нет.
Посмотри темы по форуму BorlandXportal:
Как корректно вызвать форму из DLL?
и
Использование DLL с VCL-формами
** pasha

Отправлено: 30.06.2004, 17:01

Не зарегистрирован

Почитал, попробовал.
и Application->Handle присваивал
и сам Application — увы, не помогло.

Без галочки в Use Dynamic Library все работает,
с галочкой в в Use Dynamic Library — при завершении — ошибка.

Но это надо таскать эти 2 библиотеки, да возможно
что где-то это и по-другому сглючит. sad.gif

** pasha

Отправлено: 30.06.2004, 17:04

Не зарегистрирован

То есть наоборот,
Без галочки в Use Dynamic Library — при завершении — ошибка,
с галочкой в в Use Dynamic Library — все работает,
AVC

Отправлено: 01.07.2004, 08:36

Ветеран

Группа: Модератор
Сообщений: 1583

Если вы в dll сбрасываете Borland’овские формы или функции, то могу поделиться опытом использования. Схема отработана давным давно и ни разу не заглючила независимо от галочек.

1. Части собораются в Borland’овские пакеты bpl (ни чем не отличаются от dll за исключением именования глобальных символов).
2. Пакеты подключаются либо статически (в менеджере проекта) либо динамически (LoadPackage)
3. Глобальные переменные и функции описываются

CODE
// для использования
extern PACKAGE void __fastcall sShow_Okno (const int pPersonalAccountID);
extern PACKAGE AnsiString Computer_Name;

// в пакете
AnsiString Computer_Name;

PACKAGE void __fastcall sShow_Okno (const int pPersonalAccountID)
{
TF_HACalk *frm = new TF_HACalk(Application);
try {
frm->Visible = false;
if (frm->Prepare(pPersonalAccountID,NULL)) frm->ShowModal();
} // try

catch (Exception &xcp) { OEMessage_Show(xcp.Message); }

delete frm;
}


4. Работа с пакетом
5. Выгрузка пакета (варианты см. тему …dll) (UnloadPackage)

MDM

Отправлено: 01.07.2004, 10:47

Ученик-кочегар

Группа: Участник
Сообщений: 23

QUOTE (** pasha @ 30/06/2004, 12:36)
зависит от Build:

2. Если галочку снять — при завершении программы (закрытии главной
формы по нажатию на [х]), выскакивает непонятный
AccessViolation.
Обработчик (OnClose) отсутствует.

Может у кого-нибудь такое было, что-то посоветуете ?


Может виноват Application?
При статической линковке Application берется от приложения (Я так думаю…???) и тогда:
Выгружается dll — форма удаляется, далее Application находит у себя ссылку на форму (отсюда -> new TForm(Application)) и пытается удалить ее еще раз.
Попробуй передавать в — new TForm(Application) чего-нибудь другое или при удалении формы присвой Form1=NULL.

** pasha

Отправлено: 01.07.2004, 11:06

Не зарегистрирован

toAVC

Попробовал, переделал под Bpl с динамической загрузкой
(LoadPackage)
Теперь программа отвечает:
«Application is not licensed to use this feature»
после загрузки пакета при попытке вызова функции из
пакета.

А как описывать вызываемые функции и их типы ?
(и в BPL и в EXE ?)

Как
extern «C» void __declspec(dllexport) PackageSay(char *WhatToSay);
или
extern PACKAGE void PackageSay(char *WhatToSay);
и как описать их тип в typedef

AVC

Отправлено: 01.07.2004, 11:27

Ветеран

Группа: Модератор
Сообщений: 1583

При статической загрузке (меньше всего головной боли)
в exe:
extern PACKAGE AnsiString Value1;
extern PACKAGE void __fastcall Fun1 (…);

в пакете
PACKAGE AnsiString Value1;
PACKAGE void __fastcall Fun1 (…)
{…}

Вызов из exe или других пакетов
Value1 = «aaa»; Fun1();

При динамической загрузке
так же как и при работе с dll
поиск адреса по имени и косвенный вызов
int funadr = (int)GetProcAddress((HMODULE)lbm->instance, funname.c_str());
if (bpl) ((void __fastcall (*)(void))funadr)();
if(dll) ((void __stdcall (*)(void))funadr)();

Кусок кода сдесь

** pasha

Отправлено: 01.07.2004, 11:43

Не зарегистрирован

Нужна только динамическая загрузка.
Как я понял, в этом случае используется
extern «C» void __declspec(dllexport) PackageSay(char *WhatToSay);
и в пакете и в exe, а не PACKAGE ?
AVC

Отправлено: 01.07.2004, 11:58

Ветеран

Группа: Модератор
Сообщений: 1583

Смотрите пример там есть все, что я хотел сказать.

Без Package
Описание в пакете:
extern «C» __declspec(dllexport) void CTB_Correct (void);
void CTB_Correct (void) { … }

Использование:
try { LEUP_void («_CTB_Correct», «TBCOR.DLL»); }
catch (Exception &xcp) { ShowMessage(xcp.Message); }

** pasha

Отправлено: 01.07.2004, 12:05

Не зарегистрирован

Так вот я так и пробовал — он пишет:
«Application is not licensed to use this feature»
после загрузки пакета при попытке вызова функции из пакета
AVC

Отправлено: 01.07.2004, 12:07

Ветеран

Группа: Модератор
Сообщений: 1583

Проверьте параметр пакета DesignTime / RunTime

Отредактировано AVC — 01/07/2004, 12:09

** pasha

Отправлено: 01.07.2004, 12:25

Не зарегистрирован

Проверил — стояло Runtime and Design
Поставил только Runtime — тоже самое сообщение.

Я использую сторонние компоненты в форме, создаваемой DLL
(EhLib, RxLib, FIBPlus, FastReport)
может это в них что-то встроено ?

Кстати, если ставлю галочку в Build With Runtime Packages —
то работает, без этой галочки — увы.
(да и работает так-же криво как и dll — дает ошибку при завершении
приложения)

Но мне то нужно, чтобы мои bpl подгружались динамически,
а стандартные bpl — входили в exe

AVC

Отправлено: 01.07.2004, 12:39

Ветеран

Группа: Модератор
Сообщений: 1583

Я с таким сталкивался на ворованом FastReport. Помогло включение FastReport как runtime bpl.

QUOTE
Но мне то нужно, чтобы мои bpl подгружались динамически,
а стандартные bpl — входили в exe


Укажите в перечне, только то что вам нужно.

PS.
Под динмическими bpl я понимал не те которыми управляет Borland (при включении галочки Runtime packages) а те которыми управляю я через Load/Unload. С моей точки зрения галочку Borlanda надо бы назвать External packages

** pasha

Отправлено: 01.07.2004, 12:57

Не зарегистрирован

FastReport и FIBplus у меня законные, купленные smile.gif
RxLib + EhLib вроде как бесплатные, других сторонних
компонентов в этих формах не использовал.

Ладно, придется оставлять в виде dll + borlndmm.dll + cc3260mt.dll
прикладываеть к проекту, так работает и работает без ошибок.

Понравилась статья? Поделить с друзьями:

Не пропустите эти материалы по теме:

  • Яндекс еда ошибка привязки карты
  • C 3102 ошибка konica minolta
  • C 2556 ошибка konica
  • C 1611 ошибка киа сид
  • C 101101 ошибка вольво

  • 0 0 голоса
    Рейтинг статьи
    Подписаться
    Уведомить о
    guest

    0 комментариев
    Старые
    Новые Популярные
    Межтекстовые Отзывы
    Посмотреть все комментарии