#c_sharp #winforms
Стоит задача изменения используемых в приложении форм во время его работы. копал в сторону выгрузки динамических библиотек (чтобы они содержали формы). Т.е. загрузил в 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? Как-то это негибко и громоздко получается
Ответы
Ответ 1
Внимание, ответ по ''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 вполне реально, надо быть просто внимательнее. Всем спасибо за помощьОтвет 2
Попробуйте в дочернем контроле перегрузить свойство CreateParams, куда записать дескриптор родительского контрола: protected override CreateParams CreateParams { get { CreateParams createParams = base.CreateParams; createParams.Parent = ...; // Сюда надо передать Handle формы return createParams; } } При этом добавлять такой контрол в коллекцию Controls не нужно. Вместо этого надо передать ему свой Handle, установить Visible = true и вызвать метод CreateControl().
Комментариев нет:
Отправить комментарий