2017-01-24 4 views
2

J'ai besoin de passer un tableau de chaînes à un appel IFT, je voudrais simplement faire comme:Comment passer un pointeur vers un objet à un appel FFI dans Squeak/Cuis?

library passArray: {'hola' 'manola'} size: 2. 

passArray:size: est quelque chose comme:

passArray: anArray size: anInteger 
    <cdecl: void 'someFunction' (void* size_t)> 
^self externalCallFailed 

Mais il échoue avec " Impossible de contraindre les arguments ", peu importe ce que j'essaie.

Des idées? (Oui, je pourrais "externaliser" toutes les chaînes, et puis construire aussi un tableau des pointeurs, mais je ne pense pas que j'en ai besoin.)

+0

Si la raison est d'éviter de forcer le client à le faire, le seul dont vous avez besoin est un objet qui le fera pour vous (c'est-à-dire pour le client). Un objet assez général sera capable de gérer cela et tout autre besoin de marshaling une collection. –

+0

Pas vraiment, l'utilisateur ne verrait pas vraiment la différence. L'externalisation utilise deux fois plus de mémoire, plus de copies, code pour libérer la mémoire après son utilisation ou relayer en finalisation, en général plus de complexité, pour quelque chose qui devrait être simple, et seulement parce que je ne peux pas passer un pointeur direct Array. Cette implémentation est-elle dépendante? Eh bien, vous pouvez le dire, ou non, une VM différente pourrait simplement faire ce qu'il faut pour supporter le passage d'un pointeur vers un objet ... – gera

+1

Si vous passez un pointeur vers un objet, vous devez alors vous assurer qu'un objet n'est pas déplacé sur GC ... au niveau des sauts lorsqu'il est utilisé "de l'extérieur". Peut-être que je dis quelque chose d'évident, mais je pense que c'est la principale raison de nos externalisations –

Répondre

1

Je préfère utiliser une approche de mémoire partagée où les données doivent être partagées entre Smalltalk et C. La bonne chose à propos de la mémoire partagée est que vous n'avez pas à vous soucier de déplacer les données entre Smalltalk et C parce que les données sont accessibles en même temps depuis C et Smalltalk, car la mémoire partagée fonctionne en dehors des limites VM et GC. Vous n'avez pas à vous inquiéter que vos données soient collectées et se retrouvent avec des fuites de mémoire

Je ne sais pas comment faire cela sur Squeak parce que je suis un utilisateur de Pharo mais doit être quelque chose de similaire.

Sur le côté C

#include <stdio.h> 
#include <stdlib.h> 
#include <sys/types.h> 
#include <sys/stat.h> 
#include <unistd.h> 
#include <fcntl.h> 
#include <sys/mman.h> 
#include <iostream> 
#include <string> 

#define FILEPATH "mmapped.bin" 
#define NUMINTS (1000) 
#define FILESIZE (NUMINTS * sizeof(int)) 

int main(int argc, char *argv[]) 
{ 
    int i; 
    int fd; 
    std::string* map; 
    std::string map_contents; 

    fd = open(FILEPATH, O_RDONLY); 
    if (fd == -1) { 
    perror("Error opening file for reading"); 
    exit(EXIT_FAILURE); 
    } 
    map = (std::string*)mmap(0, FILESIZE, PROT_READ, MAP_SHARED, fd, 0); 
    if (map == MAP_FAILED) { 
    close(fd); 
    perror("Error mmapping the file"); 
    exit(EXIT_FAILURE); 
    } 

    /* Read the file int-by-int from the mmap 
    */ 
    map_contents = std::string(*map); 
    std::cout<<"type of map is : "<< typeid(map).name()<<"\n"; 
    std::cout<<"I am reading from mmap : "<< map_contents <<" \n"; 

    if (munmap(map, FILESIZE) == -1) { 
    perror("Error un-mmapping the file"); 
    } 
    close(fd); 
    return 0; 
} 

Sur le côté Pharo

examples 
retrieveSharedValueStep1 
<example> 
"This method is an example that retrieves a struct from a shared memory section, in order for this example to work you need you first copy paste the contents of the Example Souce Code of the C++ file in the comment section (you can also find the cpp file in the same directory where the git repo has been downloaded) of this class to a C++ source code file and compile it a run then replace the path of in this code of CPPBridge openFile: with the correct path of the bin that the C++ files has created , in order for this to work also you need to execute the C++ example first so it creates and file and share the memory. 
After executing this method you can execute retrieveSharedValueStep2 to unmap and close the memory mapped file (keeps sharing the memory it just does not store it to the file)" 

|instance fdNumber lseek mmapPointer data struct| 

"Let's create an instance just an an example but we wont use it because we can use either class method or intance methods. You would want to use instance method if you want to open multiple memory mapped files meaning multiple areas of shared memory. Class methods for using just one" 

instance := CPPBridge new. 

"Warning !!! You must change the path to the file that is located in your hard drive. The file should be at the same location you built atlas-server.cpp which is responsible for creating the file. The number returned is a number that OS uses to identify the image , flag O_RDWR is just a number that states that we want to write and read the file" 

fdNumber := CPPBridge openFile: '/Users/kilon/git/Pharo/CPPBridge/mmapped.bin' flags: (O_RDWR) . 

"lseek is used to stretch the file to a new size" 
lseek := CPPBridge lSeek_fd: fdNumber range:3999 value:0. 

"this is the most importan method, this method maps the file to memmory , which means it loads its contents into memory and associates the memory with the file. PROT_READ means we want to write the memory , PROT_WRITE to write the memory and MAP_SHARED is the most importan because it defines the memory area as shared so we can access it from other application" 

mmapPointer := CPPBridge mmap_adress: 0 fileSize:4000 flag1: (PROT_READ | PROT_WRITE)flag2: MAP_SHARED fd: fdNumber offset: 0 . 

"This assigns the pointer to our Pharo structure so we can use it to get the contents of the C structure located in the shared memory" 
struct := CPPStruct pointTo: (mmapPointer getHandle). 

"data here serves as a convenience array its not necessary we use it just to collect information about the instance, the fd number of the file, the streched size of the file, the adress (point) where the file is mapped to in memory and struct that contains the values of the C struct that we received" 
data :={ instance. fdNumber . lseek. mmapPointer . struct}. 
data inspect. 

"Store data to the class so we can use it in the second method" 
ExampleDATA := data. 
^data 

" 
Its also possible to write to the shared memory , in this case we use once again the C struct which has the following members (variables) : 
1) data = char[3000] this is where we store the string 
2) count = int this is where we store the size of the string 
struct := {(mmapPointer getHandle copyFrom: 1 to:3000)asString . (mmapPointer getHandle integerAt: 3001 size:4 signed: false)}. 
mmapPointer is the pointer that points to the first byte of the shared memory. 
getHandle gives us the memory adress that the pointer points to 
copyFrom:1 to:3000 copies byte from byte 0 (remember C counts from 0 , Pharo counts from 1) to byte 3000 because the string we store is stored as a char array of 3000 elements, each element is a char, each char is 1 byte in leght and represents a single character of the string. This gets the value of the first struct member. 
on the other hand integerAt: 3001 size: 4 signed: false returns us the value count memeber of the C struct . its an integer in position 3001 because our string is a char[3000] and the size is 4 bytes because its an C int, signed false because we use no negative values because it does not make sense for a string to have negative length. This gets the value of the second struct member" 

Vous pouvez trouver plus d'informations en visitant mon repo github parce que je l'ai emballé tout cela dans une bibliothèque que j'appelle RPC (principale intention était utiliser C++, mais il fonctionne avec C ainsi)

https://github.com/kilon/CPP

Les avantages de mon approche sont:

  1. vous n'avez pas à vous soucier de GC

  2. vous n'avez pas besoin de copier les données autour

  3. parce que la mémoire partagée en utilisant la mémoire mappée système de fichiers de l'OS noyau vous obtenez une tonne de vitesse plus votre mémoire partagée est toujours stocké dans un fichier automagiquement de sorte que vous n'avez pas besoin de s'inquiéter de perdre vos données en cas de sh

  4. le fichier mmap fonctionne de manière similaire à grincer l'image, le stockage en direct état

  5. mmap car est un noyau OS fonctionnent ses prises en charge dans tous les systèmes d'exploitation, mais aussi la plupart des langages de programmation, cela signifie que vous pouvez utiliser cela avec tout langage de programmation que vous voulez

Inconvénients

  1. Parce que cela fonctionne dans une région de gestion de la mémoire manuelle vous perdez les avantages de GC de sorte que vous devez gérer cette mémoire vous manuellement
  2. Parce que son GC extérieur vous aussi perdre beaucoup des capabilites dynamiques des objets Smalltalk et donc vous devez respecter les règles C. De Bien sûr, rien qui vous empêche de faire une copie des données sous forme d'objets Smalltalk si vous le souhaitez ou transmettre les données aux objets existants Smalltalk
  3. Si vous vous trompez, vous planter Squeak VM facilement avec une fuite de mémoire habituelle Pourquoi ne pas externaliser toutes les chaînes?
+0

Une approche TRES intéressante! Merci beaucoup pour le partage, je vais jeter un oeil à votre code définitivement. Je crois que je pourrais l'utiliser sur d'autres projets, où j'ai la chance de changer la bibliothèque native, mais dans ce cas particulier j'utilise une bibliothèque tierce, avec beaucoup de fonctions, donc j'ai dû envelopper fonctions exportées pour les faire utiliser de la mémoire partagée, et puis je dois maintenir deux ensembles de wrappers (Smalltalk et la mémoire partagée), je pense que cela va le rendre plus complexe. Merci beaucoup pour la suggestion! – gera

+0

Oui, la bibliothèque que j'ai mentionnée est faite pour utiliser les méthodes C++ dans Pharo. Ce n'est pas une approche idéale, car généralement utiliser un FFI sera plus facile et plus flexible. Mais dans mon cas, je ne peux pas utiliser UFFI car il est extrêmement difficile de transformer le code C++ en une DLL C (Unreal est un énorme moteur de jeu). Ma réponse cependant se concentrait seulement sur votre problème de partage de données entre C et Smalltalk et PAS d'exécution de code. Je pense que dans ce cas, cette approche est bien meilleure qu'une simple FFI car elle permet de maintenir facilement l'état en direct sans se soucier des restrictions VM. C'est quelque chose qu'un FFI ne peut pas faire. – Kilon