Стоит задача изменения используемых в приложении форм во время его работы. копал в сторону выгрузки динамических библиотек (чтобы они содержали формы). Т.е. загрузил в 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 вполне реально, надо быть просто внимательнее. Всем спасибо за помощь
Комментариев нет:
Отправить комментарий