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
Manuel Zaccaria
Loïc Joly a écrit:

C'est ensuite le système qui fait tout. Quand un exécutable est chargé,
ces sections sont analysées.


Est-ce que le mécanisme est identique pour les DLL chargées dynamiquement
?


Je pense que non. Il me semble qu'a ce moment, c'est pendant les appels à
GetProcAddress que le mapping se réalise. Je me trompe peut-être...


Manuel


Avatar
James Kanze
Alain Gaillard wrote:

[...]
Reste à savoir comment il s'arrange pour rétrouver ces
données sans imposer qu'il soit liées à la même adresse dans
dans tous les processus.. Comment fait VC++ pour résoudre ce
problème.


VC++ ne fait rien. C'est le système qui le fait.
Dans l'exécutable qui est un format baptisé PE et documenté par Kro $oft.
Il y des des sections décrivant les dépendances. Soit telles dlls et
telles fonctions. L'éditeur de liens (disosn VC globalement) crée ces
sectiosn et son rôle s'arrête là
C'est ensuite le système qui fait tout. Quand un exécutable est charg é,
ces sections sont analysées. Si une dll requise est déjà chargée, comme
c'est toujours le cas de kernel32 par exemple, la table de sauts est
construite. Sinon la dll est chargée puis la table de saut est
construite. Cette dernière dll ne sera pas chargée si une deuxième
application s'en sert. Seule une table de saut est construite dans les 2
Go supérieurs de l'appli.


Le problème n'est pas vraiment pour les fonctions. Je reconnais
bien qu'un niveau d'indirection supplémentaire pourrait faire
l'affaire (et encore, il y a des cas où ça ne doit pas être
évident). Le problème, c'est les données statiques. Si j'écris
quelque chose du genre :

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

le code généré par VC++ (ou par g++, si je ne donne pas l'option
-fpei) utilise un adresse absolue pour i, quelque chose du
genre :

mov eax, mangledNameOfi
incr mangledNameOfi

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 ? (Avec
g++, si je précise -fpei, il génère plutôt quelque chose du
genre :

call getLocalMemoryBlock
mov eax, mangledNameOfi[ ebx ]
incr mangledNameOfi[ ebx ]

Et getLocalMemoryBlock utilise une adresse de retour pour
trouver où est mappée l'objet -- les adresses des sauts chez
Intel sont toujours rélatives, et donc indépendantes de
l'adresse absolue où le code est mappée, mais les adresses des
données non.)

Tout ce mécanisme je le connais très très bien. A une époque
j'avais écrit un outil pour modifier les table de saut et
garder une trace des appel à certaines fonctions des dll
systèmes dans un log. Et puis tout ce que je dis est documenté
par Kro$oft. Chacun peut vérifer.


Je ne connais pas trop ce qui a fait Microsoft, mais je me
rappelle avoir eu à écrire du code indépendante de la position
pour charger sous CP/M86, aux alentours de 1982. Et qu'il m'a
fallu effectivement tout une cirque pour trouver l'adresse des
données (alors que les sauts et les appels de fonction
marchaient d'office).

C'est d'ailleurs le coût en temps d'exécution et complexité que
ceci impose qui me faisait supposer que quand on parlait d'une
édition de liens dynamique, que le chargeur/éditeur de liens
faisait bien ce que faisait un éditeur de liens normal, et qu'il
« corriger » toutes ces adresses. Ce qui voudrait dire,
évidemment, qu'un partage de l'image n'est pas possible.

--
James Kanze (Gabi Software) email:
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
James Kanze
Alain Gaillard wrote:

J'aurais imaginé que ces DLL ne contient que le mapping de
l'API C aux véritables appels systèmes (qui n'était pas des
call dans le temps, parce qu'il fallait passer en mode
superuser, masquer les interruptions, etc.).


Le mécanisme de la table de saut est complexe. Il y a
effectivment un basculement en mode superuser lors des appels
systèmes. II y a table de saut, mais l'adresse où on saute est
telle que le basculement se produit. C'est un peu compliéqué à
expliquer, mais encore une fois c'ets documentée chez kro$oft


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. En revanche, la plupart des systèmes que j'ai
vu ont choisi l'option de faire comme sur tous les autres
processeurs, de mettre les paramètres de l'appel dans un
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).

Je n'ai pas eu l'occasion de régarder ces aspects depuis bien du
temps, mais je sais que dans Unix version 7 sur PDP-11, une
fonction « système » comme open, dans la bibliothèque, ne
comportait qu'un maximum de cinq ou de six instructions
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.

Le mécanisme de gestion de mémoire virtuelle est assez
particulier. En quelques mots disons que toute application
se voit attribuer les 4 Go adressables, mais toute cette
zone est une zone de mémoire virtuelle, pas physique
évidemment. Les 2 go de mémoire basse sont à son usage.
Dans les 2 Go de mémoire haute sont installée les tables de
saut vers les dll utilisées par l'application, comme par
exemple kernel32.dll, user32.dll, madll.dll. Je parle bien
ici de mémoire virtuelle.


Il lui faut 2 Go pour ça ?


Non je n'ai pas dit ça.


J'avais oublié le :-). L'idée qu'un processus utilisateur ne
peut se servir de 2 Go, à la place de 4, 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). J'ai déjà vu des applications Solaris 32
bits qui se servait de plus.

Il lui faut les octets *physiques* qu'il lui faut.
Mais chaque sous Windows chaque appli se voit octroyer tout l'espace
d'adressage systèmatiquement mais virtuellement. Sois 4 Go en 32 bits.
Et les tables de sauts sont installées dans les 2 Go supérieurs,
justement parce qu'être là participe au mécanisme de basculement
mentionné plus haut.

Pour les sauts dans le code, je comprends bien comment ça
marche, bien que j'ai du mal à croire que tout le monde soit
d'accord pour le prix de l'indirection supplémentaire.


Microsoft ne te demand pas d'être d'accord. c'ets comme ça et
c'est tout.


Je ne pensais pas forcément à Microsoft. J'ai vue que g++ et Sun
CC font pareil. Et il y a des domaines d'application où la
performance pûre est importants, même si aujourd'hui, on ne se
sert pas de Windows pour de telles applications.

Je ne trouve pas d'option pareil dans VC++


Mon bon James, tu manques d'expérience dans le domaine ;) Il
n'y a pas d'option dans VC++ pour la bonne raison que ce n'est
pas son rôle. Lis mon autre poste dans cette discussion.


Alors, comme fait-il ? Le problème reste que dans un code ultra
simple :

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

le compilateur génère une adresse absolue pour i. Maintenant, je
connais bien une solution, qu'on passant par l'aiguillage, on
charge un autre valeur dans DS, et qu'on le réstaure en
revenant. Mais alors, il faudrait passer des pointeurs FAR
partout, ce que ne fait pas VC++ non plus.

J'avoue que je suis curieux en ce qui concerne la solution. Je
en ai implémenté une, il y a longtemps, mais il suppose ne pas
utiliser des adresses absolues ; que les références à i, dans
le code ci-dessus, passent toutes par un règistre de base (EBX,
par exemple). C'est ce que fait g++ sous Linux (et c'est
pourquoi il lui faut une option de compilateur). Maintenant, mon
expérience dans ce domaine date énormement ; il se peut que
depuis, Intel a ajouté quelque chose que je ne connais pas. Mais
alors, pourquoi est-ce que g++ sous Linux ne s'en sert pas ?

--
James Kanze (Gabi Software) email:
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
Arnold McDonald \(AMcD\)
James Kanze wrote:
L'idée qu'un processus utilisateur ne
peut se servir de 2 Go, à la place de 4, 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). J'ai déjà vu des applications Solaris 32
bits qui se servait de plus.


Je lis en diagonale votre engueulo là, je n'ai malheureusement pas le temps
d'y mettre mon gain de sel. Mais, tout de même, j'y vois écrit quelques
inexactitudes.

Déjà, ce n'est pas le processeur qui possède la mémoire virtuelle ("un
processeur moderne 32 bits avec mémoire virtuelle"), au mieux, il est
capable de la gérer. Mais ce n'est même pas obligatoire. Un processeur
possède plusieurs modes de fonctionnement.

Ensuite, ça fait quand même quelques années déjà qu'un processus peut avoir
plus de 2Go. En 1999, on lisait déjà dans le MSDN :

"With Windows NT Server 4.0, Enterprise Edition, and Windows 2000 Advanced
Server, 32-bit x86-based systems can provide applications with a 3 GB flat
virtual address space, with the kernel and executive using only 1 GB."

Il suffit de regarder du côté de la PAE (Physical Address Extension) dans le
MSDN :

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/memory/base/physical_address_extension.asp

En utilisant AWE (Address Windowing Extensions API), tu peux même aller
jusqu'à 64 Go si ça t'amuse :

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/memory/base/address_windowing_extensions.asp

--
Arnold McDonald (AMcD) - Help #52/2006

http://arnold.mcdonald.free.fr/

Avatar
Vincent Burel
"James Kanze" wrote in message
news:
Alain Gaillard wrote:

[...]
Reste à savoir comment il s'arrange pour rétrouver ces
données sans imposer qu'il soit liées à la même adresse dans
dans tous les processus.. Comment fait VC++ pour résoudre ce
problème.


VC++ ne fait rien. C'est le système qui le fait.
Dans l'exécutable qui est un format baptisé PE et documenté par Kro$oft.
Il y des des sections décrivant les dépendances. Soit telles dlls et
telles fonctions. L'éditeur de liens (disosn VC globalement) crée ces
sectiosn et son rôle s'arrête là
C'est ensuite le système qui fait tout. Quand un exécutable est chargé,
ces sections sont analysées. Si une dll requise est déjà chargée, comme
c'est toujours le cas de kernel32 par exemple, la table de sauts est
construite. Sinon la dll est chargée puis la table de saut est
construite. Cette dernière dll ne sera pas chargée si une deuxième
application s'en sert. Seule une table de saut est construite dans les 2
Go supérieurs de l'appli.


Le problème n'est pas vraiment pour les fonctions. Je reconnais
bien qu'un niveau d'indirection supplémentaire pourrait faire
l'affaire (et encore, il y a des cas où ça ne doit pas être
évident). Le problème, c'est les données statiques.

Non, parce qu'une DLL (ou un exe) ne gère que des adresse 32 bit (qui sont
que des offset en fait), le système lui gère aussi les segments, et peut
faire en sorte qu'un code s'éxécute avec le bon segment de données (et bien
sur le bon segement de pile etc...).

VB


Avatar
kanze
Vincent Burel wrote:
"James Kanze" wrote in message
news:
Alain Gaillard wrote:

[...]
Le problème n'est pas vraiment pour les fonctions. Je reconnais
bien qu'un niveau d'indirection supplémentaire pourrait faire
l'affaire (et encore, il y a des cas où ça ne doit pas être
évident). Le problème, c'est les données statiques.

Non, parce qu'une DLL (ou un exe) ne gère que des adresse 32
bit (qui sont que des offset en fait), le système lui gère
aussi les segments, et peut faire en sorte qu'un code
s'éxécute avec le bon segment de données (et bien sur le bon
segement de pile etc...).


Mais alors, il faudrait des pointeurs FAR, à 48 bits, ou ?

--
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

C'est ensuite le système qui fait tout. Quand un exécutable est
chargé, ces sections sont analysées.



Est-ce que le mécanisme est identique pour les DLL chargées dynamiquement ?



Tout à fait.
D'ailleurs sous Windows il est plus approprié de parler d'édition de
liens immédiate ou retardée (LoadLibrary/GetProcAddress)


--
Alain


Avatar
Alain Gaillard

Je pense que non. Il me semble qu'a ce moment, c'est pendant les appels à
GetProcAddress que le mapping se réalise. Je me trompe peut-être...


En effet tu te trompes, la table de sauts est constituée à l'appel de
LoadLibrary. GetProcAddress ne fait que retourner une e addresse dans la
table.

--
Alain

Avatar
Alain Gaillard

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.

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.

--
Alain

Avatar
Alain Gaillard


Mais alors, il faudrait des pointeurs FAR, à 48 bits, ou ?


??

--
Alain