在C#中使用非托管C库 [英] Using unmanaged C lib with C#

查看:100
本文介绍了在C#中使用非托管C库的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用带有C#的C库. 这是我要使用的函数的原型:

I'm using a C lib with C#. This is the prototype of the function that I want to use:

heatmap_render_default_to(const heatmap_t* h, unsigned char* colorbuf)

此函数为colorbuf分配内存,如下所示:

This function is allocating memory for colorbuf like this:

colorbuf = (unsigned char*)malloc(h->w*h->h * 4);

从c#调用我的函数,我首先尝试创建一个非托管内存,如下所示:

to call my function from c# I tried at first to create an unmanaged memory like this:

 string image = "";
 //allocate from COM heap           
 Marshal.StringToCoTaskMemAnsi(image);
 GCHandle gch = GCHandle.Alloc(image, GCHandleType.Pinned);
 HeatMap.HeatMapWrapper.NativeMethods.Render_default_to(hmPtr, image);

但是我得到了这个异常: 在Test.exe中的0x0F17263A(EasyDLL.dll)处引发的异常:0xC0000005:访问冲突写入位置0x01050000. 如果有针对此异常的处理程序,则可以安全地继续执行该程序.

But I'm getting this exception: Exception thrown at 0x0F17263A (EasyDLL.dll) in Test.exe: 0xC0000005: Access violation writing location 0x01050000. If there is a handler for this exception, the program may be safely continued.

这是我第一次尝试在c#中集成非托管库.

It's the first time that I try to integrate an unmanaged library in c#.

有人可以帮我吗?

Pinvoke:

[DllImport(DLL, EntryPoint = "heatmap_render_default_to", CallingConvention = CallingConvention.Cdecl)]
public static extern string Render_default_to(IntPtr h, byte[] colorbuf);  
[DllImport(DLL, EntryPoint = "heatmap_render_to", CallingConvention = CallingConvention.Cdecl)]
public static extern string Render_to(IntPtr h, IntPtr colorscheme, byte[] colorbuf);
[DllImport(DLL, EntryPoint = " heatmap_render_saturated_to", CallingConvention = CallingConvention.Cdecl)]
public static extern string Render_saturated_to(IntPtr h, IntPtr colorscheme, float saturation, byte[] colorbuf);

这是C代码:

__declspec(dllexport) unsigned char* __cdecl heatmap_render_default_to(const heatmap_t* h, unsigned char* colorbuf)
{
return heatmap_render_to(h, heatmap_cs_default, colorbuf);
}
__declspec(dllexport) unsigned char* heatmap_render_to(const heatmap_t* h, const heatmap_colorscheme_t* colorscheme, unsigned char* colorbuf)
{
return heatmap_render_saturated_to(h, colorscheme, h->max > 0.0f ? h->max : 1.0f, colorbuf);
}
__declspec(dllexport) unsigned char* __cdecl heatmap_render_saturated_to(const heatmap_t* h, const heatmap_colorscheme_t* colorscheme, float saturation, unsigned char* colorbuf)
{
unsigned y;
assert(saturation > 0.0f);
/* For convenience, if no buffer is given, malloc a new one. */
if (!colorbuf) {
    colorbuf = (unsigned char*)malloc(h->w*h->h * 4);
    if (!colorbuf) {
        return 0;
    }
}

/* TODO: could actually even flatten this loop before parallelizing it. */
/* I.e., to go i = 0 ; i < h*w since I don't have any padding! (yet?) */
for (y = 0; y < h->h; ++y) {
    float* bufline = h->buf + y*h->w;
    unsigned char* colorline = colorbuf + 4 * y*h->w;

    unsigned x;
    for (x = 0; x < h->w; ++x, ++bufline) {
        /* Saturate the heat value to the given saturation, and then
        * normalize by that.
        */
        const float val = (*bufline > saturation ? saturation : *bufline) / saturation;

        /* We add 0.5 in order to do real rounding, not just dropping the
        * decimal part. That way we are certain the highest value in the
        * colorscheme is actually used.
        */
        const size_t idx = (size_t)((float)(colorscheme->ncolors - 1)*val + 0.5f);

        /* This is probably caused by a negative entry in the stamp! */
        assert(val >= 0.0f);

        /* This should never happen. It is likely a bug in this library. */
        assert(idx < colorscheme->ncolors);

        /* Just copy over the color from the colorscheme. */
        memcpy(colorline, colorscheme->colors + idx * 4, 4);
        colorline += 4;
    }
}

return colorbuf;

}

推荐答案

heatmap_render_default_to似乎想要一个字节的块将其输出写入到其中.这些字节必须要么分配在本机堆上(例如,通过Marshal.AllocHGlobal分配),要么必须由GC固定,以使它们在调用C函数时不会移动.

It looks like heatmap_render_default_to wants a block of bytes to write its output to. These bytes must be either allocated on the native heap (e.g. via Marshal.AllocHGlobal), or pinned by the GC so that they don't move while the C function is being called.

一个例子:

var colourbuf = new byte[width * height * 4];
fixed (byte* colourbufPtr = colourbuf)
    heatmap_render_default_to(hmPtr, colourbufPtr);
// Now you can play with the bytes in colourbuf

或者,如果P/Invoke声明了接受byte[]的函数,则应在调用封送处理期间为您完成GC固定.很简单:

Or if the P/Invoke declares the function to accept byte[], the GC pinning should be done for you during the marshaling of the call. So simply:

var colourbuf = new byte[width * height * 4];
heatmap_render_default_to(hmPtr, colourbuf);

出现访问冲突的原因是,您将与长度为0的字符串(因此,一个缓冲区包含一个空字节)相对应的本机ANSI字节传递给了一个需要大字节缓冲区的函数(并因此而结束了.)

The reason that you got an access violation is that you were passing in the native ANSI bytes that correspond to a string of length 0 (so, a buffer containing a single null byte) to a function that expects a large byte buffer (and thus wrote past the end of it).

此外,还要确保在P/Invoke导入中正确设置了您的调用约定和封送处理参数(尤其是字符串).

Also, be extra sure that your calling convention and marshaling parameters (especially for strings) are set properly on the P/Invoke import.

通常,如果您需要使用C API进行多个琐碎的调用,那么我发现将其放到C ++/CLI中并编写一个经过管理的本地包装程序要容易得多,然后再从C#中使用它作为托管程序集.

In general, if you have to work with a C API for more than a trivial amount of calls, I find it's much easier to drop down into C++/CLI and write a manged-native wrapper assembly, then consume that from C# as a managed assembly.

这篇关于在C#中使用非托管C库的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆