Создаю эти два экземпляра в main, и храню ссылки на них в глобальных переменных. Код (Text): void main(void){ hLibMySql = LoadLibrary("C:\\WebServers\\usr\\local\\php5\\libmysql.dll"); dBase = new MySQL(hLibMySql, "127.0.0.1", "u", "p", "b"); dBase->query( "SET NAMES 'utf8'" ); dBase2 = new MySQL(hLibMySql, "127.0.0.1", "u", "p", "b"); dBase2->query( "SET NAMES 'utf8'" ); } использую примерно так: Код (Text): void loadAccountList(){ LVITEM lvi; char **result, **result2; SendMessage (hListAccount, LVM_DELETEALLITEMS, 0, 0); lvi.mask = LVIF_TEXT; lvi.iItem = 0; if ( NULL != dBase->queryCon("SELECT DISTINCT category FROM `", depAccountTbl, "` WHERE state = 0 ORDER BY category", 0)){ while ( result=dBase->fetch_row() ){ lvi.cchTextMax = 100; lvi.iSubItem = 0; lvi.pszText = res[0]; ListView_InsertItem(hListAccount, &lvi); lvi.iItem ++; dBase2->query("SELECT 1+1"); PRNT(S,result[0]); } } } Цикл while ( result=dBase->fetch_row() ) превращается в бесконечный. Результат dBase2->query накладывается на результат dBase->queryCon и зацикливает программу. Вместо ожидаемых данных PRNT(S,result[0]); выводит "2". Класс базируется на функциях внешней dll. Если используется один экземпляр класса - все работает безупречно. Но хотелось бы чтобы была возможность создания нескольких экземпляров, что упростило бы код. Подгрузить два раза либу не получается, LoadLibrary возвращает HMODULE уже загруженого первого экземпляра. Поэтому оба экземпляра класса используют одни и те же области данных в dll, создавая друг другу помеху. Как обойти это? Копировать dll под другим именем и подгружать "замаскированную" dll? Или есть что-то другое?
Вообще-то, mysql потокобезопасен. Но у тебя налицо ошибка дизайна: два объекта БД в одном потоке вместо одного + двух объектов запросов. Плюс, почитай доку:
Мой класс не противоречит доке: 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 отказывается грузить два экземпляра Есть конечно хакерские способы, типа вручную загрузить dll, настроить все релоки и т.п. но это слишком... Мне проще обойтись одним экземпляром класса, чем затевать такие манипуляции.
Код (Text): HMODULE hLibMySql = LoadLibrary("C:\\WebServers\\usr\\local\\php5\\libmysql.dll"); HMODULE hLibMySql2 = LoadLibrary("C:\\WebServers\\usr\\local\\php5\\libmysql2.dll"); PRNT(X, hLibMySql); //первая dll маппируется по адресу 0x1000000 PRNT(X, hLibMySql2); //вторая dll маппируется по адресу 0x0890000 dBase = new MySQL(hLibMySql, "127.0.0.1", "u", "p", "b"); dBase2 = new MySQL(hLibMySql2, "127.0.0.1", "u", "p", "b"); if ( NULL != dBase->queryCon("SELECT DISTINCT category FROM `", depAccountTbl, "` WHERE state = 0 ORDER BY category", 0)){ while ( result=dBase->fetch_row() ){ //insert category lvi.cchTextMax = 100; lvi.iSubItem = 0; lvi.pszText = result[0]; ListView_InsertItem(hListAccount, &lvi); lvi.iItem ++; //insert products dBase2->queryCon("SELECT code, product, outprice, count FROM `", depAccountTbl, "` WHERE state = 0 AND category = '", result[0], "' ORDER BY product", 0); while (result2=dBase2->fetch_row()){ lvi.iSubItem = 0; lvi.pszText = result2[0]; ListView_InsertItem(hListAccount, &lvi); //code ListView_SetItemText(hListAccount, lvi.iItem, 1, result2[1]); //product ListView_SetItemText(hListAccount, lvi.iItem, 2, result2[2]); //outprice ListView_SetItemText(hListAccount, lvi.iItem, 3, result2[3]); //count lvi.iItem ++; } //insert empty row as separator lvi.pszText = 0; ListView_InsertItem(hListAccount, &lvi); lvi.iItem ++; } } Вот так проблем никаких, второй запрос использует результаты первого запроса, и никто никому не мешает. Но нужно иметь копию файла libmysql.dll как libmysql2.dll, чтобы можно было её подгрузить. Хотелось бы обойтись без нескольких одинаковых файлов dll, различающихся только названием. Если вдруг понадобится штук пять экземпляров класа, что тогда? Создавать кучу копий либы на винте?
Да нет, там всё ок. Ищи всё же ошибку у себя: Код (Text): while(MYSQL_ROW row = mysql_fetch_row(res1)){ unsigned long *lengths = mysql_fetch_lengths(res1); for(unsigned i = 0; i < num_fields; i++) printf("%.*s\t", lengths[i], row[i] ? row[i] : "<null>"); printf("\n"); int re2 = mysql_real_query(db, q2, sizeof(q2)); if(re1){ const char* err = mysql_error(db); printf("\n\terror occured: %s\n", err); break; } MYSQL_RES* res2 = mysql_store_result(db); unsigned n = mysql_num_fields(res2); MYSQL_ROW r2 = mysql_fetch_row(res2); const char* x = r2[0]; mysql_free_result(res2); }
Каким образом результаты одного запроса из одного экземпляра класса попадают в результаты запроса другого класса - не понятно. Никаких глобальных переменных нет, классы изолированы друг от друга, а вместе с ними изолированы и результаты запросов. Никаких ошибок в конструкции класса я не вижу (по крайней мере тех, которые могут привести к наложению приватных членов друг на друга). Прицепил бы реализацию класса (может кто и увидел бы ошибку) только кнопка прикрепления аттача отсутствует Черт, кнопка появляется при редактировании сообщения.
В общем после переделки программы с использования классов на обычное процедурное программирование пришёл к выводу (на 99%) что ошибка внутри dll. Проблемы с распределением памяти между разными коннектами. Если одновременно существуют несколько коннектов, то рано или поздно mysql_store_result возвратит указатель, уже возвращенный другому коннекту. Т.е. два коннекта имеют один и тот же указатель MYSQL_RES. А т.к. после запросов необходимо делать mysql_free_result, то легальное освобождение ресурсов одного коннекта приводит к ошибочному освобождению ресурсов другого коннекта (ибо указатель один и тот же) со всеми вытекающими последствиями. На простых запросах (не ресурсоемких) это происходит редко, на запросах, потребляющих более или менее значительный объём памяти - практически наверняка вылет программы. Никаких реальных способов противостоять этому, кроме загрузки нового экземпляра dll для каждого нового коннекта, не нашёл. Можно ещё создавать коннекты локально, в каждой процедуре, закрывая их на выходе, но это только смягчает проблему, не устраняя её полностью.
IceStudent Можно обойтись и одним коннектом, но это не очень удобно. Нужно где-то хранить данные от первого запроса, чтобы на их основе составлять второй запрос. Если бы два коннекта сосуществовали мирно, код значительно бы упростился. А так приходится либо забивать результаты в листвью, а потом доставать их оттуда, либо городить массивы в памяти, в которых сохранять данные. Но в листвью не всегда уместно отображать промежуточные данные, а с массивом в памяти приходится городить процедуры по его составлению и последующему разгребанию (аналоги store_result и fetch_row). Пока делаю как проще - каждому конекту свою dll со своей памятью
Так чем MYSQL_RES для хранения результатов запроса не подходит? Это в SQLite он теряется (проверял на STMT) при параллельном запросе, а в MySQL не должен.
Так чем MYSQL_RES для хранения результатов запроса не подходит? Именно для того, чтобы хранить в MYSQL_RES данные, мне и нужен был второй коннект. Но в MySql такая же беда как и в SQLite. Если есть два параллельно выполняющихся запроса, то MYSQL_RES* одного из запросов может совпасть с MYSQL_RES* другого запроса. И когда происходит освобождение ресурса (на который указывает MYSQL_RES*) для одного запроса, получается что и для второго запроса тоже результат обнулился. Но второй запрос об этом не знает, и в свою очередь пытается освободить уже освобожденную первым запросом память. В результате прога падает. Именно на mysql_free_result. Код (Text): if (result1 = mysql_store_result(conn1)){ ... ... ... if (result2 = mysql_store_result(conn2)){ PRNT ("%08X == %08X", result1, result2); .... .... mysql_free_result(result2); } // тут происходит крах, т.к. result1 == result2, и кусок памяти, адресуемый result1, // уже был освобожден вложенным запросом mysql_free_result(result1); } Т.е. я реально вижу, как PRNT ("%08X -- %08X", result1, result2) выводит в DbgView строчку 0087F580 == 0087F580. Не всегда, но бывает. Если я закомментирую mysql_free_result, то прога перестает падать и работает. Но при этом количество потребляемой памяти нарастает с каждым запросом. И в конце концов наступает момент, когда прогу приходится прибить самому, чтобы разгрузить память. Если завершить первый коннект до открытия второго, то всё ОК. Но работать одновременно они не могут. Можно воспользоваться mysql_fetch_row и mysql_fetch_lengths, чтобы составить свой собственный массив по аналогии с MYSQL_RES, но проще грузить вторую dll.
Хм, всё же бага? Отпиши в MySQL AB. Вообще, надо будет глянуть в сорцы библиотеки, как она умудряется косячить, но это позже.