【问题标题】:How do I pass a pointer to an Object to an FFI call in Squeak/Cuis?如何将指向对象的指针传递给 Squeak/Cuis 中的 FFI 调用?
【发布时间】:2017-01-24 12:31:47
【问题描述】:

我需要将字符串数组传递给 FFI 调用,我想这样做:

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

passArray:size: 类似于:

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

但无论我尝试什么,它都会失败并显示“无法强制参数”。

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

【问题讨论】:

  • 为什么你不想外部化所有的字符串?如果原因是为了避免强迫客户端这样做,那么您唯一需要的是一个可以为您(即客户端)执行此操作的对象。一个足够通用的对象将能够处理这个以及任何其他编组集合的需要。
  • 并非如此,用户不会真正看到差异。外部化是使用两倍所需的内存,更多的副本,在内存被使用后释放内存的代码或在最终确定上进行中继,通常更复杂,对于应该简单的事情,只是因为我无法将直接指针传递给大批。这个实现是否依赖?好吧,你可以这么说,也可以不这么说,一个不同的虚拟机可以做实际支持将指针传递给对象所需的事情......
  • 如果你传递一个指向 anObject 的指针,你应该确保 anObject 在 GC 上不会被移动......至少在“从外部”使用它时。可能是我在陈述一些显而易见的事情,但我认为这是我们外化的主要原因
  • 是的!好点卡洛斯,谢谢!!!我有专门为此设计的测试,检查外部函数是否包含引用和/或复制对象。
  • 好的,我明白了。但请注意,将 oops 暴露给外部函数有点冒险。即使您在单个线程中运行并且外部函数复制其参数,它也可能会回调您并且您的对象可能会移动。我并不是说你不应该这样做,我只是说不能作为一般规则采用,这也会增加复杂性。

标签: smalltalk ffi squeak


【解决方案1】:

我更喜欢使用在 Smalltalk 和 C 之间共享数据的共享内存方法。共享内存的好处是您不必担心在 Smalltalk 和 C 之间移动数据,因为可以从 C 访问数据和 Smalltalk 同时进行。此外,由于共享内存在 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;
}

在法罗一侧

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"

您可以通过访问我的 github 存储库找到更多信息,因为我已将所有这些打包到我称为 CPP 的库中(主要目的是使用 C++,但它也适用于 C)

https://github.com/kilon/CPP

我的方法的优点是:

  1. 您不必担心 GC

  2. 你不需要复制数据

  3. 因为共享内存使用操作系统的内存映射文件系统 内核,您将获得大量速度,而且您的共享内存始终 自动存储到文件中,因此您无需担心 发生崩溃时丢失数据

  4. mmap 文件的工作方式类似于 squeak 图像,实时存储 状态

  5. mmap 因为它是一个操作系统内核函数,它在所有操作系统中都受支持,但 也是大多数编程语言,这意味着您可以将其与 任何你想要的编程语言

缺点

  1. 因为这在手动内存管理区域内有效,所以您会丢失 GC 的优势,因此您需要自己处理该内存 手动
  2. 由于它的外部 GC,您还失去了许多动态能力 Smalltalk 对象,因此您必须遵守 C 规则。的 当然没有人阻止您将数据复制为 Smalltalk 对象,如果你愿意或将数据传递给现有的 Smalltalk 对象
  3. 如果你搞砸了,你会像平常一样轻松地使 Squeak VM 崩溃 内存泄漏

【讨论】:

  • 非常有趣的方法!非常感谢分享,我一定会看看你的代码。我相信我可以在其他一些项目中使用它,在那里我确实有机会更改本机库,但在这种特殊情况下,我使用的是具有很多功能的第三方库,所以我不得不包装导出的函数让它们使用共享内存,然后我必须维护两组包装器(Smalltalk 和共享内存),我认为这会使它更复杂。非常感谢您的建议!
  • 是的,我提到的库是在 Pharo 中使用 C++ 方法的。这不是一个理想的方法,因为通常使用 FFI 会更容易和更灵活。但就我而言,我不能使用 UFFI,因为将 C++ 代码转换为 C DLL 非常困难(Unreal 是一个巨大的游戏引擎)。然而,我的回答只关注您在 C 和 Smalltalk 之间共享数据而不执行代码的问题。我认为在这种情况下,这种方法比简单的 FFI 要好得多,因为它有助于轻松维护实时状态,而无需担心 VM 限制。这是 FFI 无法做到的。
猜你喜欢
  • 2018-09-03
  • 1970-01-01
  • 1970-01-01
  • 2014-03-31
  • 2023-03-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多