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

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

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

« : 28-03-2007 04:30 » 

Пишу на VisualStudio2005, C++(MFC).
Я с DLL ни разу не работал.
Описание
Есть приложение (назовем его прил.A), которое вызывает DLL.
В DLL есть класс, имя которого известно (иначе ведь никак) и этот класс, удовлетворяет интерфейсу, о котором знает прил.A.
Я могу работать c этим классом из прил.A?

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

Записан

С уважением, asker
Finch
Спокойный
Администратор

il
Offline Offline
Пол: Мужской
Пролетал мимо


« Ответ #1 : 28-03-2007 23:23 » 

asker, Я опишу свои пляски с бубном по заталкиванию классов в DLL. Делай выводы сам.

Первое что нужно сделать, это создать виртуальный класс с пустыми функциями. Я его назвал BaseClass.
BaseClass.h
Код:
#ifndef BASE_CLASS_H
#define BASE_CLASS_H

class BaseClass
{
public:
    BaseClass(){};
virtual void MyPrint(){};
virtual ~BaseClass(){};
};

#endif
Тут нужно заметить, что деструктор должен быть обязательно виртуальным.

Ну теперь очередь за созданием самой DLL Внешняя программа ничего не знает о внутреннем устройстве DLL и все общение производится только через экспортируемые функции. Как сделать экспортируемый целый класс я лично не знаю и думаю что нельзя такое сделать. Поэтому нужно делать функцию, которая будет создавать класс и возвращать ссылку  экземпляр класса. Заголовочный файл библиотеки будет выглядеть так:
Dll.h
Код:
/********************************************************************
Module  :   Dll.h
Subject :   
********************************************************************/
#ifndef DLLLOADAPI
#define DLLLOADAPI extern "C" __declspec(dllimport)
#endif

//-------------------------------------------------------------------
DLLLOADAPI   void * __stdcall Create();
//-------------------------------------------------------------------

Ну и соответственно реализация функции и класса будет такой:
Dll.cpp
Код:
/********************************************************************
Module  :   Dll.cpp
Subject :   
********************************************************************/
#include <stdio.h>
#include "Dll.h"
#include <windows.h>
#include "..\BaseClass.h"

class MyProbClass: BaseClass
{
public:
MyProbClass(int a);
virtual ~MyProbClass();

virtual void MyPrint();
protected:
int a;
};

MyProbClass::~MyProbClass()
{
printf("Destructor MyProbClass with const %d\n",a);
}

MyProbClass::MyProbClass(int a)
{
this->a=a;
printf("Constructor MyProbClass with const %d\n",this->a);
}

void MyProbClass::MyPrint()
{
printf("MyPrint MyProbClass with const %d\n",this->a);
}


void * __stdcall Create(int a)
{
MyProbClass *res=  new MyProbClass(a);
return (void *)res;
}

BOOL WINAPI DllMain(HINSTANCE hinst, unsigned long reason, void*)
{

switch( reason )
   {
case DLL_PROCESS_ATTACH:
           // Initialize once for each new process.
           // Return FALSE to fail DLL load.
           break;

      case DLL_THREAD_ATTACH:
         // Do thread-specific initialization.
            break;

      case DLL_THREAD_DETACH:
         // Do thread-specific cleanup.
            break;

      case DLL_PROCESS_DETACH:
         // Perform any necessary cleanup.
            break;
    }
    return TRUE;  // Successful DLL_PROCESS_ATTACH.
}

Так как мы применяем атрибут вызова функции __stdcall, чтобы не танцевать с бубном, отгадывая как обзавет функцию Create компилятор в таблице экспорта, нужно прописать def файл также следующего вида:
Dll.def
Код:
LIBRARY     Dll                        

DESCRIPTION 'Probe DLL'

EXPORTS
            Create                      @1

Комилируем библиотеку и проверяем DumpBin таблицу экспорта нашей Dll. Присутствует ли наша функция Create и правильно ли дано ей имя.

Теперь рассмотрим, как использовать шаманство в приложении:
Main.cpp
Код:
#include <stdio.h>
#include <windows.h>
#include ".\BaseClass.h"

typedef void * (__stdcall *Cre)(int);

int main()
{
HMODULE lib=LoadLibrary("Dll.Dll");
Cre create=(Cre) GetProcAddress(lib, "Create");
BaseClass *mycl=(BaseClass *)create(4);
mycl->MyPrint();
delete mycl;
FreeLibrary(lib);


return 0;
}

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

Цитата
И второе, что будет, если в разных DLL будет класс с одинаковым именем и обе эти DLL должны будут загрузиться в память.
Будет нормально работать. Так как сами классы находятся в разной зоне видимости.
Записан

Не будите спашяго дракона.
             Джаффар (Коша)
asker
Помогающий

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

« Ответ #2 : 31-03-2007 08:42 » 

Finch, болшое спасибо за ответ.

Добавлено через 38 дней, 18 часов, 47 минут и 4 секунды:
Finch, привет еще раз спасибо за отличный ответ.
Кстати, экспортировать целый класс можно. Надо перед именем класса поставить макрос AFX_EXT_CLASS.
Так называемая DLL-расширения.
« Последнее редактирование: 09-05-2007 03:29 от asker » Записан

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

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

« Ответ #3 : 09-05-2007 08:32 » 

Кстати, экспортировать целый класс можно. Надо перед именем класса поставить макрос AFX_EXT_CLASS. Так называемая DLL-расширения.

Тут необходимо уточнить. Этот макрос работает в том случае, если это библиотека расширения MFC. Те библиотека, которая экспортирует MFC классы или порождённые от них. А ты нигде про это не сказал.

В общем случае для экспорта своих классов работает, так как Finch сказал, ещё приведу код попроще:

Код:
#ifdef MYEXPORTCLASS_API_EXPORTS
#define MYEXPORTCLASS_API __declspec(dllexport)
#else
#define MYEXPORTCLASS_API __declspec(dllimport)
#endif

class MYEXPORTCLASS_API CMyExportClass
{
public:
CMyExportClass(void);

};
Записан

Я вам что? Дурак? По выходным и праздникам на работе работать. По выходным и праздникам я работаю дома.
"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."
nikedeforest
Команда клуба

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

« Ответ #4 : 13-05-2007 12:19 » 

Маленькая поправочка.  В С++ абстрактным классом (более редкое название виртуальный класс) называется класс, который содержит в себе хотя бы одну чисто виртуальную функцию. В связи с этим, BaseClass, будет примерно таким
Код:
class BaseClass
{
public:
    virtual void MyPrint()=0;
virtual ~BaseClass()=0;
};

При желании можно еще приписать BaseClass(){}. Не знаю, есть ли смысл.
« Последнее редактирование: 13-05-2007 13:56 от nikedeforest » Записан

ещё один вопрос ...
asker
Помогающий

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

« Ответ #5 : 17-05-2007 07:26 » 

Извиняюсь за неточность. Я просто подумал, что в начале я указал на чем пишу и соответсвенно...
А я не знал что деструктор может быть чисто виртуальным.
Записан

С уважением, asker
nikedeforest
Команда клуба

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

« Ответ #6 : 17-05-2007 07:52 » 

Код компилил, проблем не было.
Записан

ещё один вопрос ...
Алексей++
глобальный и пушистый
Глобальный модератор

ru
Offline Offline
Сообщений: 13


« Ответ #7 : 26-07-2011 12:41 » 

Есть глупый вопрос: импорт класса из DLL нельзя сделать без h-файла ?
Записан

RXL
Технический
Администратор

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

WWW
« Ответ #8 : 26-07-2011 13:49 » 

Сомневаюсь: таблицы импорта и экспорта - это просто имена меток - текстовое имя и адрес в модуле. Между собой они не связаны. Если только в имя не помещают полную исчерпывающую информацию, в чем я сомневаюсь.
« Последнее редактирование: 26-07-2011 13:51 от RXL » Записан

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

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

« Ответ #9 : 26-07-2011 14:03 » 

Лёш, а как ты собираешься потом эту ДЛЛ (класс из неё) использовать?

зы Это был встречный глупый вопрос. Ага
Записан

Я вам что? Дурак? По выходным и праздникам на работе работать. По выходным и праздникам я работаю дома.
"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."
Алексей++
глобальный и пушистый
Глобальный модератор

ru
Offline Offline
Сообщений: 13


« Ответ #10 : 26-07-2011 16:24 » 

ну, то есть, чтобы работать с тем же COM объектом, всегда в коде (неважно, на C++ или другом языке) просто необходимо иметь свой туземный языковый заголовочник и либу. Так ?

Добавлено через 3 минуты и 20 секунд:
а то вот, к примеру, написал я DLL с COM объектом. Зарегил в реестре всё, что нужно. Далее вижу два варианта:
1) lib + h + левые исходники, пользующие хедер и либу
2) LoadLibrary + GetProcAddress . А в DLL - набор голых экспортированных функций

Добавлено через 4 минуты и 52 секунды:
короче, ноги растут из этой темы https://forum.shelek.ru/index.php/topic,27463.0.html

А я понятия не имею, как 1С юзает DLL и COM. Сдаётся мне, это именно второй вариант
2) LoadLibrary + GetProcAddress . А в DLL - набор голых экспортированных функций
« Последнее редактирование: 26-07-2011 16:33 от Алексей1153 » Записан

Kivals
Команда клуба

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

WWW
« Ответ #11 : 26-07-2011 17:36 » 

стоп! dll и com - это абсолютно разные вещи!
Да - COM объект может быть в dll, но с тем же успехом может быть и в exe.
Если мы говорим о dll, содержащей COM - то ей никакие заголовки не нужны: в такой dll есть служебные области, которые описывают COM объект.
Другое дело - "стандартная" dll. Она (как написано выше) содержит только адреса точек входа и даже не содержит количества (и типа) параметров функций.
Т.е. COM - это расширение стандартной dll.
1C (по умолчанию) умеет юзать только COM объекты (не важно - dll или exe - главное чтобы они были зарегистрированы в системе).
В то же время - WinAPI - это "стандартные" dll.
Потому для использования WinAPI в 1С (и подобных языках, умеющих подключать исключительно COM) и сущесвует DynamicWrapper, который реализует COM интерфейс, но умеет вызывать функции из "стандартных" dll. Для него эти функции описываются кодом при вызове.
Пример для VB:
Код: (Visual Basic)
Set Wrap = CreateObject("DynamicWrapper")
Wrap.Register "KERNEL32.DLL", "Beep", "i=ll", "f=s", "r=l"
res = Wrap.Beep(500, 100)
res = Wrap.Beep(550, 100)
res = Wrap.Beep(600, 100)
res = Wrap.Beep(650, 100)
res = Wrap.Beep(700, 700)
WScript.Sleep 200
res = Wrap.Beep(700, 100)
res = Wrap.Beep(650, 100)
res = Wrap.Beep(600, 100)
res = Wrap.Beep(550, 100)
res = Wrap.Beep(500, 700)

Чем-то это напоминает вызов функций WinAPI из коммандной строки с использованием rundll32:
rundll32 kernel32,Beep 500,100
rundll32 kernel32,Beep 550,100
rundll32 kernel32,Beep 600,100
...

Если же ты пишешь правильный COM объект, упакованый в dll, то никаких дополнительных ухищрений не нужно (кроме как зарегистрировать объект в системе):
Пример для VB:
Код: (Visual Basic)
Set Obj = CreateObject("Alex1153PP.MyRegisteredObject")
Obj.MySuperMethod(1,2,"Hello world!")
Записан
Алексей++
глобальный и пушистый
Глобальный модератор

ru
Offline Offline
Сообщений: 13


« Ответ #12 : 26-07-2011 18:14 » new

Цитата
(23:47:16) Алексей1153++: Kivals, в общем, ситуация такая - читаю теорию по COM , пока не всё переварил. А заказчик хочет побыстрее, поэтому самый быстрый сейчас вариант - добывать адресА функций и вызывать напрямую (описание параметров я заку предоставлю)

Это возможно ?
(23:47:36) Алексей1153++: в смысле - вызвать из 1С
(23:48:14) Алексей1153++: параметры все простые - int , void
(23:48:19) Kivals: Возможно, но нужно изучить доку по DynWrap http://www.script-coding.com/dynwrap.html
(23:48:48) Kivals: она простая (по ссылке в разделе по 1С) и примеров достаточно
(23:49:30) Алексей1153++: так у меня нет 1С и нет справки по ней (
(23:50:02) Алексей1153++: поэтому я не представляю, как мне правильно DLL оформить, чтобы можно всё было без проблем вызвать
(23:50:29) Алексей1153++: кстати, строку мне тоже надо будет передавать и возвращать
(23:50:35) Kivals: я имею в виду справку по DynWrap. Код 1С будет достаточно прост (щас накидаю)
(23:50:47) Алексей1153++: причём она там юникодная
(23:50:48) Kivals: Судя по доке - строки умеет
(23:51:14) Алексей1153++: ок
(23:51:18) Kivals: Возможные типы данных:
Код:
const ARGTYPEINFO ArgInfo[] =
{
{'a', sizeof(IDispatch*), VT_DISPATCH}, // a IDispatch*
{'c', sizeof(unsigned char), VT_I4}, // c signed char
{'d', sizeof(double), VT_R8}, // d 8 byte real
{'f', sizeof(float), VT_R4}, // f 4 byte real
{'k', sizeof(IUnknown*), VT_UNKNOWN}, // k IUnknown*
{'h', sizeof(long), VT_I4}, // h HANDLE
{'l', sizeof(long), VT_I4}, // l long
{'p', sizeof(void*), VT_PTR}, // p pointer
{'s', sizeof(BSTR), VT_LPSTR}, // s string
{'t', sizeof(short), VT_I2}, // t short
{'u', sizeof(UINT), VT_UINT}, // u unsigned int
{'w', sizeof(BSTR), VT_LPWSTR}, // w wide string
}
Примечание: для строк в некоторых случаях работает тип 'r' VT_BYREF (передача по ссылке).
(23:51:43) Kivals: скорее всего у тебя для строк будет тип w
(23:51:52) Алексей1153++: это что
(23:51:56) Kivals: Напиши сюда заголовок функции с параметрами
(23:52:11) Kivals: Кста: а 1С у заказчика какая?
(23:52:24) Kivals: А то создание объекта будет отличаться немного
(23:54:28) Алексей1153++: типы такие

typedef int (WINAPI *tdf_TMV1C_Interface__CreateInstance )();
typedef void (WINAPI *tdf_TMV1C_Interface__ShowDialog )(int,int);
typedef void (WINAPI *tdf_TMV1C_Interface__ReleaseInstance )(int);
typedef void (WINAPI *tdf_TMV1C_Interface__SomeF1 )(const WCHAR*,int);//передать строку
typedef void (WINAPI *tdf_TMV1C_Interface__SomeF2 )(const WCHAR*,int); //вертать строку
(23:55:10) Алексей1153++: версию не знаю. Скорее 7 , но может и 8
(00:00:48) Алексей1153++: может мне сделать тестовую длл, которая будет мессаджи показывать ?
(00:00:51) Kivals: Для 7.7 для первых 2х функций как-то так:

CreateInstance = CreateObject("DynamicWrapper");
CreateInstance.Register("dllname.dll", "tdf_TMV1C_Interface__CreateInstance", "i=", "f=s", "r=l");
Res = CreateInstance();

ShowDialog = CreateObject("DynamicWrapper");
ShowDialog.Register("dllname.dll", "tdf_TMV1C_Interface__ShowDialog", "i=ll", "f=s", "r=l");
ShowDialog(100, 100);
(00:01:27) Алексей1153++: а энтот DynamicWrapper - он у них обычно установлен уже ?
(00:02:09) Kivals: не - скачать и зарегистрировать нужно. Это сторонняя компонета.
Можешь в свой инсталятор добавить, если прога с интсалятором поставляется
(00:02:14) Алексей1153++: "i=", "f=s", "r=l" - это как разобрать
(00:03:01) Kivals: Для 8.Х вместо
CreateInstance = CreateObject("DynamicWrapper");
будет
CreateInstance = New ComObject("DynamicWrapper");

И так для каждой функии - вот и все различия.
(00:03:29) Алексей1153++: ок, спасибо
(00:03:38) Kivals: Там же в ссылке есть описание:
http://www.script-coding.com/dynwrap.html
(00:04:08) Kivals: Параметры при объявлении содержат имя библиотеки, имя функции, а также:

i - описывает количество и тип данных параметров функции. Если функция не принимает параметры, этот параметр можно опустить. Параметр i является строкой, количество букв в которой равно количеству параметров объявляемой функции. Первая буква задаёт тип первого параметра, вторая - втрого и т.д.
f - тип вызова: _stdcall, _cdecl и т.д. Значение по умолчанию - _stdcall. Возможные значения параметра:
'm' - DC_MICROSOFT 0x0000, Default
'b' - DC_BORLAND 0x0001, Borland compat
's' - DC_CALL_STD 0x0020, __stdcall
'c' - DC_CALL_CDECL 0x0010, __cdecl
'4' - DC_RETVAL_MATH4 0x0100, Return value in ST
'8' - DC_RETVAL_MATH8 0x0200, Return value in ST
r - тип возвращаемых данных.
(00:05:41) Алексей1153++: угу, понятно. Щас почитаю
(00:06:07) Kivals: Давай.
Если что - пиши дальше в тему
(00:06:50) Алексей1153++: а, то есть сначала как бы декларация функций
(00:06:59) Алексей1153++: а потом их можно вызывать привычно
(00:07:05) Kivals: угу, именно
(00:07:14) Алексей1153++: Const WM_GETTEXT = &HD
Set Wrap = CreateObject("DynamicWrapper")
Wrap.Register "USER32.DLL", "GetForegroundWindow", "f=s", "r=l"
Wrap.Register "USER32.DLL", "SendMessage", "i=lllr", "f=s", "r=l"
Title = Space(100)
res = Wrap.SendMessage(Wrap.GetForegroundWindow(), WM_GETTEXT , 100, Title)
MsgBox Title
(00:07:43) Алексей1153++: ну усё, смысл понятен, благодарю )
(00:07:47) Kivals: Т.е. для COM объектов эта декларация вшита в dll, а для обычных dll ты должен сам про нее позаботиться


Добавлено через 1 час, 29 минут и 58 секунд:
А что за странность выползает при компиляции DLL (но всё работаем, тем не менее)

Цитата
Осуществляется регистрация выхода...
Project : error PRJ0050: ЌҐ г¤ «®бм § ॣЁбваЁа®ў вм ўл室. “ЎҐ¤ЁвҐбм, зв® г ў б Ґбвм ­Ґ®Ўе®¤Ё¬лҐ а §аҐиҐ­Ёп ­  Ё§¬Ґ­Ґ­ЁҐ ॥бва .

нечитабельная строка - она в студии такая же нечитабельная
« Последнее редактирование: 26-07-2011 19:47 от Алексей1153 » Записан

Алексей++
глобальный и пушистый
Глобальный модератор

ru
Offline Offline
Сообщений: 13


« Ответ #13 : 26-07-2011 22:11 » 

Интересную вещь заметил: после загрузки DLL

HMODULE m=LoadLibrary(_T("111.dll") );

её (dll то есть) можно удалить с диска. Это не помешает ни получить адреса функций при помощи GetProcAddress, ни даже вызывать эти функции.

Это как так ? Улыбаюсь
Записан

Kivals
Команда клуба

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

WWW
« Ответ #14 : 26-07-2011 23:31 » 

Алексей1153++, ну так в памяти она уже - вот и удаляй сколько влезет... Улыбаюсь
Записан
Страниц: [1]   Вверх
  Печать  
 

Powered by SMF 1.1.21 | SMF © 2015, Simple Machines