Сложный технический вопрос по DLL - как передавать указатели на объекты? - страница 3

 

Вот тебе и раз. Чувствую себя так, как будто из какой-нибудь пещеры вылез :) Такие дела творятся, а я про них ничего не знаю :)

Надо будет перебираться на новый язык потихоньку.

 

Пользуйтесь кнопкой SRC, чтобы код вставлять.

CanSee:

Ну, в общем-то, код показать всегда лучше, хотя он и кривоват :) Я решил пробовать без указателей (через хранение их в статических переменных), но проблемный код всё же приведу.

Сначала объяснение, как оно работает. Используется Qt 4. В DLL объявлены экспортируемые функции - те, которые будут вызываться из MQL4. Также описан класс, который отвечает за работу с БД. Назначение класса простое - по запросу от робота открывает соединение с БД и сохраняет его во внутренней переменной, а функция возвращает указатель на созданный объект этого класса роботу. Потом другая функция принимает от робота этот указатель и данные, которые роботу надо в базу записать. Все данные передаются параметрами фу6кций с простыми типами - int, double, string, больше ничего. При завершении работы вызывается функция деинициализации, которая вызывает в классе функцию закрытия соединения и затем освобождает объект класса. Чтение данных из БД внутри робота не производится (планируется в будущих версиях по той же схеме). Вот с передачей указателя возникла проблема. Вернее, я думаю, что проблема с передачей указателя. Но на самом деле она может быть и в чем-то другом.

Функция инициализации - создаёт объект класса MqlDll (который работает с БД), открывает соединение через вызов функции класса и возвращает ссылку на класс, преобразованную в int, роботу:

MT4_EXPFUNC int __stdcall OpenConnMySql(char* sConnNameIn, char* sHostNameIn, int iPortIn, char* sDbNameIn, char* sUserIn, char* sPassIn)
 {
  // Создаём объект MqlDll:
  MqlDll * dllClass = new MqlDll();
  // Пробуем открыть соединение с MySql:
  dllClass->openMySqlDb(QString(sConnNameIn), QString(sHostNameIn), iPortIn, QString(sDbNameIn), QString(sUserIn), QString(sPassIn));
  // Возвращаем ссылку на полученный класс:
  return (int)dllClass;
}
Описание этой функции в роботе на MQL4:
int OpenConnMySql(string sConnNameIn, string sHostNameIn, int iPortIn, string sDbNameIn, string sUserIn, string sPassIn);
Функция деинициализации (закрытие соединения выполняется в деструкторе класса):
MT4_EXPFUNC void __stdcall CloseConnection(const int iPointer)
 {
  MqlDll * dllClass = (MqlDll*)iPointer;
  delete dllClass;
 }
Описание этой функции в роботе на MQL4. Полученный от функции инициализации указатель робот передаёт в функцию, как int:
void CloseConnection(int iPointer);
Ну и для примера одна из функций, работающих с указателем. Служит для получения от класса описания последней ошибки, произошедшей при работе с БД:
MT4_EXPFUNC char* __stdcall GetErrorMsg(const int iPointer)
 {
  // Преобразуем переданный int в указатель на класс:
  MqlDll * dllClass = (MqlDll *)iPointer;
  // Вызываем функцию получения описания последней ошибки:
  return dllClass->getErrorMsg().toLatin1().data();
 }
Описание на MQL4:
string GetErrorMsg(int iPointer);
Вот примерно так оно всё и работало.

1. Между запусками эксперта требуется держать данные в классе MqlDll? Эксперт, может даже несколько экспертов, могут быть загружены/выгружены несколько раз и, если все потоки завершились, то библиотека выгрузится. Данные класса будут потеряны.

2. Если класс объявлен глобально и ссылается на другие глобальные объекты, если они существуют (?), то синглетон необходим. Иначе, возможны непрогнозируемые разнообразные ошибки. Причём, на каждый раз новые :-)

3. Пользуетесь всеми параметрами функции инициализации? Т.е. у Вас несколько вызовов с разными значениями?

4. По поводу коммутации. Подойдёт ассоциативный контейнер (std::map). Складываейте в него пары идентификатор-указатель. Снаружи оперируйте идентификатором. Андрей выше написал, как делать.

 
TheXpert:
Ну думай что ты меня макнул ) если тебе так легче .

Сначала подумай сто раз, прежде, чем что-то сказать или написать.

А то, хотел ближнего макнуть, да сам обмочился :-)

 
TheXpert:
Это как? фонит что ли? ) ну покажи свой православный для тех же задач )

Сильно удивлён, что ТЕБЕ буду ЭТО объяснять. Это же базовые знания!

1. Так синхронизацию никто не делает. Критическая секция должна быть в классе-обёртке.
2. К ней нужен шаблон класса-замка. Если у тебя один класс объекта синхронизации, можно без шаблона. У меня их 4 для всех случаев жизни.

3. Сам синглетон это отдельная сущность. Должен быть шаблоном класса. Любой свой класс можно сделать синглетоном путём наследования от своего класса.

4. Класс критической секции для синглетона должен быть с глобальным инстансом. Одним на все твои синглетоны.

5. Ну, и для законченных перфекционистов :-)) Нужна функция чистки памяти от глобального инстанса класса критической секции перед завершением приложения или библиотеки. Собственно, это один из методов этого глобального инстанса.

6. Надеюсь, не надо говорить, что все указатели в синглетонах должны быть умными?

 
Даа... Простота рулит.
 

Это просто. Один раз надо сделать. Больше никогда никаких ошибок при исполнении не будет.

Потом делаю макрос так:

#define UTILS_THREAD(dwClassId) Utils::PSingleton<Utils::THREAD::_SIMPLE<dwClassId> >::GetInstance()

Обращение к методам:

UTILS_THREAD(0)->Init();
UTILS_THREAD(0)->Start();
Проще некуда.

Вот вариант с наследованием.

class g_CInitCS : public Utils::Sync::CriticalSection, UTILS_BASE
 {
  public: UTILS_PSINGLETON(g_CInitCS);
 };
// Класс g_CInitCS теперь является синглетоном со всеми методами класса Utils::Sync::CriticalSection.
 
Zhunko:

Пользуйтесь кнопкой SRC, чтобы код вставлять.

1. Между запусками эксперта требуется держать данные в классе MqlDll? Эксперт, может даже несколько экспертов, могут быть загружены/выгружены несколько раз и, если все потоки завершились, то библиотека выгрузится. Данные класса будут потеряны.

2. Если класс объявлен глобально и ссылается на другие глобальные объекты, если они существуют (?), то синглетон необходим. Иначе, возможны непрогнозируемые разнообразные ошибки. Причём, на каждый раз новые :-)

3. Пользуетесь всеми параметрами функции инициализации? Т.е. у Вас несколько вызовов с разными значениями?

4. По поводу коммутации. Подойдёт ассоциативный контейнер (std::map). Складываейте в него пары идентификатор-указатель. Снаружи оперируйте идентификатором. Андрей выше написал, как делать.

Логику работы кнопки SRC я не особо понял. Нажимаю - у меня весь набранный текст сообщения полностью пропадает, появляется новое поле ввода. Отжимаю - опять вижу своё сообщение. Поэтому сделал предыдущее сообщение таким..

Теперь по пунктам.

1. Между запусками эксперта ничего в самом классе не сохраняется. Запустился эксперт - для него создался объект класса, инициализировался, создал подключение к БД и держит его. Все данные - только на текущий сеанс работы, типа строки с последней ошибкой. Все данные, которые надо сохранять после запуска, хранятся в БД (для чего и нужно подключение). Данные, которые надо сохранять между запусками одного робота - хранятся в глобальных переменных. Так что потеряно ничего не будет.

2. С синглетонами не работал до этого. Класс MqlDll объявлен глобально. Кроме него, планируется сделать коасс со статическим контейнером (std::map, или, вернее, "QMap<Key, T>") для хранения указателей. До этого я думал просто сделать статический класс и в нём блокировку для многопоточно-безопасного обращения к контейнеру. Надо будет погуглить синглетон и посмотреть, что это такое, зачем он нужен и как его приделать к своему классу.

3. Функция инициализации одна. Параметры, конечно, используются все. Это обычные параметры подключения к БД - хост, порт, логин, пароль, название БД. Их надо передавать все каждый раз. Кроме этого ещё название подключения - оно нужно для классов Qt.

4. Насчёт контейнера с парами "идентификатор-указатель" - это да, отличное решение сложной проблемы, которое я как раз и планирую приделать. Я использую Qt. В нём можно приделать и std::map, но там были какие-то сложности с этим, и потому в Qt куча своих классов-контейнеров. В частности, есть "QMap<Key, T>".

 
CanSee:

0. Логику работы кнопки SRC я не особо понял. Нажимаю - у меня весь набранный текст сообщения полностью пропадает, появляется новое поле ввода. Отжимаю - опять вижу своё сообщение. Поэтому сделал предыдущее сообщение таким..

Теперь по пунктам.

1. Между запусками эксперта ничего в самом классе не сохраняется. Запустился эксперт - для него создался объект класса, инициализировался, создал подключение к БД и держит его. Все данные - только на текущий сеанс работы, типа строки с последней ошибкой. Все данные, которые надо сохранять после запуска, хранятся в БД (для чего и нужно подключение). Данные, которые надо сохранять между запусками одного робота - хранятся в глобальных переменных. Так что потеряно ничего не будет.

2. С синглетонами не работал до этого. Класс MqlDll объявлен глобально. Кроме него, планируется сделать класс со статическим контейнером (std::map, или, вернее, "QMap<Key, T>") для хранения указателей. До этого я думал просто сделать статический класс и в нём блокировку для многопоточно-безопасного обращения к контейнеру. Надо будет погуглить синглетон и посмотреть, что это такое, зачем он нужен и как его приделать к своему классу.

3. Функция инициализации одна. Параметры, конечно, используются все. Это обычные параметры подключения к БД - хост, порт, логин, пароль, название БД. Их надо передавать все каждый раз. Кроме этого ещё название подключения - оно нужно для классов Qt.

4. Насчёт контейнера с парами "идентификатор-указатель" - это да, отличное решение сложной проблемы, которое я как раз и планирую приделать. Я использую Qt. В нём можно приделать и std::map, но там были какие-то сложности с этим, и потому в Qt куча своих классов-контейнеров. В частности, есть "QMap<Key, T>".

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

Без контейнера не обойтись. Синхронизация при обращении к нему обязательна. Кстати, в этом случае можно работать даже с 64-разрядной базой. Ведь, указатели можно в контейнере хранить уже какие угодно.

4. Да, это тот же std::map. Аналог.

 
Zhunko:

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

Без контейнера не обойтись. Синхронизация при обращении к нему обязательна. Кстати, в этом случае можно работать даже с 64-разрядной базой. Ведь, указатели можно в контейнере хранить уже какие угодно.

4. Да, это тот же std::map. Аналог.

Спасибо за консультации :)

Я планирую сделать так. Класс MqlDll, обеспечивающий работу с БД, оставить без изменений. В дополнение к нему сделать статический класс, который будет обеспечивать хранение указателей в контейнере, получение указателя по идентификатору, потокобезопасное создание класса и помещение его в контейнер с получением идентификатора и т.п.

Насколько я понимаю, раз статический класс будет один - то порядок инициализации особого значения не имеет, а значит, можно без синглетона?

 
CanSee:

Спасибо за консультации :)

Я планирую сделать так. Класс MqlDll, обеспечивающий работу с БД, оставить без изменений. В дополнение к нему сделать статический класс, который будет обеспечивать хранение указателей в контейнере, получение указателя по идентификатору, потокобезопасное создание класса и помещение его в контейнер с получением идентификатора и т.п.

Насколько я понимаю, раз статический класс будет один - то порядок инициализации особого значения не имеет, а значит, можно без синглетона?

Если один, то можно без синглетона. Но там же есть ещё глобальный MqlDll. Лучше тогда сделать один. Объединить.
Причина обращения: