Страницы

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

вторник, 7 апреля 2020 г.

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

#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().

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

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