如何在使用数组的 C# 互操作调用中通过引用访问基于 C 的指针?

发布于 2024-11-17 13:01:34 字数 2051 浏览 3 评论 0原文

我对 P/Invoke 相当陌生(并且对 C 非常生疏),并且我花了相当多的时间尝试解决以下问题:

我想从托管 C# 调用非托管 C 函数。 C 函数应该创建一个包含值的数组并返回该数组,以便我可以在 C# 中使用这些值。 (相反的工作很好,即将数组从 C# 传递到 C。)即我想将参数作为数组的引用传递。下面是一个简单的示例代码,我认为应该可以工作(但不能):

C:

int __declspec(dllexport) __stdcall AllocateMemory(int **values, const unsigned int N)
{
  int sum = 0;
  free(*values);
  *values = (int*) malloc(N*sizeof(int));
  for(unsigned int i = 0; i < N; i++)  { 
  (*values)[i] = i;    
  sum += (*values)[i];
}

这可以直接从 C 控制台测试应用程序正常工作:

int main( int argc, char** argv) 
{
  int* values;
  int sum = AllocateMemory(&values, 4);
  printf ("Sum: %d\n", sum);

}

但是,直接从 C# 进行此调用会呈现(内存)System.AccessVioaltionException:

[DllImport("Test.dll", CharSet = CharSet.Ansi, SetLastError = true, CallingConvention = CallingConvention.Winapi)]
public static extern int AllocateMemory([Out, MarshalAs(UnmanagedType.LPArray, SizeConst = 10)] out int[] someValues, int N);

private static void RunCalculation(string message)
{
  int[] someValues; 
  int sum = AllocateMemory(out someValues, 4);
}

I'我已经尝试了 DllImport 签名的各种版本,但没有成功,我现在将继续调查。但是,如果有人有提示,我将非常感激!


(下面是一个小时左右后添加的(在下面的一些评论之后),因为直到明天我才可以自己发布回复)

我发现我能够使用以下 C# 代码:

[DllImport("FrontAuction2.dll", CharSet = CharSet.Ansi, SetLastError = true, CallingConvention = CallingConvention.Winapi)]
public static extern float AllocateCudaMemory(out IntPtr someValues, int N);

private static IntPtr AllocateCudaMemory()
{ 
  IntPtr temp; 
  float sum = AllocateCudaMemory(out temp, 4);  

  //NOTE: If you'd actually want to get the value and just not want to pass the IntPtr to another P/Invoke method (as I do), this is one way: 
  //var values = new int[4];
  //Marshal.Copy(temp, values, 0, 4);

  return temp;
} 

但是,有下面是我评论过的一些有趣的观点。

我帐户上所有这些的目的是允许将 CUDA GPU 内存引用传递给 C# 以供以后使用。我认为更好的方法可能是将引用保留在 COM C++ 状态服务器中,而不是将其传递给 C#。但解决这个问题至少能让我现在能够专注于 GPU 的事情,而不是互操作。

I am fairly new to P/Invoke (and very rusty on C) and I have spent considerable time trying solve the folling problem:

I want to call a unmanaged C function from managed C#. The C function should create an array with values and return this so I can use these values in C#. (The opposite works fine, i.e. passing in an array from C# to C.) I.e. I want to do pass parameters as references with arrays. Here's a simple sample code of what I figure should work (but does not):

C:

int __declspec(dllexport) __stdcall AllocateMemory(int **values, const unsigned int N)
{
  int sum = 0;
  free(*values);
  *values = (int*) malloc(N*sizeof(int));
  for(unsigned int i = 0; i < N; i++)  { 
  (*values)[i] = i;    
  sum += (*values)[i];
}

This works fine from a C console test app directly:

int main( int argc, char** argv) 
{
  int* values;
  int sum = AllocateMemory(&values, 4);
  printf ("Sum: %d\n", sum);

}

However, making this call directly from C# renders a (memory) System.AccessVioaltionException:

[DllImport("Test.dll", CharSet = CharSet.Ansi, SetLastError = true, CallingConvention = CallingConvention.Winapi)]
public static extern int AllocateMemory([Out, MarshalAs(UnmanagedType.LPArray, SizeConst = 10)] out int[] someValues, int N);

private static void RunCalculation(string message)
{
  int[] someValues; 
  int sum = AllocateMemory(out someValues, 4);
}

I've tried various versions of the DllImport signature without success and I'll continue my investigation now. However, if anyone has a hint I would be much obliged!


(Below is added an hour or so later (after some comments below) since I am not allowed to post a reply myself until tomorrow)

I found I was able to use the following C# code:

[DllImport("FrontAuction2.dll", CharSet = CharSet.Ansi, SetLastError = true, CallingConvention = CallingConvention.Winapi)]
public static extern float AllocateCudaMemory(out IntPtr someValues, int N);

private static IntPtr AllocateCudaMemory()
{ 
  IntPtr temp; 
  float sum = AllocateCudaMemory(out temp, 4);  

  //NOTE: If you'd actually want to get the value and just not want to pass the IntPtr to another P/Invoke method (as I do), this is one way: 
  //var values = new int[4];
  //Marshal.Copy(temp, values, 0, 4);

  return temp;
} 

However, there are some interesting points in the comments below which I have commented on.

The purpose of all this on my account is to allow a CUDA GPU memory reference to be passed to C# for later usage. I think a better way to do this probably is to keep the reference in a COM C++ state server instead of passing it to C#. But solving this will at least make it possible for me to focus on GPU stuff right now instead of interop.

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

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

发布评论

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

评论(2

柠栀 2024-11-24 13:01:34

您的 C 代码存在内存泄漏,数组的内存没有被释放。 pinvoke 编组器不喜欢泄漏内存。它将尝试释放数组,它将使用 CoTaskMemFree()。这不会有一个好的结局,你没有使用 CoTaskMemAlloc() 进行分配。

不要编写需要调用者释放被调用者分配的内存的代码。这很少能取得好的效果。只需让调用者传递一个您填充的数组:

int __declspec(dllexport) __stdcall AllocateMemory(int *values, const unsigned int N)
{
  int sum = 0;
  for(unsigned int i = 0; i < N; i++)  { 
    values[i] = i;    
    sum += values[i];
  }
}

[DllImport("Test.dll")]
public static extern int AllocateMemory(int[] someValues, int N);
...
  int[] values = new int[10];
  int sum = AllocateMemory(values, values.Length);

您必须为该函数想出一个不同的名称:)

Your C code has a memory leak, the memory for the array isn't getting released. The pinvoke marshaller does not like leaking memory. It is going to try to release the array, it will use CoTaskMemFree(). That's not going to come to a good end, you didn't allocate with CoTaskMemAlloc().

Don't write code that requires the caller to release memory allocated by the callee. That very rarely works out well. Simply let the caller pass an array that you fill:

int __declspec(dllexport) __stdcall AllocateMemory(int *values, const unsigned int N)
{
  int sum = 0;
  for(unsigned int i = 0; i < N; i++)  { 
    values[i] = i;    
    sum += values[i];
  }
}

[DllImport("Test.dll")]
public static extern int AllocateMemory(int[] someValues, int N);
...
  int[] values = new int[10];
  int sum = AllocateMemory(values, values.Length);

You'll have to come up with a different name for that function :)

爱的那么颓废 2024-11-24 13:01:34

如果可以的话,最好在 C# 中分配内存并让 C 代码填充数组:

C

__declspec(dllexport) void __stdcall PopulateArray(int N, int values[])
{
    int i;
    for (i = 0; i < N; i++)
        values[i] = i;    
}

C#

[DllImport(@"test.dll")]
public static extern void PopulateArray(int N, int[] values);

static void Main(string[] args)
{
    int N = 5;
    int[] values = new int[N];
    PopulateArray(N, values);
}

看来你的问题错过了关键信息您需要在 C 代码中分配内存,然后在 C# 代码中保存指针,以便依次将其交给 CUDA。

这样做:

C

__declspec(dllexport) int* __stdcall AllocateIntArray(int N)
{
    int i;
    int *values = malloc(N*sizeof(int));
    for (i = 0; i < N; i++)
        values[i] = i;    
    return values;
}

__declspec(dllexport) void __stdcall FreeMemory(void* ptr)
{
    free(ptr);
}

C#

[DllImport(@"test.dll")]
public static extern IntPtr AllocateIntArray(int N);

[DllImport(@"test.dll")]
public static extern void FreeMemory(IntPtr ptr);

static void Main(string[] args)
{
    IntPtr ptr = AllocateIntArray(5);
    //...do something with the memory
    FreeMemory(ptr);
}

It's best, if you can do so, to allocate the memory in C# and let your C code fill out the array:

C

__declspec(dllexport) void __stdcall PopulateArray(int N, int values[])
{
    int i;
    for (i = 0; i < N; i++)
        values[i] = i;    
}

C#

[DllImport(@"test.dll")]
public static extern void PopulateArray(int N, int[] values);

static void Main(string[] args)
{
    int N = 5;
    int[] values = new int[N];
    PopulateArray(N, values);
}

It seems that your question missed the crucial information that you need to allocate the memory in your C code and then hold the pointer in your C# code so that in turn you can hand it on to CUDA.

Do it like this:

C

__declspec(dllexport) int* __stdcall AllocateIntArray(int N)
{
    int i;
    int *values = malloc(N*sizeof(int));
    for (i = 0; i < N; i++)
        values[i] = i;    
    return values;
}

__declspec(dllexport) void __stdcall FreeMemory(void* ptr)
{
    free(ptr);
}

C#

[DllImport(@"test.dll")]
public static extern IntPtr AllocateIntArray(int N);

[DllImport(@"test.dll")]
public static extern void FreeMemory(IntPtr ptr);

static void Main(string[] args)
{
    IntPtr ptr = AllocateIntArray(5);
    //...do something with the memory
    FreeMemory(ptr);
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文