Smalltalk 如何将指向对象的指针传递给Squeak/CUI中的FFI调用?

Smalltalk 如何将指向对象的指针传递给Squeak/CUI中的FFI调用?,smalltalk,ffi,squeak,Smalltalk,Ffi,Squeak,我需要将字符串数组传递给FFI调用,我只想按照以下方式执行: library passArray: {'hola' 'manola'} size: 2. 其中passArray:size:类似于: passArray: anArray size: anInteger <cdecl: void 'someFunction' (void* size_t)> ^ self externalCallFailed passArray:数组大小:整数 ^自我外部调用失败 但无论

我需要将字符串数组传递给FFI调用,我只想按照以下方式执行:

library passArray: {'hola' 'manola'} size: 2.
其中
passArray:size:
类似于:

passArray: anArray size: anInteger
   <cdecl: void 'someFunction' (void* size_t)>
   ^ self externalCallFailed
passArray:数组大小:整数
^自我外部调用失败
但无论我做什么尝试,它都以“无法强迫论点”而失败


有什么想法吗?(是的,我可以“外部化”所有字符串,然后构建一个指针数组,但我认为我不需要它。

我更喜欢使用共享内存方法,在Smalltalk和C之间共享数据。共享内存的好处是,您不必担心在Smalltalk和C之间移动数据,因为数据可以从C和Smallt访问同时,由于共享内存在VM和GC边界之外运行,所以您不必担心数据被垃圾收集并最终导致内存泄漏

我不知道如何在Squeak上这样做,因为我是一个Pharo用户,但必须是类似的

在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;
}
#包括
#包括
#包括
#包括
#包括
#包括
#包括
#包括
#包括
#定义文件路径“mmapped.bin”
#定义整数(1000)
#定义文件大小(NUMINTS*sizeof(int))
int main(int argc,char*argv[])
{
int i;
int-fd;
std::string*映射;
std::字符串映射内容;
fd=打开(仅文件路径);
如果(fd==-1){
perror(“打开文件读取时出错”);
退出(退出失败);
}
map=(std::string*)mmap(0,文件大小,PROT_READ,map_SHARED,fd,0);
if(map==map\u失败){
关闭(fd);
perror(“映射文件时出错”);
退出(退出失败);
}
/*从mmap中逐个读取文件int
*/
地图内容=标准::字符串(*地图);

std::coutWhy您不想外部化所有字符串?如果原因是为了避免强制客户机这样做,那么您唯一需要的是一个将为您(即,为客户机)这样做的对象。一个足够通用的对象将能够处理这一点以及封送集合的任何其他需要。不是真的,用户不会真正看到区别。外部化是使用所需内存的两倍、更多副本、代码在使用后释放内存,或者在最终确定时以通常更复杂的方式,为应该e很简单,只是因为我不能传递一个指向数组的直接指针。这个实现依赖于吗?你可以这么说,也可以不这样说,一个不同的VM可以做实际支持传递指向对象的指针所需的事情…如果你传递指向某个对象的指针,那么你应该确保在GC上不移动某个对象…在使用它的时候“从外面”.也许我说的是一些显而易见的事情,但我认为这是我们外部化的主要原因!Carlos,谢谢!!!我有专门为此设计的测试,检查外部函数是否包含引用和/或对象的副本。好的,我理解。但请注意,向外部函数公开OOP有点冒险。即使你在一个线程中运行,外部函数复制了它的参数,它也可能会调用你,你的对象可能会移动。我不是说你不应该这样做,我只是说这不能作为一般规则来采用,这也会增加复杂性。非常有趣的方法!非常感谢分享,我将我肯定会看看你的代码。我相信我可以在其他一些项目上使用它,在这些项目中,我确实有机会更改本机库,但在这种情况下,我使用的是第三方库,有很多函数,所以我必须包装导出的函数,让它们使用共享内存,然后我必须维护两组包装器(Smalltalk和共享内存)我想它会使它更复杂。谢谢。我提到的库是在法罗里面使用C++方法的。这不是一个理想的方法,因为一般使用FFI会更容易和更灵活。但是在我的例子中,我不能使用UFFI,因为将C++代码转换成C DLL非常困难。(虚幻是一个巨大的游戏引擎)。然而,我的回答只是关注在C和Smalltalk之间共享数据而不是执行代码的问题。我认为在这种情况下,这种方法比简单的FFI好得多,因为它可以帮助轻松地维护活动状态,而不必担心VM限制。这是FFI无法做到的。
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"