Страницы

Поиск по вопросам

пятница, 21 июня 2019 г.

Реализация приложения с обновляемыми формами

Стоит задача изменения используемых в приложении форм во время его работы. копал в сторону выгрузки динамических библиотек (чтобы они содержали формы). Т.е. загрузил в AppDomain библиотеку, получил нужный
контрол (допустим, что обновлять нужно не только формы) разместил его где нужно, отобразил с его помощью нужные данные.
Получив сигнал о необходимости обновить контрол - свернули всё это хозяйство, выгрузили домен, получили новую версию DLL, создали новый домен - и поехали заново.
Проблемы, возникшие по ходу реализации:
Вся инфа по работе между доменами ведет к интерфейсам и неким абстрактным классам - а мне нужен хотя бы System.Forms.Control При обмене данными между доменами приложений используется маршаллинг. Используемые контролы имеют всего лишь несколько внешних свойств, которые позволяют их настроить и для получения данных они используют
OracleConnection, который не может быть передан между доменами (он не маршаллится).
т.е. выходит, что подход изначально не верный - загрузка библиотеки в другой домен, создание контрола отображения для данных из oracle-сессии из первого домена.
Как можно реализовать набор таких обновлямых контролов, причем не прерывая соединения с бд, т.е. не выгружая ехе, если это реально?
UPDATE попробовал передать между доменами
общая библиотека OracleCall.dll //интерфейс для класса, через который передаю оригинальный оракловый коннекшн public interface IConnectInitializer { void CreateConnectionClone(Oracle.DataAccess.Client.OracleConnection conn); }
вторая библиотека libForNewClasses.dll, содержащая ссылку на общую в этой библиотеке реализуется статический хранитель оралового соединения:
public class oracleConnInitializer : MarshalByRefObject, IConnectInitializer { public static Oracle.DataAccess.Client.OracleConnection conn;
public void CreateConnectionClone(Oracle.DataAccess.Client.OracleConnection connection) { conn = (Oracle.DataAccess.Client.OracleConnection)connection.Clone(); conn.Open(); } }
и контрол:
public class newControl : UserControl { private void newControl_Load(object sender, EventArgs e) { OracleCall.OraCallProc.getData(oracleConnInitializer.conn); } }
третий модуль, ехе-шник, содержит ссылку на первый, общий, чтобы работать с передачей OracleConnection в нем находится форма, на которую и нужно положить контрол из второй библиотеки.
public partial class Form1 : Form {
// образец соединения static Oracle.DataAccess.Client.OracleConnection conn = new Oracle.DataAccess.Client.OracleConnection(connString);
// домен, куда будем грузить третью библиотеку с дополнительным контролом AppDomain dmn;
public Form1() { InitializeComponent(); init(); } void init() { // создаем домен dmn = AppDomain.CreateDomain("newDomain");
// загружаем библиотеку с контролом Assembly asm = dmn.Load("libForNewClasses");
// создаем передатчик коннекшена IConnectInitializer connInit = (IConnectInitializer) dmn.CreateInstanceAndUnwrap("libForNewClasses", "libForNewClasses.oracleConnInitializer");
// передаем коннекшн connInit.CreateConnectionClone(conn);
// создаем контрол Control ctrl = (Control) dmn.CreateInstanceAndUnwrap("libForNewClasses", "libForNewClasses.newControl");
this.Controls.Add(ctrl);// пытаемся добавить - БАБАХ! ОШИБКА!!!! } }
ОШИБКА: Remoting cannot find field 'parent' on type 'System.Windows.Forms.Control'.
Что я делаю не так?
UPDATE 2 переделал общую библиотеку. дабы придать ей общий вид обзовем ее common.dll теперь она содержит образец контрола: public class myControls : UserControl { private Button button1;
private void InitializeComponent() { // ... } public IntPtr _handle { get; set; } public void SetParentHandle(IntPtr handle) { _handle = handle; } protected override CreateParams CreateParams { get { CreateParams createParams = base.CreateParams; createParams.Parent = _handle; // Сюда надо передать Handle формы return createParams; } } protected Oracle.DataAccess.Client.OracleConnection Connection; public void InitConnection(Oracle.DataAccess.Client.OracleConnection conn) { Connection = conn; } protected override void OnLoad(EventArgs e) { base.OnLoad(e); InitializeComponent(); // почему-то без этого не видно } }
дочерний контрол также изменил свой код:
public class newControl : myControls { private Label label1;
private void InitializeComponent() { // ... }
protected override void OnLoad(EventArgs e) { base.OnLoad(e); InitializeComponent(); // почему-то без этого не видно OraCallProc.getData(Connection); // БАБАХ! падаем с ошибкой! } }
ушел от статического хранения соединения, теперь на главной форме:
public partial class Form1 : Form { // образец соединения static Oracle.DataAccess.Client.OracleConnection conn = new Oracle.DataAccess.Client.OracleConnection(connString); // домен, куда будем грузить третью библиотеку с дополнительным контролом AppDomain dmn; public Form1() { InitializeComponent(); init(); } void init() { // открываем conn.Open(); // создаем dmn = AppDomain.CreateDomain("newDomain"); // грузим сборку в новый домен Assembly asm = dmn.Load("libForNewClasses"); // создаем контрол myControls ctrl = (myControls) dmn.CreateInstanceAndUnwrap("libForNewClasses", "libForNewClasses.newControl"); // отправляем контролу соединение ctrl.InitConnection(conn); // устанавливанем хэндл формы ctrl.SetParentHandle(this.Handle); ctrl.Visible = true; ctrl.CreateControl(); // БАБАХ! падаем с ошибкой! (это та, что на OnLoad) } }
вопросы:
1) ошибка: Remoting cannot find field 'm_collRef' on type 'Oracle.DataAccess.Client.OracleParameter'.
2) даже если не подключаться к базе, newControl теряет весь свой вид. Почему приходится самому переинициализировать в OnLoad? Не красиво ведь в каждом наследнике переопределять
3) т.е. для каждого типа нового контрола нужно создать наследника (будь то TextEdit, ListView и подобных), даже если нет необходимости подключаться к БД, мне нужен метод SetParentHandle? Как-то это негибко и громоздко получается


Ответ

Внимание, ответ по ''Remoting cannot find field 'm_collRef' on type'' Ошибка была не в передаче соединения. С ним работа была налажена. Она крылась в процедуре
OraCallProc.getData(Connection);
это был статический метод, который выглядел примерно так:
public static DataTable getData(OracleConnection conn) { using (OracleCommand cmd = conn.CreateCommand()) { cmd.CommandType = System.Data.CommandType.StoredProcedure; cmd.CommandText = "TESTSHEME.TESTPROC.GETDATAFUNCTION"; OracleParameter outvalue = new OracleParameter(); // неправильно! объект создается не в том домене!!! outvalue.OracleDbType = OracleDbType.RefCursor; outvalue.Direction = ParameterDirection.ReturnValue; outvalue.ParameterName = "OUTP"; cmd.Parameters.Add(outvalue); // при попытке добавить объект из одного домена к списку параметров команды из другого возникает ошибка с m_colRef
// вообще и здесь не верно. OracleDataAdapter также создается в другом домене OracleDataAdapter da = new OracleDataAdapter(cmd); DataTable dt = new DataTable(); da.Fill(dt); // здесь адаптер пытается обратиться к соединению из другого домена, пытается изменить m_state, и тоже падаем return dt;
} }
решить проблему получилось таким образом:
public static DataTable getData(OracleConnection conn) { using (OracleCommand cmd = conn.CreateCommand()) { cmd.CommandText = "TESTSHEME.TESTPROC.GETDATAFUNCTION"; cmd.CommandType = System.Data.CommandType.StoredProcedure; OracleParameter outvalue = cmd.CreateParameter(); //создается в том же потоке, что и cmd outvalue.OracleDbType = OracleDbType.RefCursor; outvalue.Direction = ParameterDirection.ReturnValue; outvalue.ParameterName = "OUTP"; cmd.Parameters.Add(outvalue); // поэтому здесь нет ошибки междоменного доступа к внутренним полям // OracleDataAdapter не нашел как создать в том же домене, зато есть ExecuteReader DataTable dt = new DataTable(OracleDataAdapter.DefaultSourceTableName); using (OracleDataReader r = cmd.ExecuteReader()) { for (int i = 0; i < r.FieldCount; i++) dt.Columns.Add(r.GetName(i), r.GetFieldType(i)); object[] rowValues = new object[r.FieldCount]; while (r.Read()) { // не нашел метода получения значений всей строки, получаю поштучно for (int i = 0; i < r.FieldCount; i++) // мне нужны "чистые" значения, поэтому DBNull -> null rowValues[i] = object.Equals(r.GetValue(i), DBNull.Value) ? null : r.GetValue(i); dt.Rows.Add(rowValues); } }
return dt; } }
Так что междоменное взаимодействие с OracleConnection вполне реально, надо быть просто внимательнее. Всем спасибо за помощь

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

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