/ Forside / Teknologi / Udvikling / C/C++ / Nyhedsindlæg
Login
Glemt dit kodeord?
Brugernavn

Kodeord


Reklame
Top 10 brugere
C/C++
#NavnPoint
BertelBra.. 2425
pmbruun 695
Master_of.. 501
jdjespers.. 500
kyllekylle 500
Bech_bb 500
scootergr.. 300
gibson 300
molokyle 287
10  strarup 270
Dll filer
Fra : Gonex


Dato : 25-05-01 13:02

Egentlig et spørgsmål om Windows programmering, men jeg prøver alligevel.
Jeg ønsker at eksportere en klasse ud af en dll, og jeg er indtil videre
nået frem til en løsning der ser ud som følgende:

//---------------------------minKlasse.h------------------------------------
--

#ifdef __cplusplus
extern "C" {
#endif

#ifdef DLLBUILD
#define DllClass __declspec(dllexport)
#else
#define DllClass __declspec(dllimport)
#endif

class DllClass minKlasse
{
public:
minKlasse(int i);
~minKlasse();
int minKlasseSum(int i);
private:
int enVariabel;
};

#ifdef __cplusplus
}
#endif


//--------------------------minKlasse.cpp-----------------------------------


#define DLLBUILD
#include "minKlasse.h"

minKlasse::minKlasse(int i)
{
enVariabel = i;
}

minKlasse:minKlasse()
{
}

int minKlasse::minKlasseSum(int i)
{
return enVariabel + i;
}


Første spørgsmål. Er det den rigtige måde at eksportere en klasse ud af en
DLL?

Spørgsmål to. Nu står jeg så og skal lave en klient der kan bruge min
klasse. (Jeg vil gerne hvis det er muligt have hjælp til både dynamisk og
statisk linkning, men helst dynamisk.) Hvordan gør jeg det? I dynamisk
linkning er jeg ved at have fundet ud af jeg skal bruge LoadLibrary(),
FreeLibrary() og GetProcAddress(). Men jeg mangler viden om hvordan man i
praksis gør. Jeg har fundet ud af at kalde funktioner fra min dll, men en
klasse er stadig et mysterium for mig. Hvis der er nogle der ligger inde med
eksempler modtages de også gerne.

På forhånd tak.
Hilsen Mikael Heinze
gonex@esenet.dk

Brug ikke "svar til afsender" der er spamfilter på.



 
 
Mogens Hansen (25-05-2001)
Kommentar
Fra : Mogens Hansen


Dato : 25-05-01 17:05

Hej Mikael,

Først en generel advarsel:
Med mindre at man har en _meget_ god grund til at anvende DLL'er er det
ondsindet!
Det giver ofte flere problemer end de løser (version helvede, mere
kompliceret build, hvilken heap-manager bliver der allokeret på, manglende
kompatibilitet på tværs compilere - både versioner og leverandører etc.).
Der findes dog gode grunde til at bruge DLL'er.
Der er generel enighed om kalde-konvention på C niveau og som
COM-interfaces, men ikke på C++. Der mangler et ABI (application binary
interface). Det er på vej på visse platforme (f.eks. gcc V3.0, Intel Itanium
på ???) - "En ny og bedre fremtid" (TM).
Se udvalgte dele af argumentationen for Microsoft .NET platformen, hvis du
er i tvivl.

"C++ foretrækker source kode" (TM).

"Gonex" <nospam@please.tk> wrote in message
news:9ele3m$qs2$1@news.inet.tele.dk...
>
> #ifdef __cplusplus
> extern "C" {
> #endif

Giver det mening at skrive extern "C" i forbindelse med C++ klasser ?
Man plejer at bruge det når man vil interface C-funktioner med C++
funktioner (Hint: name-mangling, typesage linkage).

>
> #ifdef DLLBUILD

Advarsel: du er ikke den første der har opfundet macroen DLLBUILD!
Brug BUILD_MY_DLL_V1_1_1 eller nogen GUID baseret som Microsoft Visual C++
vistnok gør.

>
> Første spørgsmål. Er det den rigtige måde at eksportere en klasse ud af en
> DLL?

Det er ikke den eneste måde.

Istedet for __declspec(...) kan du anvende module definition filer (DEF
filer), hvor member-funktionerne bliver listet i EXPORT sektionen.
Det har den fordel at det ikke påvirker source-koden, hvis man vil bruge den
i andre sammenhænge (anden platform, statisk linket, linket i et andet DLL
etc.). Det krævet til gengæld lidt kedeligt manuelt (men automatisérbart)
arbejde.
Du kan bruge TDUMP.EXE sammen med Borland C++Builder eller DUMPBIN.EXE
sammen med Microsoft Visual C++ til at liste de "manglede" navne til brug i
DEF-filer.
Det giver vistnok et lille performance overhead at anvende EXPORT sektionen.

En anden måde, er at lægge sig op af Microsoft COM-modellen:
Definer et abstrakt interface.
Implementer en konkret implementation af det abstrakte interface i DLL.
Lav en factory funktion, som benyttes til at oprette objekter af den
konkrete type.
Så slipper du for at eksportere klassen i det hele taget!

>
> Spørgsmål to. Nu står jeg så og skal lave en klient der kan bruge min
> klasse. (Jeg vil gerne hvis det er muligt have hjælp til både dynamisk og
> statisk linkning, men helst dynamisk.) Hvordan gør jeg det? I dynamisk

Lige lidt definition, som jeg opfatter det:
Statisk linking: funktionerne er direkte indehold i EXE-filen
Dynamisk linkning: funktionerne ligger i en DLL-fil
Dynamisk loadning: DLL loades med LoadLibrary, FreeLibrary og
GetProcAddress

Hvis du bruger dynamisk linkning, er det nemmere at anvende import library i
stedet for LoadLibrary.

> linkning er jeg ved at have fundet ud af jeg skal bruge LoadLibrary(),
> FreeLibrary() og GetProcAddress(). Men jeg mangler viden om hvordan man i
> praksis gør. Jeg har fundet ud af at kalde funktioner fra min dll, men en
> klasse er stadig et mysterium for mig. Hvis der er nogle der ligger inde
med
> eksempler modtages de også gerne.

Hvis du vil bruge LoadLibrary sammen med C++ klasser, er den eneste (? - man
kan sikkert noget med noget assembler-kode kombineret med GetProcAddress!)
farbare vej at lave et abstrakt interface kombineret med factory metoder til
oprettelse og nedlæggelse af objekterne (a la COM).

Venlig hilsen

Mogens Hansen



Gonex (25-05-2001)
Kommentar
Fra : Gonex


Dato : 25-05-01 22:21

Allerførst tak for svaret

[klip]
> En anden måde, er at lægge sig op af Microsoft COM-modellen:
> Definer et abstrakt interface.
> Implementer en konkret implementation af det abstrakte interface i DLL.
> Lav en factory funktion, som benyttes til at oprette objekter af den
> konkrete type.
> Så slipper du for at eksportere klassen i det hele taget!
[klip]

Jeg er i gang med at lære COM (Skal faktisk til eksamen i et valgfag der
hedder C++, COM, DCOM & COM+), og kunne sikkert godt løse problemet den vej.
Meningen var dog at jeg gerne ville have noget af den grundlæggende dll
viden lidt på plads. Desuden har jeg haft opfattelsen af at COM er overkill
til "småting".

[klip]
> Hvis du bruger dynamisk linkning, er det nemmere at anvende import library
i
> stedet for LoadLibrary.
[klip]

Det kan godt vøre jeg spørger dumt nu, men mener du #import af et
typebibliotek *.tlb eller selve dll'en, altså i stil med #include, eller er
det en funktion/procedure, eller noget helt andet?

[klip]
> Hvis du vil bruge LoadLibrary sammen med C++ klasser, er den eneste (? -
man
> kan sikkert noget med noget assembler-kode kombineret med GetProcAddress!)
> farbare vej at lave et abstrakt interface kombineret med factory metoder
til
> oprettelse og nedlæggelse af objekterne (a la COM).
[klip]

Konklusionen må være at det (næsten) ikke er muligt at eksportere en klasse
ud af en dll, kun funktioner/procedure.



Mogens Hansen (25-05-2001)
Kommentar
Fra : Mogens Hansen


Dato : 25-05-01 22:36


"Gonex" <nospam@please.tk> wrote in message
news:9emeqm$sb$1@news.inet.tele.dk...
>
> [klip]
> > En anden måde, er at lægge sig op af Microsoft COM-modellen:
> > Definer et abstrakt interface.
> > Implementer en konkret implementation af det abstrakte interface i DLL.
> > Lav en factory funktion, som benyttes til at oprette objekter af den
> > konkrete type.
> > Så slipper du for at eksportere klassen i det hele taget!
> [klip]
>
> Jeg er i gang med at lære COM (Skal faktisk til eksamen i et valgfag der
> hedder C++, COM, DCOM & COM+), og kunne sikkert godt løse problemet den
vej.
> Meningen var dog at jeg gerne ville have noget af den grundlæggende dll
> viden lidt på plads. Desuden har jeg haft opfattelsen af at COM er
overkill
> til "småting".

COM løser nogle opgaver og har sit eget sæt af problemer (skrøbelig
installation, versionering, begrænset typesystem etc.). Ingenting er gratis.
Se argumentationen for .NET.
Min udlægning er at COM er godt/brugbart når man skal:
* Krydse programmerings sprog grænser (især når Visual Basic skal bruge )
* Krydse process grænser (incl. netværks grænser)
* Interface til systemer, som kræver COM - f.eks. MTS.
Hvis man ikke har nogen af ovenstående problemer, bidrager COM kun med
ekstra kompleksitet.

Man kan sagtens lære en del af COM's interface baserede design - men det
betyder ikke at man _skal_ bruge COM for at lave tilsvarende eller bedre
implementeringer.
Jeg foreslog _ikke_ at du skulle bruge COM.
Jeg foreslog bl.a. at man kan lave:
* Et abstrakt interface
* En konkret implementering af det konkrete interface
* En factory funktion til at oprette og nedlægge objekter
Den model _svarer_ fuldstændigt til hvad COM gør, men har ikke begrænsninger
og krav med hensyn til registry, fejlrapportering, begrænset typesystem etc.

Hvis du har et interface kaldet "test_class", som bliver implementeret i den
konkrete klasse "test_class_impl", og du gerne vil have "test_class_impl"
til at ligge i et DLL kaldet MY_DLL.DLL, så har du følgende:

----------------- start på TEST_CLASS.H ----------------------------
#ifndef TEST_CLASS_H_GUARD
#define TEST_CLASS_H_GUARD

#include <vector>

class test_class
{
public:
test_class(void);
virtual ~test_class();

class some_error {};

virtual bool some_func(std::vector<int>& vi) throw (some_error) = 0;

private:
test_class(const test_class&);
test_class& operator=(const test_class&);
};

#endif

------------ start på TEST_CLASS.CPP -------------------------------
#include "test_class.h"

test_class::test_class(void)
{
}

test_class:test_class()
{
}

-------------- start på TEST_IMPL.H --------------------------
#ifndef TEST_IMPL_H_GUARD
#define TEST_IMPL_H_GUARD

#include "test_class.h"

class test_class_impl : public test_class
{
public:
test_class_impl(void);

virtual bool some_func(std::vector<int>& vi) throw (some_error);

private:
test_class_impl(const test_class_impl&);
test_class_impl& operator=(const test_class_impl&);
};

#endif

--------------- start på TEST_IMPL.CPP ---------------------------
#include "test_impl.h"

test_class_impl::test_class_impl(void) :
test_class()
{
}

bool test_class_impl::some_func(std::vector<int>& vi) throw
(test_class::some_error)
{
if(vi.empty()) {
throw test_class::some_error();
}

return true;
}

--------------------- start på MY_DLL.H ---------------------------------
#ifndef MY_DLL_H_GUARD
#define MY_DLL_H_GUARD

#include "test_class.h"

test_class& __declspec(dllimport) create_test_class(void);
void __declspec(dllimport) destroy_test_class(test_class& to);

#endif
----------------------- start på MY_DLL.CPP --------------------------
#include <windows.h>
#include "test_impl.h"

int WINAPI DllEntryPoint(HINSTANCE /*hinst*/, unsigned long /*reason*/,
void* /*lpReserved*/)
{
return 1;
}

test_class& __declspec(dllexport) create_test_class(void)
{
return *new test_class_impl;
}

void __declspec(dllexport) destroy_test_class(test_class& to)
{
delete &to;
}

DLL'et MY_DLL.DLL består af følgende source filer:
MY_DLL.CPP
TEST_CLASS.CPP
TEST_IMPL.CPP
bemærk at de vigtige klasser "test_class" og "test_class_impl" er
fuldstændig almindelig, platform neutral ANSI C++.
Der er _ikke_ gjort noget for at eksportere klasserne - det er ikke
nødvendigt!
Kun MY_DLL indeholder noget kode der har med Windows og DLL'er at gøre.
DLL'er (og COM) er _ikke_ et design-fænomen, men en deployment teknologi!
Jeg mener at man gør sig selv en bjørnetjeneste ved at tro noget andet -
selvom det ikke er usædvanligt.

Man kun nu bruge det i et program, som f.eks.:
#include "my_dll.h"

int main(void)
{
test_class* to = 0;
try {
to = &create_test_class();
to->some_func(std::vector<int>());
destroy_test_class(*to);
return 0;
}
catch(...) {
if(to) {
destroy_test_class(*to);
}
return 1;
}
}

som dog inkluderer den DLL-specifikke fil. Det kan man dog også sagtens
skjule - hvis man har brug for det.

Enhver nogenlunde Windows compatibel C++ compiler bør kunne klare
ovenstående.

>
> [klip]
> > Hvis du bruger dynamisk linkning, er det nemmere at anvende import
library
> i
> > stedet for LoadLibrary.
> [klip]
>
> Det kan godt vøre jeg spørger dumt nu, men mener du #import af et
> typebibliotek *.tlb eller selve dll'en, altså i stil med #include, eller
er
> det en funktion/procedure, eller noget helt andet?

Når du laver et DLL, hvorfra der bliver eksporteret nogle funktioner, laver
linkeren et import library, som er en LIB-fil der hedder det samme som DLL.
Hvis du includerer headerfilerne der erklærer de eksporterede funktioner, og
linker import librariet med, så skal man ikke gøre yderligere.
Personligt bryder jeg mig ikke om #import.
Det er YARLE (yet another redundant language extension), som jeg ikke mener
tjener andet formål end at sovse koden ind i Microsoft platformsbindinger,
uden at give noget til nævneværdigt til gengæld.

>
> Konklusionen må være at det (næsten) ikke er muligt at eksportere en
klasse
> ud af en dll, kun funktioner/procedure.

Nej, overhovedet ikke. Kig f.eks. på MFC DLL'et. Der er masser af DLL'er der
eksporter klasser. Hvis det er konkrete klasser, de eksporterer, må de bruge
import libraries - så vidt jeg kan se.
Virkede det du havde skrevet i dit oprindelige indlæg ikke ? Det så ud til
at være tæt på.
Eller start din compiler op, klik på nogle passende Wizards hurtigt efter
hinanden - så skulle den være hjemme.

Venlig hilsen

Mogens Hansen



Gonex (26-05-2001)
Kommentar
Fra : Gonex


Dato : 26-05-01 16:44

Mange tak for hjælpen Mogen. Det virker perfekt nu. Faktisk har det hjulpet
mig meget i at forstå hvad det egentlig er der sker i COM når man f.eks
kalder CoCreateInstance(...).

Jeg har dog et sidste spørgsmål som jeg håber du måske kan svare på.

Hvis du i nogle metoder på en klasse bruger "cout << "et eller andet debug
info" << endl;" og klassen ligger i dll filen, bliver det ikke skrevet i
consollen når metoden kaldes. Til gængæld bliver det skrevet lige før
programmet lukker. Altså lige efter main() har afviklet sin sidste linie.
Har du en god forklaring på det?
Jeg har måske selv en teori om at det er fordi exe filen "ejer" dette consol
vindue så dll filen får ikke lov at skrive i den før den igen er givet fri.




Mogens Hansen (26-05-2001)
Kommentar
Fra : Mogens Hansen


Dato : 26-05-01 19:55


"Gonex" <nospam@please.tk> wrote in message
news:9eofg4$st0$1@news.inet.tele.dk...
> Mange tak for hjælpen Mogen. Det virker perfekt nu. Faktisk har det
hjulpet
> mig meget i at forstå hvad det egentlig er der sker i COM når man f.eks
> kalder CoCreateInstance(...).

Det lyder godt.
Jo mere man forstår en teknologi, jo bedre kan man bruge den på de rigtige
steder, og se hvad der er nyttigt og hvad der er hype. Magien forsvinder.

>
> Jeg har dog et sidste spørgsmål som jeg håber du måske kan svare på.
>
> Hvis du i nogle metoder på en klasse bruger "cout << "et eller andet debug
> info" << endl;" og klassen ligger i dll filen, bliver det ikke skrevet i
> consollen når metoden kaldes. Til gængæld bliver det skrevet lige før
> programmet lukker. Altså lige efter main() har afviklet sin sidste linie.
> Har du en god forklaring på det?

Nej, egentlig ikke.
Det _skal_ skrives ud øjeblikkeligt med "endl", idet det indeholder en
flush.

> Jeg har måske selv en teori om at det er fordi exe filen "ejer" dette
consol
> vindue så dll filen får ikke lov at skrive i den før den igen er givet
fri.

Nej, exe-filen "ejer" ikke consol vinduet.
Consol vinduet ejes af processen, som både exe-filen og dll-filen er en del
af.
Det skal være muligt at tilgå cout på lige fod i exe-filen og dll-filen.

Hvordan har du linket runtime library ?
Hvilken compiler bruger du ?

Venlig hilsen

Mogens Hansen





Gonex (27-05-2001)
Kommentar
Fra : Gonex


Dato : 27-05-01 13:58

[klip]
> > Jeg har dog et sidste spørgsmål som jeg håber du måske kan svare på.
> >
> > Hvis du i nogle metoder på en klasse bruger "cout << "et eller andet
debug
> > info" << endl;" og klassen ligger i dll filen, bliver det ikke skrevet i
> > consollen når metoden kaldes. Til gængæld bliver det skrevet lige før
> > programmet lukker. Altså lige efter main() har afviklet sin sidste
linie.
> > Har du en god forklaring på det?
>
> Nej, egentlig ikke.
> Det _skal_ skrives ud øjeblikkeligt med "endl", idet det indeholder en
> flush.
[klip]

Problemet er løst. Det er mig der er en klovn, jeg havde glemt "<< endl" nu
er der ingen problemer. Såddan kan man jo se sig så blindt på et problem at
man ikke engang kan se den løsning der ligger lige for.



Gonex (26-05-2001)
Kommentar
Fra : Gonex


Dato : 26-05-01 16:53

Jeg glemte lige et andet spørgsmål. Hvorfor gør du det på følgende måde:

[klip]
> test_class& __declspec(dllexport) create_test_class(void)
> {
> return *new test_class_impl;
> }
>
> void __declspec(dllexport) destroy_test_class(test_class& to)
> {
> delete &to;
> }
>
> int main(void)
> {
> test_class* to = 0;
> to = &create_test_class();
> destroy_test_class(*to);
> return 0;
> }
[klip]

istedet for:

test_class* __declspec(dllexport) create_test_class(void)
{
return new test_class_impl;
}

int main(void)
{
test_class* to = 0;
to = create_test_class();
delete to;
return 0;
}




Mogens Hansen (26-05-2001)
Kommentar
Fra : Mogens Hansen


Dato : 26-05-01 19:55


"Gonex" <nospam@please.tk> wrote in message
news:9eog13$1l7$1@news.inet.tele.dk...
> Jeg glemte lige et andet spørgsmål. Hvorfor gør du det på følgende måde:

> > test_class& __declspec(dllexport) create_test_class(void)
>
> istedet for:
>
> test_class* __declspec(dllexport) create_test_class(void)

Godt spørgsmål.
Det at en funktion tager eller returnerer en reference _garanterer_ at der
findes et objekt (eller er programmet ill formed).
Hvis funktionen returnerer en pointer kan den være 0, hvorfor den kaldende
skal teste.
Det bliver nemt en sammenblanding af fejl-håndtering og returnering af
resultatet.
Hvis funktionen ikke er i stand til at oprette det ønskede objekt, kan den
bare kaste en exception, for at signalere det.

Venlig hilsen

Mogens Hansen





Søg
Reklame
Statistik
Spørgsmål : 177552
Tips : 31968
Nyheder : 719565
Indlæg : 6408847
Brugere : 218887

Månedens bedste
Årets bedste
Sidste års bedste