OVH Cloud OVH Cloud

Utilité DLL C++

102 réponses
Avatar
Korchkidu
Bonjour,

A partir de quand estimez vous que faire une DLL plutot qu'un LIB soit
int=E9ressant. En effet, utiliser des DLL fait des programmes plus
petits (entre autres) mais induit d'enormes contraintes au niveau de la
programmation. Donc d'apres moi, faire une DLL pour une bibliotheque de
petite taille n'est pas forcement une bonne idee...

Merci pour vos commentaires.
K=2E

10 réponses

Avatar
Alain Gaillard


En effet tu lis en diagonale, car d'abord je ne vois pas en quoi cette
discussion est une engueulo.



Ne serait-ce que ton ton "je suis le best je sais tout", ça donen une vague
idée de franche camaraderie.


Tu te trompes. En tout.
James et moi discutions, nous ne nous engueulions pas. Donc tu as mal
commencé ton intervention. Il me semble qur si tu veux intervenir dans
une discussion, la moindre des choses et de lire ce qui a été écrit

C'est dommage que tu prennes ça comme ça. Tu vois des engueulo là il n'y
en a pas et tu es presque déjà en train d'ne provoquer une.



PS : garde ton ton doct


Encore une fois tu te trompes, mais bon peu importe.


--
Alain


Avatar
kanze
Alain Gaillard wrote:

Alors, comment fait-il si cette fonction fait partie d'une
DLL ? Est-ce qu'il y a une option que je n'ai pas vue ?


Je pense surtout que ce que tu n'as pas encore vu, c'est qu'un
Windows ce n'est pas un Unix. Et ce que tu t'attends à trouver
d'un Unix à un autre tu ne vas pas le trouver forcément sous
Windows.


Ça, je sais. Tout ce qui rend le développement conviviable, par
exemple, semble manquer sous Windows:-).

Mais la question n'en est pas là. Avant ce thread, je ne savais
pas comment ça se passait sous Unix non plus. En revanche, en
étudiant le code généré, j'y arrive -- en fin de compte,
Linux/g++ fait à peu près ce que j'ai fait sur 8086 il y a 25
ans, quand j'avais le même problème. Sous Windows, en revanche,
je ne vois pas trop.

Je ne voudrais pas trop faire de off-topic. Je pense que tu
devrais lire les docs Kro$oft relatives aux dll et aux édition
de liens, non pas au sens du compilateur, mais au sens de
l'OS.


Il faut que les deux collaborent. Il me semble que ma question
est assez simple. J'ai une assez bonne connaissance de comment
marche un OS, mais je ne vois pas comment elle réussit ici. Si
tu as un lien vers des documentation détaillée, je suis
intéressé.

--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34


Avatar
Alain Gaillard


N'importe quoi...

Prends un debugguer et scrute quelques applis. Ne confond pas chargé et
mappé, ne confonds pas en mémoire et sur disque. Et ne confond pas données
et code...

Eh ! Les vacances sont finies, faut se reconcentrer man :-).



Tiens au fait toi qui parle volontiers "d'engueulo, de franche
camaraderie, de docte". Que dirais tu toi même de ta propre intervention
là ? LOL

Faites ce que je dis mais ne faites pas ce que je fais, le principe
d'Arnold ?

--
Alain

Avatar
Manuel Zaccaria
kanze a écrit:

Si l'OS s'amuse à jouer avec les règistres de segmentation.


Il ne le fait pas. Il s'appuie sur le mécanisme de paging
et la mémoire virtuelle.


En fait, il s'agit toujours de la fonction :

int
f()
{
static int i = 0 ;
return i ++ ;
}

Dans le code généré par VC++, les instructions d'accès à i
utilise un offset constant (à l'encontre de ce qui se passe avec
g++ -fpei sous Linux, par exemple). Cet offset, c'est par
rapport à DS. Alors, je compile cette fonction, et je le lie
dans une DLL. Je charge la DLL dans deux processus différents,
dont les données, etc., se trouvent à des adresses différentes.
Comment fait Windows pour que ce bout de code fonctionne ? S'il
partage l'image du code entre plusieurs processus, il ne peut
pas « corriger » l'offset dans le code. Si on ne se sert pas
des pointeurs 48 bits (avec chaque fois un segment, et un
segment par DLL), il ne peut pas s'assurer que l'offset soit
identique dans chaque processus. Alors, que fait-il ? (Il doit
bien y avoir quelque chose que je ne vois pas, parce
qu'indubitablement, ça marche.)


Les adresses (virtuelles) ne sont pas concernées. Les données
statiques sont mappées dans des pages avec une politique COW.
Si un processus modifie une de ces pages, l'OS en fait une copie
et remappe l'adresse virtuelle de la page sur cette copie.

Manuel

Avatar
Alain Gaillard


g++ -fpei sous Linux, par exemple). Cet offset, c'est par
rapport à DS.


Hum, l'adressage par rapport à DS c'est du temps du 16 bits ça non ?

Alors, je compile cette fonction, et je le lie
dans une DLL. Je charge la DLL dans deux processus différents,
dont les données, etc., se trouvent à des adresses différentes.
Comment fait Windows pour que ce bout de code fonctionne ?



Poussés par la curiosité et ton insistance, j'ai fait une dll autour de
ta fonction et désassemblé, ça donne:

__declspec( dllexport ) int ma_fonction()
{
10011300 push ebp
10011301 mov ebp,esp
10011303 sub esp,0C4h
10011309 push ebx
1001130A push esi
1001130B push edi
1001130C lea edi,[ebp-0C4h]
10011312 mov ecx,31h
10011317 mov eax,0CCCCCCCCh
1001131C rep stos dword ptr es:[edi]
static int i = 0 ;
return i ++ ;
1001131E mov eax,dword ptr [i (10017168h)]
10011323 mov dword ptr [ebp-0C4h],eax
10011329 mov ecx,dword ptr [i (10017168h)]
1001132F add ecx,1
10011332 mov dword ptr [i (10017168h)],ecx
10011338 mov eax,dword ptr [ebp-0C4h]
}
1001133E pop edi
1001133F pop esi
10011340 pop ebx
10011341 mov esp,ebp
10011343 pop ebp
10011344 ret

Il me semble qu'il est clair que ta variable static est dans la zone des
2 go supérieure.
Et je suppose qu'au démarrage de l'appli le système qui lit le fichier
PE y trouve une section "données" contenant au moins ta variable static
et que lors du chargement il crée une zone en mémoire (une par process
si plusieurs utilisent la dll) contenant cette variable. Je dis je
suppose parce que pour être certain à 100% il faudrait examiner le
fichier binaire PE, mais je ne pense pas me tromper toutefois.
Quand l'appli va lire la valeur elle le fait avec un pointeur qui vaut
10017198h dans l'exemple. Mais cette adresse est toute virtuelle. Quand
elle est lue le système fait ce qu'il faut pour que la variable
appartenant au process soit lue effectivement en mémoire physique.
Et chaque process à sa propre copie de la variable static i bien entendu.

En fait je ne comprends pas bien ce qui te défrise tant. On dirait que
tu raisonnes comme si l'adressage était à l'ancienne en 16 bits
(registre DS...) à moins que je ne comprenne pas ta question.

--
Alain

Avatar
Vincent Burel
"kanze" wrote in message
news:
Vincent Burel wrote:
"kanze" wrote in message
news:
Vincent Burel wrote:
"James Kanze" wrote in message
news:
Alain Gaillard wrote:
ben c'est à dire que chaque "task" s'exécute dans un

environnement mémoire propre, et en fonction d'une table de
mappage mem-phy <-> mem logique spécique. Quand le programme
fait appel à une adresse 32 bit, c'est toujours dans un
contexte mémoire donné...

Certes, mais qu'est-ce que ça change ? Je parle bien ici de
l'adresse dans l'espace d'adressage d'un processus. Le
compilateur génère une adresse constante (par rapport à DS, en
tout cas).


ben les différentes hinstance de la DLL travaille avec les mêmes adresses 32
bit (logiques), mais chaque appli à ses 6 segments sauvé dans sont swith
context, et chaque segements décrivent des zones différentes...

Après, vous etes sur que le compilo est capable d'user d'adresse vraiment
constante ? genre un nombre codé en dur dans le code ASM ? ca me parait
bizarre !

Tout ça c'est bien beau, même si j'imagine mal quelqu'un qui ne
le connais pas.


ha oui c'est vrai que tout le monde connais ca sur le bout des doigt !
d'ailleurs ce thread en ai la preuve :-)

Et je constate également qu'à l'intérieur d'un processus, on ne touche
normalement pas à DS -- si la fonction renvoie l'adresse, par exemple, il

renvoie bien l'offset, sans renseigner sur le segment

une appli user, ne gère par d'adresse seg:offset, il n'utilise que l'offset.

Seulement, c'est impossible que l'adresse logique soit
la même dans tous les processus.


pourquoi ? justement le mappage permet ca !

VB


Avatar
kanze
Alain Gaillard wrote:

Je sais que le 80386 introduisait un mechanisme pour
supporter ce genre de chose -- un appel FAR pouvait passer
par un espèce de portail qui faisait qu'on ne pourrait
arriver qu'à des adresses bien définies, mais qu'on ce
faisant, on passait en mode super-user.


Encore ce terme de FAR.... :-S


C'est vrai que ça date:-). Mais la dernière fois que j'ai
travaillé professionnellement sur l'architecture Intel, le
processeur était un 80386, et l'OS MS-DOS 3.2. Alors, je ne
prétendrais pas être au courant des dernières nouveautés.

Mais globalement c'ets comme ce que tu décris.


Je vois qu'il va falloir que j'apprenne faire une DLL sous
Windows:-). Parce que j'ai toujours du mal à voir comment ça se
passe. Mettons qu'à la place de ma fonction avant, j'ai une
fonction :

int*
f()
{
static int i ;
return &i ;
}

Cette fonction se trouve linkée dans une DLL. L'adresse (c-à-d
l'offset par rapport à DS) dans le code généré par le
compilateur ne fait entrer en jeu aucun régistre ou d'autre
aspect variable -- ce que renvoie la fonction, c'est bien une
constante qui fait partie variable (régistre, etc.). Je suppose
donc que si j'appelle cette fonction depuis la partie principale
(non-DLL) de mon programme, j'ai toujours la même valeur,
quelque soit le processus. Et c'est ça que je ne comprends
pas : comment est-ce que je pourrais avoir toujours la même
valeur ? Qu'est-ce qui se passe, par exemple, si mon code a
déjà quelque chose d'autre (mettons d'une autre DLL) à cette
adresse ?

J'ai fait quelques essais rapides ; j'arrive assez facilement à
un point où j'ai deux processus qui charge la DLL avec le code
ci-dessus, et que dans un, la fonction renvoie 0x100217C0, et
dans l'autre 0x))002517C0. Alors que l'assembleur de la fonction
est simplement :
mov eax, OFFSET blah...
(avec un nom assez compliqué).

J'ajoute des cout<< de l'adresse dans la fonction, et j'ai aussi
la différence. Alors que le code généré (selon l'assembleur
généré avec /Fa), j'ai toujours une instruction qui renvoie une
constante. Ce qui signifierait que les images du code ne sont
pas partagés entre les processus, ou au moins non toujours (si
le système n'arrive pas à mettre la variable à l'adresse qu'il a
dans l'image déjà présent). Ou que l'assembleur que sort cl ne
correspond pas à ce qui est vraiment généré -- c'est une
possibilité qu'on ne peut pas négliger non plus.

règistre, et puis d'utiliser un « trap » -- sur une 80x86,
une instruction INT -- qui passe par une table
d'interruptions (que le code utilisateur ne peut pas
modifier, au moins en mode protégée du processeur).


Certes mais sous Windows ce n'est pa scomme ça.


Ça l'était sous MS-DOS. Ça l'est à peu près partout ailleurs.
Pourquoi est-ce que Windows ferait quelque chose de différent ?

machine. Même aujourd'hui, je vois mal un système qui mappe
l'OS et l'application utilisateur dans le même espace
virtuel.


Tu le vois mal ? Alors change de lunettes, puisque c'est comme
ça :)


Disons que ça ne me semble pas particulièrement intelligent. Ça
restreint les applications utilisateur d'une façon non
nécessaire.

J'avais oublié le :-). L'idée qu'un processus utilisateur ne
peut se servir de 2 Go, à la place de 4,


A mais je n'ai jamais dis ça. Faut lire....
J'ai dit:

4go adressables pour un processus.

Le code est dans les 2 go inférieurs. Pour être plus complet,
tout en bas de cette zone, il y a un cache (PATH, clé de
registre, etc...) qui est réservé par le système, dans le sens
où l'appli ne peut pas écrire dedans Les tables de saut sont
dans les é Go supérieurs. Ainsi que les données, ça je ne l'ai
aps précisé.

En gros, une appli dispose d'environ 3 go vraiment pour elle.


Ce n'est toujours pas les 4Go (moins des poussières) dont j'ai
l'habitude sous Linux. (Sous Solaris sur Sparc, évidemment,
je n'ai pas assez de mémoire virtuelle pour voir s'il y a
d'autres limitations.)

sur un processeur moderne 32 bits avec mémoire virtuelle, me
faisait en fait penser au commentaire « 564 [ou quelque
chose comme ça] mémoire serait assez grande pour n'importe
qui », attribué à Bill Gates (faussement, je crois).


Je ne sais pas. Mais je sais qu'il a dit "Si vous ne pouvez
pas le faire bien, faites le beau" LOL


Sauf qu'en fait, on sait faire bien. Ce n'est pas comme si
c'était une technologie nouvelle ou inconnue.

--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34


Avatar
Jean-Marc Bourguet
Je préviens (surtout qu'il y a un crosspost où peu de gens
doivent me connaîre) que je ne connais rien à Windows.

Alain Gaillard writes:



g++ -fpei sous Linux, par exemple). Cet offset, c'est par
rapport à DS.



Je suppose qu'il s'agit de -fpic et pas -fpei.

Hum, l'adressage par rapport à DS c'est du temps du 16 bits ça non ?

Alors, je compile cette fonction, et je le lie
dans une DLL. Je charge la DLL dans deux processus différents,
dont les données, etc., se trouvent à des adresses différentes.
Comment fait Windows pour que ce bout de code fonctionne ?



Poussés par la curiosité et ton insistance, j'ai fait une dll autour de ta
fonction et désassemblé, ça donne:

__declspec( dllexport ) int ma_fonction()
{
10011300 push ebp
10011301 mov ebp,esp
10011303 sub esp,0C4h
10011309 push ebx
1001130A push esi
1001130B push edi
1001130C lea edi,[ebp-0C4h]
10011312 mov ecx,31h
10011317 mov eax,0CCCCCCCCh
1001131C rep stos dword ptr es:[edi]
static int i = 0 ;
return i ++ ;
1001131E mov eax,dword ptr [i (10017168h)]
10011323 mov dword ptr [ebp-0C4h],eax
10011329 mov ecx,dword ptr [i (10017168h)]
1001132F add ecx,1
10011332 mov dword ptr [i (10017168h)],ecx
10011338 mov eax,dword ptr [ebp-0C4h]
}
1001133E pop edi
1001133F pop esi
10011340 pop ebx
10011341 mov esp,ebp
10011343 pop ebp
10011344 ret

Il me semble qu'il est clair que ta variable static est dans la zone des 2
go supérieure.
Et je suppose qu'au démarrage de l'appli le système qui lit le fichier PE y
trouve une section "données" contenant au moins ta variable static et que
lors du chargement il crée une zone en mémoire (une par process si
plusieurs utilisent la dll) contenant cette variable. Je dis je suppose
parce que pour être certain à 100% il faudrait examiner le fichier binaire
PE, mais je ne pense pas me tromper toutefois.
Quand l'appli va lire la valeur elle le fait avec un pointeur qui vaut
10017198h dans l'exemple. Mais cette adresse est toute virtuelle. Quand
elle est lue le système fait ce qu'il faut pour que la variable appartenant
au process soit lue effectivement en mémoire physique.
Et chaque process à sa propre copie de la variable static i bien entendu.

En fait je ne comprends pas bien ce qui te défrise
tant.


Je crains que tu ne comprennes pas la question.

Cela fait longtemps que j'ai fait de l'assembleur avec la
syntaxe MS -- mais il me semble que

mov eax,dword ptr [i (10017168h)]

c'est de l'adressage absolu: donc le code sait qu'i est à
l'adresse 10017168h et pas ailleurs. Il faut donc que i ait
la même adresse dans tous les process si le code est
réellement partagé. Comment Windows fait pour s'assurer que
toutes les DLL sont bien mappées pour où elles ont été
compilées?

Soit le code n'est pas partagé et Windows fait de la
relocation au chargment (ce que fait Solaris quand un .so
n'est pas compilé en PIC -- on pourrait faire de la
relocation "intelligente" et essayer de charger la lib
généralement au même endroit, je doute que ce soit fait sous
Solaris car le chargeur est un exécutable sans droits
supplémentaires et donc sans influence sur les autres
processus), soit il s'arrange pour que 10017168h désigne des
adresses différentes suivant les endroits où il est appelé,
et la seule façon que je vois, c'est de modifier la base
indiquée pas DS.

On dirait que tu raisonnes comme si l'adressage était à
l'ancienne en 16 bits (registre DS...) à moins que je ne
comprenne pas ta question.


Les segments sont toujours sur un x86 (enfin, plus en mode
64 bits si j'ai bien suivi), simplement on ne les utilise
pas beaucoup dans les OS répandus. Mais il y a des
utilisations, par exemple je crois savoir que Windows les
utilise aussi pour "pointer" vers les variables locales à
des threads et les utiliser pour les DLL, ça ne me semble
pas stupide étant donné que les x86 sont quand même mal
foutus quand on veut écrire du code PIC.

A+

--
Jean-Marc
FAQ de fclc++: http://www.cmla.ens-cachan.fr/~dosreis/C++/FAQ
C++ FAQ Lite en VF: http://www.ifrance.com/jlecomte/c++/c++-faq-lite/index.html
Site de usenet-fr: http://www.usenet-fr.news.eu.org


Avatar
Alain Gaillard


C'est vrai que ça date:-). Mais la dernière fois que j'ai
travaillé professionnellement sur l'architecture Intel, le
processeur était un 80386, et l'OS MS-DOS 3.2.


LOL


Cette fonction se trouve linkée dans une DLL. L'adresse (c-à-d
l'offset par rapport à DS)


Non non, pas en Windows 32 bits

dans le code généré par le
compilateur ne fait entrer en jeu aucun régistre ou d'autre
aspect variable -- ce que renvoie la fonction, c'est bien une
constante qui fait partie variable (régistre, etc.). Je suppose
donc que si j'appelle cette fonction depuis la partie principale
(non-DLL) de mon programme, j'ai toujours la même valeur,
quelque soit le processus. Et c'est ça que je ne comprends
pas : comment est-ce que je pourrais avoir toujours la même
valeur ? Qu'est-ce qui se passe, par exemple, si mon code a
déjà quelque chose d'autre (mettons d'une autre DLL) à cette
adresse ?


Parce que pour chaque processus c'est la même adresse *virtuelle* mais
cette adresse désigne un emplacement en mémoire physique différent pour
chaque processus.


Ça l'était sous MS-DOS. Ça l'est à peu près partout ailleurs.
Pourquoi est-ce que Windows ferait quelque chose de différent ?


Pourquoi ... faut leur demander. Mais ils l'ont fait :-)


Moi aussi j'ai désassemblé un bout de dll à ton intention dans un autre
post.


--
Alain

Avatar
Jean-Marc Bourguet
Alain Gaillard writes:

Cette fonction se trouve linkée dans une DLL. L'adresse (c-à-d
l'offset par rapport à DS)


Non non, pas en Windows 32 bits


Tu peux m'expliquer comment travailler avec un x86 en mode
32 bits sans que les adresses soient des déplacements par
rapport à des segments? (Ces segments généralement ont pour
base l'adresse 0 et une limite de 4GB, je veux bien mais ça
ne change rien, s'il n'y pas d'indication explicite de
segment, c'est DS qui est utilisé sauf quand on utilise des
déplacements par raport à (E)BP et (E)SP).

Parce que pour chaque processus c'est la même adresse
*virtuelle* mais cette adresse désigne un emplacement en
mémoire physique différent pour chaque processus.


Je crois que ce point ne dérange personne. Le problème
c'est comment s'arranger pour qu'il n'y ait pas de conflits
entre DLL et comment résoudre ceux-ci.

A+

--
Jean-Marc
FAQ de fclc++: http://www.cmla.ens-cachan.fr/~dosreis/C++/FAQ
C++ FAQ Lite en VF: http://www.ifrance.com/jlecomte/c++/c++-faq-lite/index.html
Site de usenet-fr: http://www.usenet-fr.news.eu.org