将指针数组传递给非托管DLL函数

时间:2015-02-10 17:32:56

标签: c# dll pinvoke dllimport

我正在尝试使用以下C#代码创建并传递指向非托管DLL函数的指针数组。

[DllImport("libantumbra.dll", CallingConvention = CallingConvention.Cdecl)] 
public static extern uint AnCtx_Init(IntPtr ctx);

//create context
this.ptr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(IntPtr)));
AnCtx_Init(ptr);//returns 0 (non-error)
this.ctx = (IntPtr)Marshal.PtrToStructure(ptr, typeof(IntPtr));

[DllImport("libantumbra.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int AnDevice_GetList(IntPtr ctx, out IntPtr outdevs, out int outndevs);
IntPtr devs, ndevs;
AnDevice_GetList(ctx, out devs, out ndevs); //exception occurs here  

但是,在我上次通话时,我收到了AccessViolationException。我认为它与我传递的数组指针有关但是我无法找到解决方案。

我想在这里实现的最终目标是传递一个指向AnDevice_GetList的指针,并且参数outdevs留下一个由DLL填充的数组。

如果您需要任何进一步的信息或有任何想法让我尝试,请告诉我。

编辑:

这是我试图打电话的功能。

标题文件:

An_DLL AnError AnDevice_GetList(AnCtx *ctx, AnDeviceInfo ***outdevs,
                            size_t *outndevs);
typedef struct AnDevice AnDevice;
typedef int AnError;
typedef struct AnCtx AnCtx;

并实施:

AnError AnDevice_GetList(AnCtx *ctx, AnDeviceInfo ***outdevs, size_t *outndevs)
{
    An_LOG(ctx, AnLog_DEBUG, "enumerate devices...");
    libusb_device **udevs;
    ssize_t ndevs = libusb_get_device_list(ctx->uctx, &udevs);
    if (ndevs < 0) {
        An_LOG(ctx, AnLog_ERROR, "libusb_get_device_list: %s",
               libusb_strerror(ndevs));
       return AnError_LIBUSB;
    }
    AnDeviceInfo **devs = malloc((ndevs + 1) * sizeof *devs);
    if (!devs) {
        An_LOG(ctx, AnLog_ERROR, "malloc: %s", strerror(errno));
        return AnError_MALLOCFAILED;
    }
    memset(devs, 0, (ndevs + 1) * sizeof *devs);
    size_t j = 0;
    for (ssize_t i = 0; i < ndevs; ++i) {
        libusb_device *udev = udevs[i];
        AnDeviceInfo info;
        An_LOG(ctx, AnLog_DEBUG, "device: bus %03d addr %03d",
           libusb_get_bus_number(udev), libusb_get_device_address(udev));
        if (populate_info(ctx, &info, udev))
            continue;
        An_LOG(ctx, AnLog_DEBUG, "vid 0x%04x pid 0x%04x",
               info.devdes.idVendor, info.devdes.idProduct);
        if (!match_vid_pid(info.devdes.idVendor, info.devdes.idProduct)) {
            An_LOG(ctx, AnLog_DEBUG, "  does not match Antumbra VID/PID");
            continue;
        }
        devs[j] = malloc(sizeof *devs[j]);
        if (!devs[j]) {
            An_LOG(ctx, AnLog_ERROR, "malloc: %s", strerror(errno));
            continue;
        }
        libusb_ref_device(udev);
        *devs[j] = info;
        ++j;
    }
    libusb_free_device_list(udevs, 1);
    *outdevs = devs;
    *outndevs = j;
    return AnError_SUCCESS;
}

2 个答案:

答案 0 :(得分:0)

有些事情在你的例子中没有意义。您创建ptr2并为其分配空间但从未将任何内容复制到该空间中,并且您不会将其传递给AnDevice_GetList函数,因此这似乎完全没必要。您创建ptArray但从未在任何地方使用它。

在此代码中,您将创建一个IntPtr结构的托管数组,并为每个结构分配内存以指向它们,并且它们指向的内容的大小是单个指针的大小:

IntPtr[] ptArray = new IntPtr[] {
    Marshal.AllocHGlobal(IntPtr.Size),
    Marshal.AllocHGlobal(IntPtr.Size)
};

要真正帮助我们清楚地了解AnDevice_GetList将要做什么。如果AnDevice_GetList填充指针数组,它们指向什么?它们是否指向由AnDevice_GetList分配的结构?如果是这样,那么您要做的是创建一个IntPtr数组并在进行非托管调用时将其固定。由于您要为填充调用创建数组,请不要将该数组作为out参数传递。

[DllImport("libsomething.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern uint AnDevice_GetList(IntPtr outdevs);

IntPtr[] ptArray = new IntPtr[numberOfPointersRequired];
GCHandle handle = GCHandle.Alloc(ptArray);

try
{
    AnDevice_GetList(handle.AddrOfPinnedObject());
}
finally
{
    handle.Free();
}

我中断了其他参数,因为我不知道你对它们做了什么,或者你是如何期待它们被处理的。

答案 1 :(得分:0)

您的非托管函数声明如下:

AnError AnDevice_GetList(AnCtx *ctx, AnDeviceInfo ***outdevs, size_t *outndevs)

您应将其翻译为:

[DllImport("libantumbra.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int AnDevice_GetList(IntPtr ctx, out IntPtr outdevs, 
    out IntPtr outndevs);

这几乎和你一样。唯一的区别是返回值为intoutndevs参数的类型为IntPtr。那是因为size_t是我所知道的平台上的指针大小。

这样称呼:

IntPtr ctx = ...; // create a context somehow
IntPtr devs;
IntPtr ndevs;
int retval = AnDevice_GetList(ctx, out devs, out ndevs);
if (retval != AnError_SUCCESS)
    // handle error

那么,您的代码哪里可能出错?一种可能的解释是,您传递的上下文无效。另一种可能性是您执行64位代码并且翻译中outndevs的大小错误导致错误。

使用p / invoke调用这是一个相当难的API。你现在可以用devs做什么。您可以轻松地将值复制到IntPtr[]数组中。并且可能该库具有对这些不透明设备指针进行操作的功能。但是你必须保持devs并将其传递回库以解除分配。据推测,图书馆出口的功能可以做到这一点吗?


根据您的评论和各种更新,您似乎没有获得正确的背景信息。我们只能猜测,但我希望AnCtx_Init被声明为

AnError AnCtx_Init(AnCtx **octx)

这是指向不透明上下文AnCtx*的指针。翻译为:

[DllImport("libantumbra.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int AnCtx_Init(out IntPtr octx);

这样称呼:

IntPtr ctx;
int retval = AnCtx_Init(out ctx);
if (retval != AnError_SUCCESS)
    // handle error

你现在要做的最重要的事情就是开始检查错误。非托管代码不会抛出异常。您需要自己进行错误检查。这很费劲,但必须这样做。一次一个功能。一旦确定函数调用正在运行,请转到下一个。