从C#程序调用C DLL

时间:2012-02-17 04:42:56

标签: c# c dll

我需要将指向结构的指针传递给我的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();
        }
    }
}

3 个答案:

答案 0 :(得分:3)

要将指向值类型的指针传递给P / Invoke函数,只需将参数声明为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中,根据定义,无符号字符与C#字节大小相同--8位,无符号。在C#中,char实际上是两个字节。运行时会自动将unsigned char *“转换”为byte[]。 (根本没有真正的转换;它们只是同一类型的不同名称。)

答案 1 :(得分: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);
    }
}

答案 2 :(得分:1)

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

有几种传递和转换类型的方法 正如迈克尔·伊登菲尔德指出的那样,你通常可以只使用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。