从C#程序调用C DLL

本文关键字:DLL 调用 程序 | 更新日期: 2023-09-27 18:24:59

我需要向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();
        }
    }
}

从C#程序调用C DLL

要将指向值类型的指针传递到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#中,字符实际上是两个字节。运行时会自动将unsigned char *"转换"为byte[]。(实际上根本没有转换;它们只是同一类型的不同名称。)

值类型的默认封送-提供了有关封送结构的一些信息
用P/Invoke调用C#中的Win32 DLL——在页面的一半多一点的地方,有一个表显示了标准非托管类型和托管类型之间的类型转换。

有几种方法可以传递和转换类型
正如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的类型以及它是否是可blitable的。

这对我有效:

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);
    }
}