Два экземпляра класса и одна dll.

Тема в разделе "WASM.WIN32", создана пользователем cresta, 27 сен 2007.

  1. cresta

    cresta Active Member

    Публикаций:
    0
    Регистрация:
    13 июн 2004
    Сообщения:
    2.257
    Создаю эти два экземпляра в main, и храню ссылки на них в глобальных переменных.

    Код (Text):
    1. void main(void){
    2.  
    3.     hLibMySql = LoadLibrary("C:\\WebServers\\usr\\local\\php5\\libmysql.dll");
    4.     dBase = new MySQL(hLibMySql, "127.0.0.1", "u", "p", "b");
    5.     dBase->query( "SET NAMES 'utf8'" );
    6.  
    7.     dBase2 = new MySQL(hLibMySql, "127.0.0.1", "u", "p", "b");
    8.     dBase2->query( "SET NAMES 'utf8'" );
    9. }
    использую примерно так:

    Код (Text):
    1. void loadAccountList(){
    2.     LVITEM      lvi;
    3.     char        **result, **result2;
    4.  
    5.     SendMessage (hListAccount, LVM_DELETEALLITEMS, 0, 0);
    6.  
    7.     lvi.mask = LVIF_TEXT;
    8.     lvi.iItem = 0;
    9.     if ( NULL != dBase->queryCon("SELECT DISTINCT category FROM `",
    10.                     depAccountTbl, "` WHERE state = 0 ORDER BY category", 0)){
    11.         while ( result=dBase->fetch_row() ){
    12.             lvi.cchTextMax = 100;
    13.             lvi.iSubItem = 0;
    14.             lvi.pszText = res[0];
    15.             ListView_InsertItem(hListAccount, &lvi);
    16.             lvi.iItem ++;
    17.             dBase2->query("SELECT 1+1");
    18.             PRNT(S,result[0]);
    19.         }
    20.     }
    21. }
    Цикл while ( result=dBase->fetch_row() ) превращается в бесконечный.
    Результат dBase2->query накладывается на результат dBase->queryCon и зацикливает программу.
    Вместо ожидаемых данных PRNT(S,result[0]); выводит "2".
    Класс базируется на функциях внешней dll. Если используется один экземпляр класса - все работает безупречно. Но хотелось бы чтобы была возможность создания нескольких экземпляров, что упростило бы код.
    Подгрузить два раза либу не получается, LoadLibrary возвращает HMODULE уже загруженого первого экземпляра. Поэтому оба экземпляра класса используют одни и те же области данных в dll, создавая друг другу помеху.
    Как обойти это? Копировать dll под другим именем и подгружать "замаскированную" dll?
    Или есть что-то другое?
     
  2. IceStudent

    IceStudent Active Member

    Публикаций:
    0
    Регистрация:
    2 окт 2003
    Сообщения:
    4.300
    Адрес:
    Ukraine
    Вообще-то, mysql потокобезопасен. Но у тебя налицо ошибка дизайна: два объекта БД в одном потоке вместо одного + двух объектов запросов.

    Плюс, почитай доку:
     
  3. cresta

    cresta Active Member

    Публикаций:
    0
    Регистрация:
    13 июн 2004
    Сообщения:
    2.257
    Мой класс не противоречит доке:

    Two threads can't send a query to the MySQL server at the same time on the same connection. In particular, you have to ensure that between calls to mysql_query() and mysql_store_result(), no other thread is using the same connection.
    ...
    you must have a mutex lock around your pair of mysql_query() and mysql_store_result() calls


    Класс сделан так, что в методе dBase->query() вызывается mysql_query() и тут же следом mysql_store_result(). Именно результат mysql_store_result() является возвращаемым значением метода dBase->query().
    Т.е. пока выполняется метод, между этими двумя функциями никто не вклинивается.

    Кроме того, оба экземпляра класса устанавливают свой собственный connect к серверу. Это происходит в конструкторе. Поэтому речи об использовании одного и того же коннекта не идёт.
    И одновременно запросы не производятся, т.к. поток один.

    Тут всё-таки видимо происходит следующее:
    mysql_store_result() сохраняет результаты запроса от первого класса где-то внутри памяти, владельцем которой является dll, и возвращает указатель на него.
    Второй класс после этого выполняет свой запрос, и ему тоже надо где-то хранить результаты запроса. И он сохраняет их поверх результатов первого запроса, ибо больше негде хранить. Вот и получается, что при попытке fetch_row() для первого запроса данных от первого запроса уже нет, на их месте данные от второго запроса, которые мне и возвращаются.
    Единственно, что я могу придумать как обойти этот момент - разделить память двух экземпляров класса, т.е. иметь две маппированые в процесс dll'ки, каждая со своими данными.
    Но LoadLibrary отказывается грузить два экземпляра :dntknw:

    Есть конечно хакерские способы, типа вручную загрузить dll, настроить все релоки и т.п. но это слишком... Мне проще обойтись одним экземпляром класса, чем затевать такие манипуляции.
     
  4. cresta

    cresta Active Member

    Публикаций:
    0
    Регистрация:
    13 июн 2004
    Сообщения:
    2.257
    Код (Text):
    1.     HMODULE hLibMySql = LoadLibrary("C:\\WebServers\\usr\\local\\php5\\libmysql.dll");
    2.     HMODULE hLibMySql2 = LoadLibrary("C:\\WebServers\\usr\\local\\php5\\libmysql2.dll");
    3.    
    4.     PRNT(X, hLibMySql);    //первая dll маппируется по адресу 0x1000000
    5.     PRNT(X, hLibMySql2);   //вторая dll маппируется по адресу 0x0890000
    6.    
    7.     dBase = new MySQL(hLibMySql, "127.0.0.1", "u", "p", "b");
    8.     dBase2 = new MySQL(hLibMySql2, "127.0.0.1", "u", "p", "b");
    9.  
    10.     if ( NULL != dBase->queryCon("SELECT DISTINCT category FROM `",
    11.                     depAccountTbl, "` WHERE state = 0 ORDER BY category", 0)){
    12.         while ( result=dBase->fetch_row() ){
    13.             //insert category
    14.             lvi.cchTextMax = 100;
    15.             lvi.iSubItem = 0;
    16.             lvi.pszText = result[0];
    17.             ListView_InsertItem(hListAccount, &lvi);
    18.             lvi.iItem ++;
    19.             //insert products
    20.             dBase2->queryCon("SELECT code, product, outprice, count FROM `",
    21.                             depAccountTbl, "` WHERE state = 0 AND category = '",
    22.                             result[0], "' ORDER BY product", 0);
    23.             while (result2=dBase2->fetch_row()){
    24.                 lvi.iSubItem = 0;
    25.                 lvi.pszText = result2[0];
    26.                 ListView_InsertItem(hListAccount, &lvi); //code
    27.                 ListView_SetItemText(hListAccount, lvi.iItem, 1, result2[1]); //product
    28.                 ListView_SetItemText(hListAccount, lvi.iItem, 2, result2[2]); //outprice
    29.                 ListView_SetItemText(hListAccount, lvi.iItem, 3, result2[3]); //count
    30.                 lvi.iItem ++;
    31.             }
    32.             //insert empty row as separator
    33.             lvi.pszText = 0;
    34.             ListView_InsertItem(hListAccount, &lvi);
    35.             lvi.iItem ++;
    36.         }
    37.     }
    Вот так проблем никаких, второй запрос использует результаты первого запроса, и никто никому не мешает.
    Но нужно иметь копию файла libmysql.dll как libmysql2.dll, чтобы можно было её подгрузить.
    Хотелось бы обойтись без нескольких одинаковых файлов dll, различающихся только названием.
    Если вдруг понадобится штук пять экземпляров класа, что тогда? Создавать кучу копий либы на винте?
    :dntknw:
     
  5. IceStudent

    IceStudent Active Member

    Публикаций:
    0
    Регистрация:
    2 окт 2003
    Сообщения:
    4.300
    Адрес:
    Ukraine
    Да нет, там всё ок. Ищи всё же ошибку у себя:
    Код (Text):
    1.     while(MYSQL_ROW row = mysql_fetch_row(res1)){
    2.         unsigned long *lengths = mysql_fetch_lengths(res1);
    3.  
    4.         for(unsigned i = 0; i < num_fields; i++)
    5.             printf("%.*s\t", lengths[i], row[i] ? row[i] : "<null>");
    6.  
    7.         printf("\n");
    8.  
    9.         int re2 = mysql_real_query(db, q2, sizeof(q2));
    10.         if(re1){
    11.             const char* err = mysql_error(db);
    12.             printf("\n\terror occured: %s\n", err);
    13.             break;
    14.         }
    15.  
    16.         MYSQL_RES* res2 = mysql_store_result(db);
    17.         unsigned n = mysql_num_fields(res2);
    18.         MYSQL_ROW r2 = mysql_fetch_row(res2);
    19.         const char* x = r2[0];
    20.         mysql_free_result(res2);
    21.     }
     
  6. cresta

    cresta Active Member

    Публикаций:
    0
    Регистрация:
    13 июн 2004
    Сообщения:
    2.257
    Каким образом результаты одного запроса из одного экземпляра класса попадают в результаты запроса другого класса - не понятно. Никаких глобальных переменных нет, классы изолированы друг от друга, а вместе с ними изолированы и результаты запросов.
    Никаких ошибок в конструкции класса я не вижу (по крайней мере тех, которые могут привести к наложению приватных членов друг на друга).
    Прицепил бы реализацию класса (может кто и увидел бы ошибку) только кнопка прикрепления аттача отсутствует :dntknw:

    Черт, кнопка появляется при редактировании сообщения.
     
  7. cresta

    cresta Active Member

    Публикаций:
    0
    Регистрация:
    13 июн 2004
    Сообщения:
    2.257
    В общем после переделки программы с использования классов на обычное процедурное программирование пришёл к выводу (на 99%) что ошибка внутри dll. Проблемы с распределением памяти между разными коннектами. Если одновременно существуют несколько коннектов, то рано или поздно mysql_store_result возвратит указатель, уже возвращенный другому коннекту. Т.е. два коннекта имеют один и тот же указатель MYSQL_RES. А т.к. после запросов необходимо делать mysql_free_result, то легальное освобождение ресурсов одного коннекта приводит к ошибочному освобождению ресурсов другого коннекта (ибо указатель один и тот же) со всеми вытекающими последствиями. На простых запросах (не ресурсоемких) это происходит редко, на запросах, потребляющих более или менее значительный объём памяти - практически наверняка вылет программы.
    Никаких реальных способов противостоять этому, кроме загрузки нового экземпляра dll для каждого нового коннекта, не нашёл. Можно ещё создавать коннекты локально, в каждой процедуре, закрывая их на выходе, но это только смягчает проблему, не устраняя её полностью.
     
  8. IceStudent

    IceStudent Active Member

    Публикаций:
    0
    Регистрация:
    2 окт 2003
    Сообщения:
    4.300
    Адрес:
    Ukraine
    cresta
    Хм. А тебе обязательно создавать два коннекта для одной программы?
     
  9. cresta

    cresta Active Member

    Публикаций:
    0
    Регистрация:
    13 июн 2004
    Сообщения:
    2.257
    IceStudent

    Можно обойтись и одним коннектом, но это не очень удобно. Нужно где-то хранить данные от первого запроса, чтобы на их основе составлять второй запрос. Если бы два коннекта сосуществовали мирно, код значительно бы упростился. А так приходится либо забивать результаты в листвью, а потом доставать их оттуда, либо городить массивы в памяти, в которых сохранять данные. Но в листвью не всегда уместно отображать промежуточные данные, а с массивом в памяти приходится городить процедуры по его составлению и последующему разгребанию (аналоги store_result и fetch_row).

    Пока делаю как проще - каждому конекту свою dll со своей памятью :)
     
  10. IceStudent

    IceStudent Active Member

    Публикаций:
    0
    Регистрация:
    2 окт 2003
    Сообщения:
    4.300
    Адрес:
    Ukraine
    Так чем MYSQL_RES для хранения результатов запроса не подходит? Это в SQLite он теряется (проверял на STMT) при параллельном запросе, а в MySQL не должен.
     
  11. cresta

    cresta Active Member

    Публикаций:
    0
    Регистрация:
    13 июн 2004
    Сообщения:
    2.257
    Так чем MYSQL_RES для хранения результатов запроса не подходит?

    Именно для того, чтобы хранить в MYSQL_RES данные, мне и нужен был второй коннект.

    Но в MySql такая же беда как и в SQLite. Если есть два параллельно выполняющихся запроса, то MYSQL_RES* одного из запросов может совпасть с MYSQL_RES* другого запроса. И когда происходит освобождение ресурса (на который указывает MYSQL_RES*) для одного запроса, получается что и для второго запроса тоже результат обнулился. Но второй запрос об этом не знает, и в свою очередь пытается освободить уже освобожденную первым запросом память. В результате прога падает. Именно на mysql_free_result.

    Код (Text):
    1. if (result1 = mysql_store_result(conn1)){
    2.     ...
    3.     ...
    4.     ...
    5.     if (result2 = mysql_store_result(conn2)){
    6.         PRNT ("%08X == %08X", result1, result2);
    7.         ....
    8.         ....
    9.         mysql_free_result(result2);
    10.     }
    11.     // тут происходит крах, т.к. result1 == result2, и кусок памяти, адресуемый result1,
    12.     // уже был освобожден вложенным запросом
    13.     mysql_free_result(result1);
    14. }
    Т.е. я реально вижу, как PRNT ("%08X -- %08X", result1, result2) выводит в DbgView строчку
    0087F580 == 0087F580. Не всегда, но бывает.

    Если я закомментирую mysql_free_result, то прога перестает падать и работает. Но при этом количество потребляемой памяти нарастает с каждым запросом. И в конце концов наступает момент, когда прогу приходится прибить самому, чтобы разгрузить память.

    Если завершить первый коннект до открытия второго, то всё ОК. Но работать одновременно они не могут.

    Можно воспользоваться mysql_fetch_row и mysql_fetch_lengths, чтобы составить свой собственный массив по аналогии с MYSQL_RES, но проще грузить вторую dll.
     
  12. IceStudent

    IceStudent Active Member

    Публикаций:
    0
    Регистрация:
    2 окт 2003
    Сообщения:
    4.300
    Адрес:
    Ukraine
    Хм, всё же бага? Отпиши в MySQL AB. Вообще, надо будет глянуть в сорцы библиотеки, как она умудряется косячить, но это позже.