Question [Function]Création dynamique de structure de donné

Plus d'informations
il y a 15 ans 5 mois #3186 par Laurent Dardenne
Création dynamique de structure de données, classe C# de type struct.
Evolution d'un script issu du site Poshcode
[code:1]
#note :
# Les compilateurs C#, Visual Basic .NET et C++ appliquent par défaut
# la valeur de disposition Sequential aux structures, c'est à dire :
# [StructLayout(LayoutKind.Sequential)]
# Struct MonNom {....}
#
# Cette information précise comment sont marshallé les données et pas comment elles sont stockées.
# L'ordre de déclaration des champs, dans une structure, n'est donc pas important dans ce cas,
#
# #Définition PowerShell de la structure
# Disque=@{
# Nom=[String]
# Partitions=\"Partition[]\"
# }
# sauf pour la déclaration du constructeur pour qui l'ordre peut ne pas correspondre :
#
# Définition C# du code de la structure Disque
# public struct Disque {
# public Partition[] Partitions;
# public System.String Nom;
#
# public Disque (Partition[] partitions,System.String nom) {
# Partitions = partitions;
# Nom = nom;
# }
#
# #Affichage du constructeur via Get-Constructeur
# Disque(
# Partition[] partitions,
# String nom,
# )
# Ceci est du au fait qu'une hashtable n'est pas ordonnée.
#

# Si on souhaite utiliser la version originale on ne peut pas créer de structures utilisant d'autres structures,
# à moins de passer par un fichier DLL externe tout en modifiant le script d'origine
# Dans ce cas le nom de l'assembly doit avoir une extension .DLL et son chemin pointer de préférence sur %Temp%.
#

## New-Struct
## poshcode.org/190
## Auteurs : Joel Bennett
## Creates a Struct class and emits it into memory
## The Struct includes a constructor which takes the parameters in order...
##
## Usage:
## # Assuming you have a csv file with no header and columns: artist,name,length
## New-Struct Song @{
## Artist=[string];
## Name=[string];
## Length=[TimeSpan];
## }
## $songs = gc C:\Scripts\songlist.csv | % { new-object Song @($_ -split \",\"«») }
##
function New-Struct {
param([HashTable]$Structs)

switch($Structs.Keys){{$_ -isnot [String]}{throw \"Invalid Syntax.\"}}
switch($Structs.Values){{$_ -isnot [hashtable]}{throw \"Invalid Syntax.\"}}
#switch($Structs.Values){{$_ -isnot [type]}{throw \"Invalid Syntax.\"}}


# CODE GENERATION MAGIKS!
$code = @\"
using System;
$($Structs.Keys|% {$Name=$_;$Properties=$Structs.$_; \"`n public struct $Name {`r`n\"
$($Properties.Keys | % { \" public {0} {1};`n\" -f $Properties[$_],($_.ToUpper()[0] + $_.SubString(1)) })
\"`n public $Name (\"+$( [String]::join(',',($Properties.Keys | % { \"{0} {1}\" -f $Properties[$_],($_.ToLower()) })) )+\"«») {`r`n\"
$($Properties.Keys | % { \" {0} = {1};`n\" -f ($_.ToUpper()[0] + $_.SubString(1)),($_.ToLower()) })
\"`n }`n }\"
})
\"@

## Obtains an ICodeCompiler from a CodeDomProvider class.
$provider = New-Object Microsoft.CSharp.CSharpCodeProvider
## Get the location for System.Management.Automation DLL
$dllName = [PsObject].Assembly.Location
## Configure the compiler parameters
$compilerParameters = New-Object System.CodeDom.Compiler.CompilerParameters
$assemblies = @(\"System.dll\", $dllName)
$compilerParameters.ReferencedAssemblies.AddRange($assemblies)
$compilerParameters.IncludeDebugInformation = $true
$compilerParameters.GenerateInMemory = $true

$compilerResults = $provider.CompileAssemblyFromSource($compilerParameters, $code)
if($compilerResults.Errors.Count -gt 0) {
$compilerResults.Errors | % { Write-Error (\"{0} :`t {1}\" -F $_.Line,$_.ErrorText) }
}
}
[/code:1]
Voici quelques exemples d'utilisation,
d'abord les erreurs possibles :
[code:1]
#Erreur : les noms de membres doivent être différents de leur type englobant
$Structs=@{D=@{Artist=[string]};
A=@{Numero=[Int32];A=\"D\"}}
New-Struct $Structs

#type ou le nom d'espace de noms 'A' est introuvable (une directive using ou une référence d'assembly est-elle manquante ?)
$Structs=@{D=@{Artist=[string]};
E=@{Numero=[Int32];Album=\"A\"}}
New-Struct $Structs
#En C# les noms d'identificateur sont sensibles à la casse.
#Cf. Microsoft.CSharp.CSharpCodeProvider
$Structs=@{D=@{Artist=[string]};
E=@{Numero=[Int32];Album=\"d\"}}
New-Struct $Structs

#Album est un 'type' mais est utilisé comme une 'variable'
$Structs=@{X=@{Artist=[string]};
Y=@{Numero=[Int32];Y=[Int32]}}
New-Struct $Structs
[/code:1]
Puis la création de structure correcte syntaxtiquement
[code:1]
#2 structures \"basic\"
New-Struct @{Song=@{Artist=[string]};
Album=@{Numero=[Int32];titre=[String]}}
$r=new-object song
$t=new-object Album
$r
$t

#Structures imbriquées
#Le type A ne peut être utilisé comme type, [A] puisqu'il n'existe pas encore
#sous Powershell mais il existera lors de la compilation via CodeDOM
$Structs=@{A=@{Artist=[string]};B=@{Numero=[Int32];Album=\"A\"}}
New-Struct $Structs
$a=new-object A
$b=new-object b
$a
$b
$b.Album
[/code:1]
On creuse le sujet :
[code:1]
#Structures imbriquées utilisées par certaines API Win32
$R=@{
Rect=@{
Left=[UInt32];
Top=[UInt32];
Right=[UInt32];
Bottom=[UInt32];
};
Windowinfo=@{
cbSize=[UInt32];
rcWindow=\"Rect\";
rcClient=\"Rect\";
dwStyle=[UInt32];
dwExStyle=[UInt32];
dwWindowStatus=[UInt32];
cxWindowBorders=[UInt16];
cyWindowBorders=[UInt16];
atomWindowType=[UInt16];
wCreatorVersion=[UInt16];
}
}
New-Struct $R
$Wi=new-object WindowInfo
$Wi
$Wi.RcClient

#Structures imbriquées comportant un champ de type tableau de structure
$DisqueInfo=@{
Partition=@{
Type=[Int32];
Taille=[Long];
};
Disque=@{
Nom=[String]
Partitions=\"Partition[]\"
}
}
New-Struct $DisqueInfo

#Affichage de la listes des constructeurs d'une classe
# rappel sous .NET tout est classe.
function get-Constructor ([type]$type, [Switch]$FullName)
{ #from Jeffrey Snover :
#http://blogs.msdn.com/powershell/archive/2008/09/01/get-constructor-fun.aspx
foreach ($c in $type.GetConstructors())
{
$type.Name + \"(\"
foreach ($p in $c.GetParameters())
{
if ($fullName)
{
\"`t{0} {1},\" -f $p.ParameterType.FullName, $p.Name
}else
{
\"`t{0} {1},\" -f $p.ParameterType.Name, $p.Name
}
}
\"«»)\"
}
}
Get-Constructor Disque
#L'affectation de $null reste possible lors de la création
$D=new-object Disque $null,\"C\"
#Constructeur par défaut, tous les champs sont à null
$D=new-object Disque

Get-Constructor Partition
#Créations imbriquées
$D2=new-object Disque @( (new-object Partition 20Gb,1),(new-object Partition 10Gb,1) ),\"C\"
$d2
$D2.Partitions
$D2.Partitions[0].Taille
[/code:1]
Les limites de la solution :
[code:1]
#Redéfinition,dans la même session, d'une structure existante en mémoire
$DisqueInfo=@{
Partition=@{
Taille=[Long];
};
Disque=@{
Numero=[Int32]
Partitions=\"Partition[]\"
}
}
New-Struct $DisqueInfo
#L'appel de new-Struct ne pose pas de pb mais le code n'est pas remplacé
#car la nouvelle structure est créée dans une DLL différente de la premiére, de plus une fois
# une DLL chargée dans un domaine d'application .NET, on ne peut plus la décharger !
Get-Constructor Disque
#L'appel suivant échoue car l'appel pointe sur le constructeur de la première déclaration
$D2=new-object Disque @( (new-object Partition 20Gb),(new-object Partition 10Gb) ),1
$D=new-object Disque $null,\"C\"

#On affiche toutes les classes créées à la volée
#Récupère du domaine d'application courant, session PowerShell, tous les assemblies chargés
[appdomain]::currentdomain.GetAssemblies()

[appdomain]::currentdomain.GetAssemblies()|`
#Le code compilé à la volée ne défini pas de numéro de version
#Filtre les assemblies compilés dynamiquement
? {$Asm=$_.GetName(); $Asm.version.tostring() -eq \"0.0.0.0\"}|`
# Affiche le nom de l'assembly, généré automatiquement,
# et le nom des types qu'il contient
% {$_.GetTypes()|Group {$asm.Name}}
[/code:1]
Pour différencier les assemblies il faut compiler les structs dans un espace de nom :
[code:1]
$code = @\"
using System;
namespace $MonNom {
## ICI LE CODE EST IDENTIQUE ##
\"`n }`n }`n }\"
})
\"@
[/code:1]
Ensuite on accédera aux types par :
[code:1]
$D=new-object NOM1.Disque $null,\"C\"
$D2=new-object NOM2.Disque $null,\"C\"
[/code:1]
Pour la création de DLL ajoutez dans le code les lignes suivantes et les paramètres adéquates dans l'entête de la fonction :
[code:1]
$assemblies = @(\"System.dll\", $dllName)
# Add Referenced Assemblies for the type of a member of a struct
if ($references.Count -ge 1)
{
$assemblies +=$References
}
...
$compilerParameters.GenerateInMemory = $true
#management for assemblies in-memory
if ($MyAssemblyName -ne [String]::Empty)
{$compilerParameters.OutputAssembly = $MyAssemblyName}
[/code:1]
PowerShell ? Ouai c'est pas mal.
:lol:<br><br>Message édité par: Laurent Dardenne, à: 23/12/08 17:03

Tutoriels PowerShell

Connexion ou Créer un compte pour participer à la conversation.

Temps de génération de la page : 0.072 secondes
Propulsé par Kunena