Hack super moche dans une catégorie

psychoh13psychoh13 Mothership DeveloperMembre
décembre 2007 modifié dans Vos applications #1
Bonjour à  tous,

J'ai un petit problème spécifique qui arrive à  la compilation et qui est vachement emmerdant, alors je prie pour qu'il y ait quelqu'un qui l'ait déjà  eu et qui a réussit à  le corriger sur ce forum.

Voilà  mon problème vient d'une méthode définie dans une catégorie de la classe NSAffineTransform. La méthode permet juste de faire une rotation autour d'un point donné et non plus autour du point d'origine du repère.
Pour pouvoir faire cette méthode, je dois accéder puis modifier l'unique variable d'instance de cette classe qui est de type NSAffineTransformStruct. Le problème c'est que lorsque je tente d'y accéder, mon compilateur me renvoie une erreur de résolution de symbole :
  "_OBJC_IVAR_$_NSAffineTransform._transformStruct", referenced from:
      -[NSAffineTransform(PSYRotationAdditions) rotateByRadians:withCenterPoint:] in NSAffineTransform_PSYAdditions.o
ld: symbol(s) not found
collect2: ld returned 1 exit status


Je précise que ma méthode se trouve dans un Framework Cocoa pour Leopard que je développe.
Donc, comme l'indique ce message d'erreur, je n'arrive pas à  utiliser directement la variable d'instance alors qu'ils s'agit d'une catégorie (théoriquement j'ai donc accès à  toutes les variables d'instance même les @private).
Le plus troublant c'est que plus tôt dans la journée, ce système marchait très bien, je n'avais rien modifié entre temps et tout à  coup j'ai eu ce message.

Alors, temporairement j'ai recours à  un hack super moche et pas vraiment pérenne :
NSAffineTransformStruct *transformStruct = (NSAffineTransformStruct*)((id)self + 1);


Ce que je voudrais savoir, c'est si vous avez une solution à  mon problème et qui me permettrait d'utiliser le comportement normal ?

Merci beaucoup.

Réponses

  • AliGatorAliGator Membre, Modérateur
    décembre 2007 modifié #2
    Heu dis-moi, pour accéder à  la transformStruct, tu utilises bien la méthode [tt]NSAffineTransformStruct tr = [self transformStruct];[/tt] non ? Plutôt que d'y accéder directement comme variable d'instance ? (doc)

    Sinon, certes c'est moins optimisé que de faire le calcul directement sur les éléments de la matrice, mais il suffit de faire l'application habituelle :
    @implementation NSAffineTransform (rotateAroundCat)<br />- (void)rotateByRadians:(CGFloat)angle aroundX:(CFFloat)x andY:(CFFloat)y<br />{<br />  [[[self translateXBy:-x yBy:-y] rotateByRadians:angle] translateXBy:x yBy:y];<br />}<br /><br />- (void)rotateByDegrees:(CGFloat)angle aroundX:(CFFloat)x andY:(CFFloat)y<br />{<br />  [[[self translateXBy:-x yBy:-y] rotateByDegrees:angle] translateXBy:x yBy:y];<br />}<br />@end
    
    Qui consiste à  définir la transformation comme un déplacement pour amener le point (x,y) en (0,0), puis effectuer une rotation, puis ramener à  l'endroit où cela était avant.
    C'est aussi simple que d'utiliser [tt]transformStruct[/tt] et [tt]setTransformStruct[/tt] pour appliquer les calculs sur ces derniers : finalement ça revient au même, et je suis pas persuadé que ce soit moins optimisé de faire avec mon code que directement sur les valeurs de la matrice...

    D'ailleurs, même s'il y a des chances que la représentation interne des NSAffineTransform soit cette transformStruct (quoique l'erreur que tu as est en fait sûrement justement dû au fait que ce n'est plus le cas ?), rien ne dit dans la doc que tu peux accéder directement à  cette variable privée (autrement dit, rien ne garantit dans la doc que cette variable privée sera toujours accessible dans les futures versions d'OSX)... et de toute façon c'est une variable privée, donc t'es pas sensé connaà®tre son existence et te baser dessus... :P (et puis si elle est privée, y a-t-on vraiment accès dans les catégories, ou n'est-ce le cas qu'avec les protected ?)
    -- Alors que [tt]transformStruct[/tt] et [tt]setTransformStruct:[/tt], eux, sont indiqués dans la doc et ont déjà  un peu plus de chances d'être pérennes : même si la représentation interne des NSAffineTransform changent, ils pourront toujours modifier ces méthodes pour convertir la valeur interne en ces structures pour te donner accès directement aux données et vice-versa.

    D'ailleurs, s'il n'y a pas de [tt]getTransformeStruct:[/tt] (qui donnerait directement accès à  la valeur interne, d'après les conventions de nommage, valeur qui aurait donc directement un impact sur le NSAffineTransform si elle était modifiée, donc), c'est sans doute pas pour rien... est-ce vraiment sa représentation interne ? Ou juste une représentation alternative dans et depuis laquelle la classe sait convertir ses données ?
  • psychoh13psychoh13 Mothership Developer Membre
    23:48 modifié #3
    dans 1197594723:

    Heu dis-moi, pour accéder à  la transformStruct, tu utilises bien la méthode [tt]NSAffineTransformStruct tr = [self transformStruct];[/tt] non ? Plutôt que d'y accéder directement comme variable d'instance ? (doc)


    Non j'y accède directement.

    dans 1197594723:

    Sinon, certes c'est moins optimisé que de faire le calcul directement sur les éléments de la matrice, mais il suffit de faire l'application habituelle :
    @implementation NSAffineTransform (rotateAroundCat)<br />- (void)rotateByRadians:(CGFloat)angle aroundX:(CFFloat)x andY:(CFFloat)y<br />{<br />&nbsp; [[[self translateXBy:-x yBy:-y] rotateByRadians:angle] translateXBy:x yBy:y];<br />}<br /><br />- (void)rotateByDegrees:(CGFloat)angle aroundX:(CFFloat)x andY:(CFFloat)y<br />{<br />&nbsp; [[[self translateXBy:-x yBy:-y] rotateByDegrees:angle] translateXBy:x yBy:y];<br />}<br />@end
    
    Qui consiste à  définir la transformation comme un déplacement pour amener le point (x,y) en (0,0), puis effectuer une rotation, puis ramener à  l'endroit où cela était avant.


    Ouais justement je voudrais utiliser le moins possible de méthode.

    dans 1197594723:
    C'est aussi simple que d'utiliser [tt]transformStruct[/tt] et [tt]setTransformStruct[/tt] pour appliquer les calculs sur ces derniers : finalement ça revient au même, et je suis pas persuadé que ce soit moins optimisé de faire avec mon code que directement sur les valeurs de la matrice...


    Je dois avouer que je n'avais pas remarqué la présence de -setTransformStruct:  :)beta: .

    dans 1197594723:
    D'ailleurs, même s'il y a des chances que la représentation interne des NSAffineTransform soit cette transformStruct (quoique l'erreur que tu as est en fait sûrement justement dû au fait que ce n'est plus le cas ?), rien ne dit dans la doc que tu peux accéder directement à  cette variable privée (autrement dit, rien ne garantit dans la doc que cette variable privée sera toujours accessible dans les futures versions d'OSX)... et de toute façon c'est une variable privée, donc t'es pas sensé connaà®tre son existence et te baser dessus... :P (et puis si elle est privée, y a-t-on vraiment accès dans les catégories, ou n'est-ce le cas qu'avec les protected ?)


    NSAffineTransform n'est pas un class-cluster, les variables d'instance sont donc parfaitement visible. Cette classe en possède une unique qui est de type NSAffineTransformStruct qui est explicitement @private. Et il est explicitement dit dans la documentation que les méthodes d'une catégorie sont équivalentes aux méthodes de la classe elle-même, tu peux parfaitement accéder à  toutes les variables d'instance même les variables @private, et ça c'est explicitement dit dans la documentation. C'est pour ça qu'une catégorie a besoin de connaà®tre l'interface de la classe pour que le compilateur sache à  quelles variables tu as le droit d'accéder.

    dans 1197594723:
    -- Alors que [tt]transformStruct[/tt] et [tt]setTransformStruct:[/tt], eux, sont indiqués dans la doc et ont déjà  un peu plus de chances d'être pérennes : même si la représentation interne des NSAffineTransform changent, ils pourront toujours modifier ces méthodes pour convertir la valeur interne en ces structures pour te donner accès directement aux données et vice-versa.


    Ah ça permets-moi d'en douter je veux bien que la représentation interne des NSString change du tout au tout entre deux version de Mac OS X, parce qu'il y a des centaines de manières de représenter une chaà®ne de caractères, mais l'utilisation de matrice pour les transformations reste tout de même le moyen le plus efficace de calculer les coordonnées de points.

    dans 1197594723:
    D'ailleurs, s'il n'y a pas de [tt]getTransformeStruct:[/tt] (qui donnerait directement accès à  la valeur interne, d'après les conventions de nommage, valeur qui aurait donc directement un impact sur le NSAffineTransform si elle était modifiée, donc), c'est sans doute pas pour rien... est-ce vraiment sa représentation interne ? Ou juste une représentation alternative dans et depuis laquelle la classe sait convertir ses données ?


    Ah non, les noms commençant par "get" ne retournent pas l'adresse d'une variable d'instance, elles ne donnent en aucun cas un accès direct aux variables d'instance. Non, une méthode -getTransformStruct: aurait exactement le même objectif qu'une -transformStruct, c'est-à -dire récupérer la valeur de la variable d'instance. La différence entre les deux, c'est que -transformStruct retourne la valeur, alors que dans le cas de get tu fournis un espace mémoire (via une adresse) pour contenir la valeur demandée. Mais il n'a jamais été question d'accéder à  l'espace mémoire.
    En général, on utilise un get... explicite lorsqu'il y a plus d'une valeur à  récupérer ce qui est impossible à  faire avec un get implicite sans allocation de mémoire ce qui rendrait le get chiant à  utiliser.


    Non, vraiment, pour la catégorie, je suis censé avoir un accès total à  la variable d'instance @private, d'ailleurs, ce n'est pas à  la compilation à  proprement parler qu'il y a l'erreur mais à  la liaison. C'est le linker qui plante, et c'est ça qui me dérange le plus...
  • psychoh13psychoh13 Mothership Developer Membre
    23:48 modifié #4
    dans 1197594723:
    @implementation NSAffineTransform (rotateAroundCat)<br />- (void)rotateByRadians:(CGFloat)angle aroundX:(CFFloat)x andY:(CFFloat)y<br />{<br />&nbsp; [[[self translateXBy:-x yBy:-y] rotateByRadians:angle] translateXBy:x yBy:y];<br />}<br /><br />- (void)rotateByDegrees:(CGFloat)angle aroundX:(CFFloat)x andY:(CFFloat)y<br />{<br />&nbsp; [[[self translateXBy:-x yBy:-y] rotateByDegrees:angle] translateXBy:x yBy:y];<br />}<br />@end
    


    Sinon ton code ne marchera jamais... Parce que je les méthodes que tu utilises retournent void et pas id, donc tu peux pas faire des appels en cascade comme ça. :D
  • AliGatorAliGator Membre, Modérateur
    23:48 modifié #5
    Ah oui pas con les appels en cascade j'ai fait ça vite fait (et à  une heure indue :D) j'ai pas fait gaffe ;D

    Par contre je reste persuadé qu'il est mieux d'utiliser transformStruct et setTransformStruct:

    Evidemment, je suis d'accord avec toi, c'est la représentation matricielle qui restera toujours la mieux pour représenter une transformation affine, on est tous d'accord. Maintenant, pour des questions diverses et variées, entre autres le fait que ce soit une variable explicitement privée, même si tu peux donc y accéder dans ta catégorie, si tu peux éviter, pour moi, c'est mieux, car plus pérenne : Apple peut vouloir changer la représentation interne, ce qui ne veut pas dire que ce ne sera pas une représentation matricielle pour autant (par exemple avoir des variables d'instance séparées pour chaque élément de la matrice plutôt que la structure directement ?)

    Je vois pas pourquoi ils feraient ça mais on sait jamais... et de toute façon ça vaut le coup de tenter avec transformStruct et setTransformStruct: pour voir si le problème persiste, non ?
  • psychoh13psychoh13 Mothership Developer Membre
    23:48 modifié #6
    Non mais ça il n'y a pas de doute, le problème ne persistera pas. C'est sûr et certain.
    Le problème ne vient pas de la compilation, et encore, si la méthode n'était pas définie avant, on aurait droit à  un warning tout au plus.
    Mais ici, mon problème vient du linker, et c'est ça qui m'emmerde. Il compile très bien, il voit que j'ai l'accès à  la variable _transformStruct, d'ailleurs le compilateur transforme correctement le symbole, mais le linker n'est pas capable de retrouver ce symbole... D'où l'erreur.
  • schlumschlum Membre
    décembre 2007 modifié #7
    J'ai essayé de faire une catégorie de NSTransform struct qui utilise _transformStruct, et je n'ai pas le bug... Il doit y avoir un élément extérieur qui interfère  :-\\



    Concernant les getters et autres méthodes, rien n'interdit de renvoyer un pointeur vers une donnée interne, mais en général, la valeur de retour est affublée de "const" afin d'éviter une modification de la part de l'utilisateur qui violerait le concept d'encapsulation.

    Ex : "bytes" de NSData

    [Edit] Les autres exemples trouvés dans "Foundation" :

    "decodeBytesForKey" de NSCoder
    "methodReturnType" et "getArgumentTypeAtIndex" de NSMethodSignature
    "fileSystemRepresentationWithPath" de NSFileManager (qui apparemment, si je lis bien entre les lignes de la doc, crée un NSString avec le bon encodage, la bonne forme de composition... et renvoie sa représentation interne avant de le mettre dans l'AutoReleasePool)
    "lossyCString", "cString", "UTF8String", "cStringUsingEncoding" de NSString (même remarque qu'au dessus)
    "objCType" de NSDecimalNumber et de NSValue
    "aeDesc" de NSAppleEventDescriptor


    D'ailleurs, parfois la doc est très explicite là -dessus :
    Returns a pointer to the AEDesc structure that is encapsulated by the receiver, if it has one.
  • psychoh13psychoh13 Mothership Developer Membre
    23:48 modifié #8
    dans 1197632675:

    J'ai essayé de faire une catégorie de NSTransform struct qui utilise _transformStruct, et je n'ai pas le bug... Il doit y avoir un élément extérieur qui interfère  :-\\


    Tu as fait ta catégorie dans un framework ? Par ce que je pense que mon problème vient de là , enfin j'en suis même sûr parce que je fais le link correctement...

    dans 1197632675:

    Concernant les getters et autres méthodes, rien n'interdit de renvoyer un pointeur vers une donnée interne, mais en général, la valeur de retour est affublée de "const" afin d'éviter une modification de la part de l'utilisateur qui violerait le concept d'encapsulation.


    Sinon, bien sûr, si la doc dit explicitement qu'il s'agit d'une représentation interne, alors en effet il retourne un pointeur sur les données internes, mais si ce n'est pas dit explicitement alors ce n'est pas le cas.

    D'ailleurs concernant certains exemples que tu donnes, je pense qu'il faut repréciser les choses.

    • "bytes" de NSData : ici je suis d'accord il s'agit bien d'une représentation interne, d'ailleus NSData est très laxiste par rapport à  ses données, j'avais par exemple fait un petit programme pour dessiner une fractale et il me fallait dessiner les pixels directement, j'ai utilisé une NoCopy de NSData pour construire l'image, je modifiais directement le buffer que je donnais à  l'objet pour modifier le dessin.
    • "decodeBytesForKey" de NSCoder : ici je doute qu'il s'agisse d'une représentation interne, il est dit dans la documentation que NSCoder utilise un buffer temporaire et le retourne, à  mon avis il s'agit juste des données qu'il lit à  un moment donnée, sinon il fonctionne comme FILE en C, c'est-à -dire qu'il lit les données à  la volée.
    • "methodReturnType" et "getArgumentTypeAtIndex" de NSMethodSignature : là  il faut voir comment sont encodés les types Objective-C. Ils sont encodés sous forme de chaà®nes de caractères C, donc forcément on est obligé de retourné un pointeur.
    • "fileSystemRepresentationWithPath" de NSFileManager : là  il s'agit aussi d'une copie, puisqu'elle permet de convertir un chemin en chemin lisible par un système de fichier.
    • "lossyCString", "cString", "UTF8String", "cStringUsingEncoding" de NSString : là  pareil ce sont aussi des copies, et ça retourne une copie si la conversion est possible. Mais ce sont des copies
    • "objCType" de NSDecimalNumber et de NSValue : pareil que pour NSMethodeSignature
    • "aeDesc" de NSAppleEventDescriptor : là  je suis d'accord c'est sans doute l'élément interne.
  • schlumschlum Membre
    décembre 2007 modifié #9
    dans 1197636062:

    dans 1197632675:

    J'ai essayé de faire une catégorie de NSTransform struct qui utilise _transformStruct, et je n'ai pas le bug... Il doit y avoir un élément extérieur qui interfère  :-\\


    Tu as fait ta catégorie dans un framework ? Par ce que je pense que mon problème vient de là , enfin j'en suis même sûr parce que je fais le link correctement...


    Non ! J'essaierai dans un framework...


    • "bytes" de NSData : ici je suis d'accord il s'agit bien d'une représentation interne, d'ailleus NSData est très laxiste par rapport à  ses données, j'avais par exemple fait un petit programme pour dessiner une fractale et il me fallait dessiner les pixels directement, j'ai utilisé une NoCopy de NSData pour construire l'image, je modifiais directement le buffer que je donnais à  l'objet pour modifier le dessin.
      D'un autre côté, tout ce qui fonctionne avec "noCopy" est très laxiste  :P cf. mon exemple de l'autre jour avec CFString, qu'on peut probablement reproduire avec NSString ("initWithCharactersNoCopy" par exemple)
    • "decodeBytesForKey" de NSCoder : ici je doute qu'il s'agisse d'une représentation interne, il est dit dans la documentation que NSCoder utilise un buffer temporaire et le retourne, à  mon avis il s'agit juste des données qu'il lit à  un moment donnée, sinon il fonctionne comme FILE en C, c'est-à -dire qu'il lit les données à  la volée.
      J'y crois pas au buffer temporaire... Où vois-tu ça dans la doc ? ça poserait bien trop de problèmes pour savoir quand relâcher les données... Ou alors ça utilise le buffer d'un NSData qu'il fout dans l'AutoReleasePool  ???
      Ou sinon, ce "buffer temporaire" est un buffer statique dans l'objet NSCoder cqfd :P

    • "methodReturnType" et "getArgumentTypeAtIndex" de NSMethodSignature : là  il faut voir comment sont encodés les types Objective-C. Ils sont encodés sous forme de chaà®nes de caractères C, donc forcément on est obligé de retourné un pointeur.
      Dans la discussion : "This encoding is implementation-specific"...
    • "fileSystemRepresentationWithPath" de NSFileManager : là  il s'agit aussi d'une copie, puisqu'elle permet de convertir un chemin en chemin lisible par un système de fichier.
      Oui, c'est ce que j'ai dit... Une copie via un NSString qui est foutu dans l'AutoReleasePool mais c'est bien la représentation interne de ce NSString qu'on renvoie. Je ne vois pas d'autre cas possible (à  moins encore une fois de passer par un NSData au lieu d'un NSString).
    • "lossyCString", "cString", "UTF8String", "cStringUsingEncoding" de NSString : là  pareil ce sont aussi des copies, et ça retourne une copie si la conversion est possible. Mais ce sont des copies
      Pareil... J'avais pourtant bien précisé, que c'était les représentations internes de copies  ;D
    • "objCType" de NSDecimalNumber et de NSValue : pareil que pour NSMethodeSignature
    • "aeDesc" de NSAppleEventDescriptor : là  je suis d'accord c'est sans doute l'élément interne.



    Quand ça renvoie un pointeur, deux cas :
    - Il y a allocation
    - Il n'y a pas allocation

    S'il n'y a pas d'allocation, pas de lézard, c'est une représentation interne, pas d'autre choix.
    Quand il y a allocation (et là  ils le précisent dans la doc en général), il faut un mécanisme de gestion de la mémoire allouée ; et là  y a pas 36 solutions...
    - Soit il y a un appel spécifique pour libérer la mémoire (ce qui n'est pas le cas ici)
    - Soit la mémoire est gérée par le NSAutoReleasePool à  travers un autre objet
    Mais dans ce cas, on reboucle sur le problème initial, donc ce pointeur vient de la représentation interne de cet autre objet  ;)
Connectez-vous ou Inscrivez-vous pour répondre.