Форум программистов «Весельчак У»
  *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

  • Рекомендуем проверить настройки временной зоны в вашем профиле (страница "Внешний вид форума", пункт "Часовой пояс:").
  • У нас больше нет рассылок. Если вам приходят письма от наших бывших рассылок mail.ru и subscribe.ru, то знайте, что это не мы рассылаем.
   Начало  
Наши сайты
Помощь Поиск Календарь Почта Войти Регистрация  
 
Страниц: [1]   Вниз
  Печать  
Автор Тема: static-переменные, используемые в загруженной длл  (Прочитано 17016 раз)
0 Пользователей и 1 Гость смотрят эту тему.
Malaja
Команда клуба

de
Offline Offline
Пол: Женский

« : 25-03-2010 17:51 » 

Дано:
а) основное приложение (MainApp)  и длл (DllApp), которая подгружается в основное приложение динамически (процесс один на всех)
2) отдельная либа (UsedClasses), в которой находится класс (mainContainer), используемый обоими приложениями из а). При этом класс изобилует статическими объектами (и это уже не изменить - с этим надо жить)
3) если в основном приложении MainApp произошли изменения объекта типа mainContainer, то этот обновленный объект должен быть передан длл-лине, т.е. ее объект должен быть изменен ( т.о. у длл-лины будет копия объекта из MainApp). [ ну или наоборот - изменения в длл-лине д.б. переданы основному приложению]

Т.е. имеем в MainApp:
Код:
// load dll
HINSTANCE hTestDll = NULL;

// get handle of the dll and check it
hTestDll = LoadLibrary(L"DllApp.dll");

typedef  void (__cdecl  TESTFUNC_2)(MainContainer*&, int);
TESTFUNC_2* pFuncStart2 = NULL;

// get the function handle for testFunc()
pFuncStart2 = (TESTFUNC_2*)GetProcAddress(hTestDll, "getNewContainerObject");

MainContainer* curContainer = NULL;
if (pFuncStart2)
        (*pFuncStart2)(curContainer, 5);

В длл-ли (m_objContainer  - переменная этого класса типа MainContainer):
Код:
__declspec( dllexport ) void  _cdecl getNewContainerObject(MainContainer*& curContainer, int nObjCount)
{
DllTestClass objClsWithCont;
objClsWithCont.testFunc_cls("Container from testFunc_2()", nObjCount, curContainer);
}


void DllTestClass::testFunc_cls(char* pStr, int nObjCount, MainContainer*& curContainer)
{

m_objContainer = new MainContainer(nObjCount, pStr);

char sName[128];
sprintf( sName, "main_app_container_%d", 0 );
m_objContainer->createCurrentElement( 0, 1, sName );

curContainer = new MainContainer(*m_objContainer);
}

В результате для объекта m_objContainer  создаем m_pRoot_ptr_array[0].
Результат копируем в переменную curContainer.

Сам класс MainContainer содержит в себе, кроме всего прочего, 2 статические переменные:
Код:
class MainContainer
{
private:

static MainContainer** m_pRoot_ptr_array;
static int m_nCounter;
}


В результате в MainApp я ожидаю увидеть в объекте curContainer все то, что было туда скопировано.
Но не тут-то было... Обе статические переменные пусты...
Т.е. m_nCounter = 0 и m_pRoot_ptr_array = NULL... Все нестаттические переменные здоровы.
Т.о. у меня в руках есть указатель на объект, но полноценно работать с ним я не могу...

Итого вопрос:
Что тут неправильно и как это исправить?

Записан

холоднокровней, Маня, Ви не на работе
---------------------------------------
четкое определение сущности бытия:
- А мы в прошлом или в будущем?- спросила Алиса.
- Мы в жопе, - ответил кролик.
- А "жопа" - это настоящее? - спросила Алиса.
- А "жопа" - это у нас символ вечности.
Вад
Модератор

ru
Offline Offline
Пол: Мужской

« Ответ #1 : 25-03-2010 20:08 » 

Могу ошибаться, но...
Из соображений здравого смысла, думается, что экземпляров статических объектов MainContainer будет два: процесс вызовет свой код, а dll - свой: процесс ничего не знает о DLL, за исключением экспортируемого интерфейса, а dll тем более не знает ничего про то, какими типами оперирует родительский процесс. Поэтому общаться через такие типы вряд ли получится.

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

На практике не пробовал, так что это только общие соображения. Улыбаюсь
Записан
Dimka
Деятель
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #2 : 25-03-2010 20:13 » 

Сразу можно сказать, что создание/копирование экземпляра к проблеме не имеет ни малейшего отношения.

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

Собственно, ты не показала ту часть кода, где бы из библиотеки экспортировались данные, хранящиеся в классе (по сути, глобальные переменные для библиотеки). Я вижу только экспорт функции, которая умеет создавать экземпляры, но статические члены никак не связаны с экземплярами.
« Последнее редактирование: 25-03-2010 20:37 от Dimka » Записан

Программировать - значит понимать (К. Нюгард)
Невывернутое лучше, чем вправленное (М. Аврелий)
Многие готовы скорее умереть, чем подумать (Б. Рассел)
Malaja
Команда клуба

de
Offline Offline
Пол: Женский

« Ответ #3 : 25-03-2010 21:49 » 

Вад,

это сделать не смогу.
Суть задачи заключается в том, что
1) есть несколько длл-лей, отличающихся между собой только одим файлом .lib, прилинкованным статически. Соответственно, каждая из них имеет свою переменную типа mainContainer
2) А наш ехе-шник MainApp должен в соответствии с конфигурацией подгружать при старте минимум одну, а максимум, все длл-ли из 1) и управлять данными, находящимися в них.
Т.е. у него теоретически должны быть указатели на соответствующие объекты из каждой длл-ли.

Дим,
правильно - никто ни о ком ничего не знает, но: раз они находятся в одном адресном пространстве и наш MainApp знает о существовании длл-лей, то при наличии у него указателя на объект, находящийся в длл-ле, он должен иметь доступ к памяти.
Код выложу завтра.
Записан

холоднокровней, Маня, Ви не на работе
---------------------------------------
четкое определение сущности бытия:
- А мы в прошлом или в будущем?- спросила Алиса.
- Мы в жопе, - ответил кролик.
- А "жопа" - это настоящее? - спросила Алиса.
- А "жопа" - это у нас символ вечности.
Антон (LogRus)
Глобальный модератор

ru
Offline Offline
Пол: Мужской
Внимание! Люблю сахар в кубиках!


WWW
« Ответ #4 : 26-03-2010 04:21 » 

ээх, люблю не портируемый код Улыбаюсь страшно представить какие грабли будут на линуксе Улыбаюсь
подожду кода
Записан

Странно всё это....
Dimka
Деятель
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #5 : 26-03-2010 07:00 » 

Цитата: Malaja
2) А наш ехе-шник MainApp должен в соответствии с конфигурацией подгружать при старте минимум одну, а максимум, все длл-ли из 1) и управлять данными, находящимися в них.
И одинаковый класс со статическими членами в каждой DLL? Ох, грехи наши тяжкие...

Тогда предлагаю завести структуру, внутри которой находятся указатели на статические члены класса. Поскольку загрузка DLL динамическая, эту структуру передавать между приложением и DLL посредством какой-нибудь функции - примерно тем способом, каким передаётся MainContainer, а внутри DLL указатели назначать на адреса статических членов. Таким образом на каждую DLL приложение получит отдельный экземпляр структуры, в котором будут находиться указатели на статические члены класса внутри каждого экземпляра DLL.
Записан

Программировать - значит понимать (К. Нюгард)
Невывернутое лучше, чем вправленное (М. Аврелий)
Многие готовы скорее умереть, чем подумать (Б. Рассел)
Dimka
Деятель
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #6 : 26-03-2010 17:03 » 

Собственно, вот:

1) Приложение, которое динамически загружает 2 одинаковые внутри библиотеки и обращается к статическим членам класса двумя способами (через экспорт данных и через экспорт функций).

Main.cpp
Код: (Text)
#include <iostream>
#include <windows.h>
#include "Interface.h"

using namespace std;

int main()
{
        HMODULE module1 = LoadLibrary("TestDll1.dll");
        HMODULE module2 = LoadLibrary("TestDll2.dll");
       
        cout << "First method" << endl;
        X_X *x1_x = *(X_X **)GetProcAddress(module1, "x_x");
        X_X *x2_x = *(X_X **)GetProcAddress(module2, "x_x");
        cout << *x1_x << endl;
        cout << *x2_x << endl;

        cout << "Second method" << endl;
        SetXMirror setXMirror1 = (SetXMirror)GetProcAddress(module1, "setXMirror");
        SetXMirror setXMirror2 = (SetXMirror)GetProcAddress(module2, "setXMirror");
        XMirror xMirror1, xMirror2;
        (*setXMirror1)(xMirror1);
        (*setXMirror2)(xMirror2);
        cout << *xMirror1.x << endl;
        cout << *xMirror2.x << endl;
       
        FreeLibrary(module1);
        FreeLibrary(module2);
        getchar();
        return 0;
}

Interface.h
Код: (Text)
#pragma once

// First method
typedef int X_X;

// Second method
struct XMirror
{
        int *x;
};
typedef void (*SetXMirror)(XMirror &mirror);

2) Библиотека, которая имеет статический класс

Main.cpp
Код: (Text)
#include <windows.h>
#include "Interface.h"

class X
{
public:
        static int x;
};

int X::x = 2;

// First method (see .def) ////////////////////////////////////////////////////
X_X &x_x = X::x;
///////////////////////////////////////////////////////////////////////////////

// Second method (see .def) ///////////////////////////////////////////////////
void setXMirror(XMirror &mirror)
{
        mirror.x = &X::x;
};
///////////////////////////////////////////////////////////////////////////////

BOOL WINAPI DllMain(HANDLE hinstDLL, DWORD dwReason, LPVOID lpvReserved)
{
        return TRUE;
}

DLL.def
Код:
LIBRARY	"TestDll2"

EXPORTS
x_x DATA
setXMirror
Экспорт осуществляется через def-файл, чтобы точно знать строковые идентификаторы экспортируемых элементов.

Вторая библиотека полностью повторяет первую, только в Main.cpp нужно задать другое значение статической переменной, а в DLL.def - другое название библиотеки (DLL-файла). В обоих случаях, например, поменять 2 на 1.

Первым способом заводятся глобальные переменные - ссылки (или указатели - не важно) на статические поля классов. Вторым способом определяется структура (см. Interface.h), в которой собираются эти указатели на статические поля классов, и саму структуру заполняет экспортируемая функция. Если статический полей много и несколько классов, а также несколько DLL с одинаковыми классами, второй пусть со структурами мне видится более предпочтительным, поскольку в приложении сохраняется структурированность данных, присущая библиотекам, и инкапсуляция полей в структурах делает программный интерфейс между приложением и библиотекой более устойчивым к изменениям.

Возможно получить доступ к целому классу через
Код: (Text)
__declspec(dllexport) class X { /* ... */ };
и вроде как при этом должны быть доступны статические поля этого класса. Но как это сделать при динамической загрузке библиотек через LoadLibrary и GetProcAddress, я не знаю; и такой способ не будет работать при наличии одинаковых классов в разных DLL - будет конфликт внутри приложения.
Записан

Программировать - значит понимать (К. Нюгард)
Невывернутое лучше, чем вправленное (М. Аврелий)
Многие готовы скорее умереть, чем подумать (Б. Рассел)
Антон (LogRus)
Глобальный модератор

ru
Offline Offline
Пол: Мужской
Внимание! Люблю сахар в кубиках!


WWW
« Ответ #7 : 29-03-2010 05:09 » 

я ничего криминального не заметил.

Возможно получить доступ к целому классу через
Код: (Text)
__declspec(dllexport) class X { /* ... */ };
и вроде как при этом должны быть доступны статические поля этого класса. Но как это сделать при динамической загрузке библиотек через LoadLibrary и GetProcAddress, я не знаю; и такой способ не будет работать при наличии одинаковых классов в разных DLL - будет конфликт внутри приложения.

Если у класса есть статик мемберы и у тебя есть непреодолимое желание работать с ними:
Код:
X::x = 1; // 1. так
...........
X * y = NULL;
y = GetX(); // 2. или так
.................
X y;
GetX(&x); // 3. или даже так
то статик разрушает идиллическую картину, т.к. формально член класса, а самом деле глобальная переменная (кстати можно получить указатели на все статики по отдельности и работать с ними соответствующим образом), как следствие изменение его значения отображается на все экземпляры
а способ номер 2 вообще не повлияет на его значение

я бы ввёл некоторый интерфейсный класс IForX, в библиотеках сделал бы реализацию которая работает уже с данными X
ну дальше всё по стандартной схеме, т.е. реализуем в библиотеке функцию GetIForX
Записан

Странно всё это....
Dimka
Деятель
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #8 : 29-03-2010 07:50 » 

Цитата: LogRus
я бы ввёл некоторый интерфейсный класс IForX, в библиотеках сделал бы реализацию которая работает уже с данными X
ну дальше всё по стандартной схеме, т.е. реализуем в библиотеке функцию GetIForX
Ну, собственно, я это и подразумевал. Только Malaja уже в нескольких темах повторяет, что есть статические члены, и это не изменить - вот этот момент немного непонятен. Видимо, есть повсеместное обращение к этим статическим членам, какие-то архитектурные соглашения. Самым малоболезненным тут будет преобразование класса в singleton - его можно передавать по указателю на предложенный интерфейс IForX между разными DLL и приложением, и при этом он сам решает проблему создания экземпляра, что сохраняет глобальный доступ к нему.
Записан

Программировать - значит понимать (К. Нюгард)
Невывернутое лучше, чем вправленное (М. Аврелий)
Многие готовы скорее умереть, чем подумать (Б. Рассел)
Антон (LogRus)
Глобальный модератор

ru
Offline Offline
Пол: Мужской
Внимание! Люблю сахар в кубиках!


WWW
« Ответ #9 : 29-03-2010 09:23 » 

Лично я не вижу помех для создания интерфейса
Записан

Странно всё это....
Malaja
Команда клуба

de
Offline Offline
Пол: Женский

« Ответ #10 : 29-03-2010 20:44 » 

ребята, извините, что пропала - гриппом накрыло...
во-первых, спасибо за советы!

Дим, ЛогРус,
1) слава богу, все это должно работать только в винде Ага
2) действительно, в этом классе есть целый набор статических переменных и методов. Менять этот класс я не имею права, т.к. он повсеместно используется.
3)  прошу прощения, но что-то я не поняла, как вы хотите организовать и в дальнейшем использовать интерфейс IForX. Я же экспортирую отдельно стоящие функции, а не функции класса... Короче, я явно мыслю в неверном направлении, поэтому вас не поняла.
4) я попробую (пока без интерфейса - через структуру) и напишу результаты.
Записан

холоднокровней, Маня, Ви не на работе
---------------------------------------
четкое определение сущности бытия:
- А мы в прошлом или в будущем?- спросила Алиса.
- Мы в жопе, - ответил кролик.
- А "жопа" - это настоящее? - спросила Алиса.
- А "жопа" - это у нас символ вечности.
Антон (LogRus)
Глобальный модератор

ru
Offline Offline
Пол: Мужской
Внимание! Люблю сахар в кубиках!


WWW
« Ответ #11 : 30-03-2010 04:33 » 

я лучше кодом Улыбаюсь
Библиотека экспортирует IForX и GetIForX (точнее только GetIForX, а интерфейс берём из include)
Код:
//Глобальный класс
class X
{
static int x;
}

// Экспортируем интерфейс доступа
class IForX
{
virtual ~IForX() = 0;
virtual int GetX = 0;
};

// Экспортируем функцию получения интерфейса
IForX * GetIForX();

// реализация
class IForXImpl : public IForX
{
int GetX() { return X::x; }
~IForXImpl() {}
}

IForX * GetIForX();
{
static IForXImpl;
return & IForXImpl;
};

со структурами полагаю тоже работать будет Улыбаюсь
Записан

Странно всё это....
Dimka
Деятель
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #12 : 30-03-2010 08:23 » 

Цитата: Malaja
Менять этот класс я не имею права, т.к. он повсеместно используется.
Собственно, непонятно вот это. Повсеместно в рамках DLL или повсеместно во всех DLL и приложении?
Записан

Программировать - значит понимать (К. Нюгард)
Невывернутое лучше, чем вправленное (М. Аврелий)
Многие готовы скорее умереть, чем подумать (Б. Рассел)
Malaja
Команда клуба

de
Offline Offline
Пол: Женский

« Ответ #13 : 30-03-2010 09:47 » 

Dimka,
всюду... Я могу создавать обертки вокруг класса, вносить какие-то свои функции (если позволят архитекторы - а это еще большой вопрос), НО - не могу трогать уже существующее.

LogRus,
огромное спасибо за код! Сейчас отобьюсь с другим заданием и внимательно разберусь с примером.
Записан

холоднокровней, Маня, Ви не на работе
---------------------------------------
четкое определение сущности бытия:
- А мы в прошлом или в будущем?- спросила Алиса.
- Мы в жопе, - ответил кролик.
- А "жопа" - это настоящее? - спросила Алиса.
- А "жопа" - это у нас символ вечности.
RXL
Технический
Администратор

ru
Offline Offline
Пол: Мужской

WWW
« Ответ #14 : 30-03-2010 10:55 » 

LogRus, в примере: X::x - приватный, а должен быть паблик...
Записан

... мы преодолеваем эту трудность без синтеза распределенных прототипов. (с) Жуков М.С.
lapulya
Молодой специалист

ru
Offline Offline

« Ответ #15 : 30-03-2010 13:16 » 

RXL, паблик плохо, лучше френд
Записан

С уважением Lapulya
Джон
просто
Администратор

de
Offline Offline
Пол: Мужской

« Ответ #16 : 30-03-2010 13:50 » 

Я думаю LogRus использовал код для наглядности и краткости. У него ни в одном классе нет public, но это ещё не значит, что он так задумал. ИМХО это очевидно.
Записан

Я вам что? Дурак? По выходным и праздникам на работе работать. По выходным и праздникам я работаю дома.
"Just because the language allows you to do something does not mean that it’s the correct thing to do." Trey Nash
"Physics is like sex: sure, it may give some practical results, but that's not why we do it." Richard P. Feynman
"All science is either physics or stamp collecting." Ernest Rutherford
"Wer will, findet Wege, wer nicht will, findet Gründe."
Dimka
Деятель
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #17 : 30-03-2010 17:49 » 

Цитата: Malaja
всюду... Я могу создавать обертки вокруг класса, вносить какие-то свои функции (если позволят архитекторы - а это еще большой вопрос), НО - не могу трогать уже существующее.
Ну раз всюду, то в каждой единице сборки (DLL, EXE) свои значения статических членов этого класса. Для перекрёстного доступа можно использовать структуры или обёртки вида интерфейс-реализация.

Правда, я не могу представить себе такой класс, который бы имел экземпляр общего доступа внутри единицы сборки, но при этом был бы нужен перекрёстно в разных единицах сборки. Что-то не то в такой архитектуре. Хотя бы из того соображения, что если внутри единицы сборки есть свой собственный, выполняющий какие-то функции, зачем нужен в точности такой же из соседней, когда для получения результата проще воспользоваться своим.
« Последнее редактирование: 30-03-2010 17:51 от Dimka » Записан

Программировать - значит понимать (К. Нюгард)
Невывернутое лучше, чем вправленное (М. Аврелий)
Многие готовы скорее умереть, чем подумать (Б. Рассел)
Страниц: [1]   Вверх
  Печать  
 

Powered by SMF 1.1.21 | SMF © 2015, Simple Machines