从跨平台 DLL 返回整数数组

发布于 2024-12-08 07:45:07 字数 1241 浏览 6 评论 0原文

我用 C++ 创建了一个跨平台 DLL,可以在 Windows 和 Mac OSX 上编译。在 Windows 上,我有一个使用 P/Invoke 调用 DLL 的 C# 应用程序,在 Mac OSX 上,有一个目标 C 应用程序调用 DLL。我有一个可以正常工作的简单函数,但我需要一个返回整数数组的新函数。

我能找到的最好的例子是 Marshal C++ int array to C# 和我能够让它发挥作用。但是,我想修改此示例以将整数数组作为引用参数传递回来。数组的大小必须在运行时设置。

这是我尝试过的。 pSize 正确返回,但列表为空。

在非托管 C++ 中:

bool GetList(__int32* list, __int32* pSize)
{

    // Some dummy data
    vector<int> ret;
    ret.push_back(5);
    ret.push_back(6);

    list = (__int32*)malloc(ret.size());
    for (unsigned int i = 0; i < ret.size(); i++)
    {
            list[i] = ret.at(i);
    }
    *pSize = ret.size();

    return true;
}

在 C# 中:

[DllImport(@"MyDll.dll",
    CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)]
    public static extern bool GetList(out IntPtr arrayPtr, out int size);


public static int[] GetList() {
    IntPtr arrayValue = IntPtr.Zero;
    int size = 0;

    bool b = GetFrames(out arrayValue, out size);
    // arrayValue is 0 here

    int[] result = new int[size];

    Marshal.Copy(arrayValue, result, 0, size);

    return result;
}

I created a cross-platform DLL in C++ that compiles on both Windows and Mac OSX. On Windows, I have a C# app that calls the DLL using P/Invoke and on Mac OSX, an objective C app calls the DLL. I have simple functions working just fine but I need a new function that returns an array of integers.

The best example I can find is at Marshal C++ int array to C# and I was able to make it work. However, I would like to modify this example to pass the integer array back as a reference argument instead. The size of the array has to be set at runtime.

Here's what I've tried. The pSize is coming back correctly but the list is empty.

In unmanaged c++:

bool GetList(__int32* list, __int32* pSize)
{

    // Some dummy data
    vector<int> ret;
    ret.push_back(5);
    ret.push_back(6);

    list = (__int32*)malloc(ret.size());
    for (unsigned int i = 0; i < ret.size(); i++)
    {
            list[i] = ret.at(i);
    }
    *pSize = ret.size();

    return true;
}

In C#:

[DllImport(@"MyDll.dll",
    CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)]
    public static extern bool GetList(out IntPtr arrayPtr, out int size);


public static int[] GetList() {
    IntPtr arrayValue = IntPtr.Zero;
    int size = 0;

    bool b = GetFrames(out arrayValue, out size);
    // arrayValue is 0 here

    int[] result = new int[size];

    Marshal.Copy(arrayValue, result, 0, size);

    return result;
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

冷心人i 2024-12-15 07:45:07

“调用者分配”是使代码可移植同时保持可维护性的唯一方法。您的代码不仅不会更改调用者的指针,而且 C# 代码也无法释放您分配的内存(malloc 内存不会被垃圾回收清理)。

如果快速找到大小(不需要生成所有输出数据),只需添加第二个函数来返回大小。

如果在生成数据之前无法获取大小,则让一个函数返回大小和指向内容的指针(int**,在 C# 端它将是 ref IntPtr )。第二个函数将该数据复制到 C# 数组并释放本机缓冲区。

"Caller-allocates" is the only way to make code portable while keeping it maintainable. Not only does your code not change the caller's pointer, but the C# code has no way to free the memory you allocated (malloc-ed memory won't be cleaned up by garbage collection).

If finding the size is quick (doesn't require generating all the output data), just add a second function to return the size.

If you can't get the size until you generate the data, then make one function return the size and a pointer to the content (int**, on the C# side it will be ref IntPtr). And a second function that copies that data to the C# array and frees the native buffer.

原野 2024-12-15 07:45:07

您的问题是 list 的定义,它确实需要是 __int32** 才能将分配的数组的地址传回。为了轻松解决指针到指针的互操作困难,您可以在失败时返回 listnull 的地址:

__int32* GetList(__int32* pSize)
{
    // Some dummy data
    vector<int> ret;
    ret.push_back(5);
    ret.push_back(6);

    // per @David's catch, you'll need to allocate the right amount
    __int32* list = (__int32*)malloc(ret.size() * sizeof(__int32));
    for (unsigned int i = 0; i < ret.size(); i++)
    {
            list[i] = ret.at(i);
    }
    *pSize = ret.size();

    return list;
}

void RemoveList(__int32* list)
{
    free(list);
}

通过对 C# 进行适当的修改:

[DllImport(@"MyDll.dll",
 CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr GetList(out int size);

[DllImport(@"MyDll.dll",
 CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)]
private static extern void RemoveList(IntPtr array);

public static int[] GetList()
{
    int[] result = null;
    int size;

    IntPtr arrayValue = IntPtr.Zero;
    try
    {
        arrayValue = GetList(out size);
        if (arrayValue != IntPtr.Zero)
        {
            result = new int[size];
            Marshal.Copy(arrayValue, result, 0, size);
        }
    }
    finally
    {
        // don't forget to free the list
        RemoveList(arrayValue);
    }

    return result;
}

Your problem is the definition of list, it really needs to be an __int32** in order to pass back the address of the allocated array. To breeze through the interop difficulties of pointers-to-pointers, how about you instead return the address of list or null if it fails:

__int32* GetList(__int32* pSize)
{
    // Some dummy data
    vector<int> ret;
    ret.push_back(5);
    ret.push_back(6);

    // per @David's catch, you'll need to allocate the right amount
    __int32* list = (__int32*)malloc(ret.size() * sizeof(__int32));
    for (unsigned int i = 0; i < ret.size(); i++)
    {
            list[i] = ret.at(i);
    }
    *pSize = ret.size();

    return list;
}

void RemoveList(__int32* list)
{
    free(list);
}

With the appropriate modifications to your C#:

[DllImport(@"MyDll.dll",
 CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr GetList(out int size);

[DllImport(@"MyDll.dll",
 CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)]
private static extern void RemoveList(IntPtr array);

public static int[] GetList()
{
    int[] result = null;
    int size;

    IntPtr arrayValue = IntPtr.Zero;
    try
    {
        arrayValue = GetList(out size);
        if (arrayValue != IntPtr.Zero)
        {
            result = new int[size];
            Marshal.Copy(arrayValue, result, 0, size);
        }
    }
    finally
    {
        // don't forget to free the list
        RemoveList(arrayValue);
    }

    return result;
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文