Tworzenie / użycie obiektu klasy zdefiniowanej w bibliotece DLL

0

Witam.

Nagłówek biblioteki DLL:

 
#ifndef _DLL_H_
#define _DLL_H_

#if BUILDING_DLL
# define DLLIMPORT __declspec (dllexport)
#else /* Not BUILDING_DLL */
# define DLLIMPORT __declspec (dllimport)
#endif /* Not BUILDING_DLL */

#include <Windows.h>
#include <iostream>

using namespace std;

class Test 
{
	public:
		int val;

	public:
		Test();
		virtual int non();
                virtual int get();
};

#endif /* _DLL_H_ */

Definicja klasy

 
#include "DLLClass.h"

Test::Test()
{
	val = 99;
}


int Test::non()
{
	return 555;
}

int Test::get()
{

	return val;
}

extern "C" __declspec(dllexport) Test * CreateObjectOfClass()
{
	return new Test();
}

BOOL APIENTRY DllMain (HINSTANCE hInst, DWORD reason ,LPVOID reserved)
{
	switch(reason)
	{
		case DLL_PROCESS_ATTACH:
			cout << "Load DLL" << endl;
		break;
 
		case DLL_PROCESS_DETACH:
			cout << "Unload DLL" << endl;
		break;
 
		case DLL_THREAD_ATTACH:
		break;
 
		case DLL_THREAD_DETACH:
		break;
	}
 
	return TRUE;
}

Wykorzystanie biblioteki:

 
#include <windows.h>
#include <iostream>
#include <string>
#include "DLLClass.h"

using namespace std;

int main()
{
    HMODULE hMod;
    hMod = LoadLibrary("E:\\DLLClass\\Release\\DLLClass.dll");

    if(hMod != NULL)
    {

        typedef Test* (*PFNCreateTest)();

        PFNCreateTest pfnCreateTest = (PFNCreateTest)GetProcAddress(hMod, "CreateObjectOfClass");

        if(pfnCreateTest != NULL)
        {
            Test * a = (pfnCreateTest)();

            cout << a->get() << endl;

            delete a;

            a = NULL;
        }

        FreeLibrary(hMod);
	}

	return 0;
}

Problem polega na tym, iż każda metoda klasy, która chce wykonywać jakieś operacje na jej składnikach ( w tym wypadku na zmiennej val ), powoduje naruszenie segmentu pamięci. Metoda get zwraca jedynie wartość zmiennej publicznej val. Wygląda na to, że sposób deklaracji metod klasy powoduje, iż nie widzą się one na wzajem. Dzieje się to samo, gdy z funkcji get chciałbym wywołać metodę non.

Obiekt klasy jest tworzony. Potrafię wywołać funkcję non bez błędnie, ale ona nie działa na zmiennej val.

Mogę również bezpośrednio odwołać się do publicznej zmiennej val:

Test * a = (pfnCreateTest)();

 cout << a->val << endl;
 

Ciekawostką jest jedna rzecz. Otóż modyfikując nieco funkcję get wszystko działa w porządku. Oto kod:


// deklaracja w klasie
virtual int get(Test * ptr);

// definicja
int Test::get(Test * ptr)
{
	return ptr->val;
}

// użycie
Test * a = (pfnCreateTest)();

cout << a->get(a) << endl;

 

, wtedy wszystko działa w porządku.

Będę wdzięczny za wskazówki.

Pozdrawiam

0

Spróbuj tak:

#if BUILDING_DLL
# define DLLIMPORT __declspec (dllexport)
# define _PURE 
#else 
# define DLLIMPORT __declspec (dllimport)
# define _PURE =0 
#endif 
 
#include <Windows.h>
#include <iostream>
 
using namespace std;
 
class Test 
{
        public:
                int val;
 
        public:
                Test();
                virtual int non() _PURE;
                virtual int get() _PURE;
};
 
#endif /* _DLL_H_ */

p.s. jeśli tworzysz coś na stercie po stronie DLL-ki, to zadbaj o to, żeby po tej samej stronie zostało to coś zwolnione.

0

Witam.

Dziękuję za odpowiedź.

Jednak po modyfikacji zasugerowanej przez Ciebie, w funkcji:

 
extern "C" __declspec(dllexport) Test * CreateObjectOfClass()
{
	return new Test();
}

otrzymuję błąd kompilacji:

DLLClass.cpp(21): error C2259: 'Test' : cannot instantiate abstract class due to following members:
'int Test::non(void)' : is abstract
e:\dllclass\dllclass\DLLClass.h(26) : see declaration of 'Test::non'
'int Test::get(void)' : is abstract
e:\dllclass\dllclass\DLLClass.h(27) : see declaration of 'Test::get'

Próbowałem wcześniej czegoś podobnego, tworząc "interfejs" dla klasy Test :

 

class Inter
{
       public:
                virtual int non() = 0;
                virtual int get() = 0; 
}

class Test : public Inter
{
        public:
                int val;
 
        public:
                Test();
                int non();
                int get();
};

, ale niestety nie pomogło.

0

Bo nie masz poprawnie zdefiniowanego BUILDING_DLL (zakładam, że z tego samego nagłówka korzysta aplikacja lub biblioteka, która importuje ów klasę). Zdefiniuj to makro w projekcie DLL-ki, w opcjach.

I nie:

extern "C" __declspec(dllexport) Test * CreateObjectOfClass() { ... } 

tylko

extern "C" DLLIMPORT Test * CreateObjectOfClass(); 
0

Tym razem, po dodaniu makra do opcji projektu, otrzymuję błąd:

e:\dllclass\dllclass\DLLClass.h(26): error C3646: '_PURE' : unknown override specifier
e:\dllclass\dllclass\DLLClass.h(27): error C3646: '_PURE' : unknown override specifier

Zapewne wciąż jest to problem z makrem BUILDING_DLL.

Dodałem je w opcjach projektu DLL-ki, C/C++/Preprocessor/Preprocessor Definitions

0

Dodałem definicję _PURE dla niezdefiniowanego BUILDING_DLL, ale niestety DLL-ka zachowuje się wciąż tak samo - metoda get powoduje błąd.

0

Nie wiem, być może czegoś nie widzę, ale według mnie wszystko powinno być ok. W każdym razie nie spotkałem się z takim zachowaniem tego typu kodu. Czy DLLClass.h to jest ten nagłówek, który zawiera definicję klasy Test? Jeśli tak, to dlaczego nie ma tam deklaracji CreateObjectOfClass? Może pokaż zawartość DLLClass.h.

0

Nagłówek poprawiłem według Twoich wcześniejszych uwag:

 
#ifndef _DLL_H_
#define _DLL_H_

#if BUILDING_DLL
# define DLLIMPORT __declspec (dllexport)
# define _PURE 
#else 
# define DLLIMPORT __declspec (dllimport)
# define _PURE =0 
#endif 

#include <Windows.h>
#include <iostream>


using namespace std;



class Test
{
	public:
		int val;

	public:
		Test();
		virtual int non() _PURE;
		virtual int get() _PURE;
};


extern "C" DLLIMPORT Test * CreateObjectOfClass();

#endif /* _DLL_H_ */

0

A w pliku .cpp od klasy test masz aby zdefiniowaną globalną funkcję createObjectOfClass()?

0

Tak, funkcja jest zdefiniowana w pliku cpp klasy Test:

 
Test::Test()
{
	val = 99;
}


int Test::non()
{
	return 555;
}

int Test::get()
{
	return val;
}

Test * CreateObjectOfClass()
{
	return new Test();
}
0

daj na razie testowo w tym creacie return 0; i obacz,czy teraz się skompiluje.
Bo widzę,że usiłujesz tworzyć obiekt klasy abstrakcyjnej,co nie ma prawa się powieść.

0

Zmiana

 
return new Test();

na

return 0;
 

nie poprawia sytuacji, ale może źle Cię zrozumiałem. Kompiluje się równie dobrze z return new Test().

Poza tym, wydaje mi się, że obiekt jest tworzony poprawnie, skoro w aplikacji korzystającej z DLL-ki potrafię odczytać wartość zmiennej val oraz poprawnie wywołać metodę non

 
Test * a = (pfnCreateTest)();

cout << a->non() << endl;
cout << a->val << endl;

Gdyby obiekt się nie utworzył, to chyba byłoby to niemożliwe.

Pozdrawiam

0

Hm,to co dokładnie Ci nie działa?

0

Tak jak napisałem w pierwszym poście, mogę bezpośrednio odwołać się do zmiennej val

 
a->val;

oraz do metody non:

 
a->non();

Problem polega na tym, że gdy metoda ma wykonać jakąś operację na składniku klasy ( w tym wypadku metoda get oraz zmienna val ), aplikacja kończy się błędem

 
a->get(); 

Wszystko działa dobrze, gdy zmodyfikuję metodę get w ten sposób:

 
// deklaracja w klasie
virtual int get(Test * ptr);
 
// definicja
int Test::get(Test * ptr)
{
        return ptr->val;
}
 
// użycie
Test * a = (pfnCreateTest)();
 
cout << a->get(a) << endl;
1

Tak dla uporządkowania określ jawnie konwencję wywołania dla metod i eksportowanych funkcji, np. na __stdcall. Być może DLL-ka i aplikacja mają inaczej ustawione domyślną konwencję wywołania, i stąd ten problem. Zresztą przybudowie bibliotek DLL powinno się zawsze jawnie określać konwencję eksportowanych funkcji/metod.

0

Trafiłeś w dziesiątkę.

Problem tkwił w konwencji wywołania eksportowanych funkcji.

Biblioteka został skompilowana ze standardowymi ustawieniami Visual Studio :

 
__cdecl

natomiast aplikacja korzystająca z biblioteki przyjęła konwencję:

 
__stdcall

Zmiana deklaracji klasy na poniższą zadziałała:

class Test
{
	public:
		int val;

	public:
		Test();
		virtual int __stdcall non() _PURE;
		virtual int __stdcall get() _PURE;
};

Dziękuję za pomoc i poświęcony czas.

0

Witam ponownie.

Muszę trochę odkopać temat.

Otóż czy ten sposób zwalniania pamięci po obiekcie utworzonym po stronie DLL-ki jest poprawny?

Obiekt tworzony jest funkcją :

 
Test * CreateObject()
{
	return new Test();
}

Następnie zwalniam pamięć po obiektach utworzonych w klasie ( substytut destruktora, ponieważ pomimo ujednolicenia konwencji wywołania funkcji/metod na __stdcall, obiekt nie potrafi wywołać destruktora zdefiniowanego w DLL-ce, aplikacja kończy się naruszeniem pamięci ):

 
void Test::Release()
{
	delete str1;
	str1 = 0;
}

Następnie zwalniam pamięć po samym obiekcie, funkcją:

 
void DestroyObject (Test * ptr)
{
	delete ptr;
}

Chciałbym się upewnić czy w ten sposób poprawnie rozwiązałem "sprzątanie" po sobie w pamięci.

Dla jasności, całość kodu:

// deklaracja klasy

#ifndef _DLL_H_
#define _DLL_H_

#if BUILDING_DLL
# define DLLIMPORT __declspec (dllexport)
# define _PURE
#else
# define DLLIMPORT __declspec (dllimport)
# define _PURE =0
#endif

#include <Windows.h>
#include <iostream>
#include <string>

class Test
{
	private:
		std::string * str1;


	public:
		Test();
		virtual void __stdcall Release() _PURE;
		virtual void __stdcall Print() _PURE;
};


extern "C" DLLIMPORT Test * CreateObject();
extern "C" DLLIMPORT void DestroyObject (Test * ptr);

#endif /* _DLL_H_ */

 

// definicja metod klasy oraz funkcji do tworzenia / niszczenia obiektu

 
#include "DLLClass.h"

Test::Test()
{
	str1 = new std::string();
	*str1="Test";
}

void Test::Print()
{
	std::cout << "Test message " << *str1 << std::endl;
}

void Test::Release()
{
	delete str1;
	str1 = 0;
}

Test * CreateObject()
{
	return new Test();
}

void DestroyObject (Test * ptr)
{
	delete ptr;
}

Wykorzystanie biblioteki:

 
int main()
{
    HMODULE hMod;
    hMod = LoadLibrary("E:\\DLLClass\\Release\\DLLClass.dll");

    if(hMod != NULL)
    {

        typedef Test* (*PFNCreateTest)();
        typedef void* (*PFNDeleteTest)(Test * ptr);

        PFNCreateTest pfnCreateTest = (PFNCreateTest)GetProcAddress(hMod, "CreateObject");
        PFNDeleteTest pfnDeleteTest = (PFNDeleteTest)GetProcAddress(hMod, "DestroyObject");

        if(pfnCreateTest != NULL)
        {
            Test * a = (pfnCreateTest)();

            a->Print();

            a->Release();

            pfnDeleteTest(a);

            delete a;

            a = NULL;
        }

        FreeLibrary(hMod);
	}

	return 0;
}

Pozdrawiam

1

substytut destruktora, ponieważ pomimo ujednolicenia konwencji wywołania funkcji/metod na __stdcall, obiekt nie potrafi wywołać destruktora zdefiniowanego w DLL-ce, aplikacja kończy się naruszeniem pamięci

Kończy się tak zapewne dlatego, bo aplikacja i DLL-ka mają różne sterty. To nie ma związku z konwencją wywołania. Wystarczy, że zdefiniujesz destruktor tak, jak powinien być zdefiniowany, a w metodę Release zdefiniujesz w ten sposób:

void __stdcall Test::Release()
{
        delete this;
}

DestroyObject wywal.


Tak sobie myślę, że dobrze by było, gdybyś całość zorganizował w ten sposób: ```cpp class ITest //<--- interfejs wspólny dla DLL-ki i aplikacji { public: virtual void __stdcall Release() = 0; virtual void __stdcall Print() = 0; };

extern "C" DLLIMPORT ITest* __stdcall CreateObject();

```cpp
class TestImpl: public ITest //<--- po stronie DLL-ki, ukrywasz szczegóły implementacyjne
{
private:
	std::string * str1;
 
public:
	TestImpl();
	void __stdcall Release();
	void __stdcall Print();
};

...

ITest* __stdcall CreateObject()
{
        return new TestImpl();
}
0

Zazdroszczę wiedzy ;-).

Dziękuję. Wszystko działa w jak najlepszym porządku.

Pozdrawiam.

1 użytkowników online, w tym zalogowanych: 0, gości: 1