2012-02-17 100 views
1

我需要将一个指针传递给一个结构到我的DLL,任何想法我会怎么做呢?从C#程序调用一个C DLL

在我的C DLL:

typedef struct 
{ 
    int length; 
    unsigned char *value; 
} Sample; 


__declspec(dllexport) void __stdcall helloWorld(Sample *sample); 

在我的C#代码:

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Runtime.InteropServices; 

namespace CSharpConsole 
{ 
    class Program 
    { 
     [StructLayout(LayoutKind.Sequential, Pack = 1)] 
     private struct Sample 
     { 
      public Int32 length; 
// What Should I Declare Here? 
     } 

     [DllImport("C:\\CTestDLL.dll")] 
     private static extern void helloWorld(Sample sample); // How would I make this a pointer? 

     void HelloWorld() 
     { 
      Sample sample = new Sample(); 
      sample .length = 20; 
      // How can I fill up the values of value? 
      helloWorld(sample); // How should I pass it inside here 
      return; 
     } 

     static void Main(string[] args) 
     { 
      Program program = new Program(); 
      program.HelloWorld(); 
     } 
    } 
} 
+0

你想要使用IntPtr(一个平台安全指针),可能还有一些数据封送。不安全的代码块可能是必需的。 – user978122 2012-02-17 05:12:24

回答

1

Default Marshaling for Value Types - 提供了有关编组结构的一些信息。
Calling Win32 DLLs in C# with P/Invoke - 页面向下一半的位置有一张表格,显示标准非托管和托管类型之间的类型转换。

有几种方式可以传递和转换类型。
正如Michael Edenfield指出的那样,你通常可以使用ref关键字来表示一个参数应该通过基本上是指针的引用传递。

但有时候,事情并不合作,特别是当涉及到字符串或复杂的数据类型时,所以这就是IntPtr类型出现的地方。 您有几个使用IntPtrs的选项。
可以使用创建一个IntPtr到指定大小的非托管内存块:

IntPtr pointer = Marshal.AllocHGlobal(sizeOfBufferInBytes); 
//Do stuff with the pointer 
Marshal.FreeHGlobal(pointer); //Don't forget to release the memory 

这显然是一个有点危险,因为你手动分配非托管内存。
你需要从缓冲区中的数据,整理回托管类型,使用类似Marshal.Copy(),Marshal.PtrToStructure(),Buffer.BlockCopy()等

另外,您可以创建一个托管对象,并在需要时将其固定在内存中,获取指向它的指针并将其传递给您的方法。

MyObject instance = new MyObject(); 
GCHandle gch = GCHandle.Alloc(instance, GCHandleType.Pinned); 
importedMethod(gch.AddrOfPinnedObject()); //AddrOfPinnedObject() gives you an IntPtr 
gch.Free(); //Release the pinned memory so the garbage collector can deal with it 

这避免了手动编组回正确的数据类型的必要性,但是,这并不总是依赖于myObject的类型以及它是否blittable一个选项。

1

这个工作对我来说:

DLL:

typedef struct 
{ 
    int length; 
    unsigned char *value; 
} Sample; 

extern "C" __declspec(dllexport) void __stdcall helloWorld(Sample *sample) 
{ 
    MessageBoxA(NULL, (LPCSTR) sample->value, (LPCSTR) sample->value, 0); 
} 

C#:

class Program 
{ 
    [StructLayout(LayoutKind.Sequential, Pack = 1)] 
    private class Sample 
    { 
     public Int32 length; 
     public String value; 
    } 

    [DllImport("C:\\Users\\Kep\\Documents\\Visual Studio 2010\\Projects\\SODLL\\Debug\\DLL.dll")] 
    private static extern void helloWorld(Sample sample); 

    static void Main(string[] args) 
    { 
     Sample s = new Sample(); 
     s.length = 10; 
     s.value = "Huhu"; 
     helloWorld(s); 
    } 
} 

重要的是要将它标记为类,而不是C#中的结构。

由此,您还可以使用构造等在C#:

class Program 
{ 
    [StructLayout(LayoutKind.Sequential, Pack = 1)] 
    private class Sample 
    { 
     public Int32 length; 
     public String value; 

     public Sample(String s) 
     { 
      length = s.Length; 
      value = s; 
     } 
    } 

    [DllImport("C:\\Users\\Kep\\Documents\\Visual Studio 2010\\Projects\\SODLL\\Debug\\DLL.dll")] 
    private static extern void helloWorld(Sample sample); 

    static void Main(string[] args) 
    { 
     Sample s = new Sample("Huhu"); 
     helloWorld(s); 
    } 
} 
+0

C#中的值类型可以有构造函数;另外,将值类型更改为引用类型以获得互操作指针行为看起来像是矫枉过正,恕我直言。 – 2012-02-17 05:42:42

+0

其实我想读的只是一个文件中的字节,任何想法如何?将byte []转换为unsigned char *? – shawn 2012-02-17 07:53:11

3

要传递的指针值类型为P /调用功能只是声明参数作为refout。这暗示需要参考的参数并传递:

[DllImport("C:\\CTestDLL.dll")] 
private static extern void helloWorld(ref Sample sample); 

由于您的结构中有一个数组,你必须照顾正确声明用于这项工作。我强烈建议,如果可能的话,你把它变成一个固定长度的数组,因为它会使封送很多很多快乐:

typedef struct 
{ 
    int length; 
    unsigned char value[MAX_LENGTH]; 
} Sample; 

这将成为:

public struct Sample 
{ 
    int length; 
    [MarshalAs(UnmanagedType.LPArray, SizeConst = MAX_LENGTH)] byte[] value; 
} 

如果是不可能的,那么运行时无法知道有多少数据要回传;在这种情况下,你可能不得不求助于你的数据的手动编组:

public struct Sample 
{ 
    int length; 
    IntPtr value; 
} 

var sample = new Sample(); 
helloWorld(ref sample); 

byte[] value = new byte[sample.length]; 
Marshal.Copy(sample.value, value, 0, sample.Length); 

然而,根据另一种回答您的评论,它看起来像你只需要得到字节块出来的C DLL到C#中。为此,你根本不需要一个结构,消除它会简化很多事情。如果你只是想传入一个数组并将其填入并返回给你,请尝试如下所示:

(这里假定你可以控制C和C#代码库;如果没有,那么ref的建议是的方式来完成你所需要的)

// In C# code: 
[DllImport(@"C:\CTestDll.dll")] 
private static extern void helloWorld(
    int length, 
    [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] byte[] buffer); 

byte[] buffer = new byte[1024 * 8]; 
helloWorld(1024 * 8, buffer); 


// In C: 
__declspec(dllexport) void __stdcall helloWorld(int, unsigned char *); 

void helloWorld(int cb, unsigned char *buf) 
{ 
    memcpy(buf, DATASRC, cb); 
} 

在C,unsigned char类型,顾名思义,大小相同的C#字节 - 8位,没有任何迹象。在C#中,char实际上是两个字节。运行时会自动将unsigned char *“转换”为byte[]。 (根本没有真正的转换;它们只是同一类型的不同名称。)

+0

我有一个C结构,其中有一个无符号的char *作为成员,并且我在C#代码中使用byte [],为什么我得到的值最终错了?该值是从文件btw中读取的。 – shawn 2012-02-19 05:06:33

+0

啊,是的,我忘记了在结构内处理数组有点奇怪。让我更新我的答案。 – 2012-02-19 14:35:02