是否可以从C#.Net调用C函数

时间:2012-07-11 03:41:51

标签: c c#-4.0 interop

我有一个C lib,想从C#应用程序调用这个库中的函数。我尝试通过添加C lib文件作为链接器输入并将源文件添加为附加依赖项来在C lib上创建C ++ / CLI包装器。

有没有更好的方法来实现这一目标,因为我不确定如何将C输出添加到c#应用程序。

我的C代码 -

__declspec(dllexport) unsigned long ConnectSession(unsigned long handle,
                            unsigned char * publicKey,
                            unsigned char   publicKeyLen);

我的CPP包装 -

long MyClass::ConnectSessionWrapper(unsigned long handle,
                                unsigned char * publicKey,
                                unsigned char   publicKeyLen)
    {
        return ConnectSession(handle, publicKey, publicKeyLen);
    }

5 个答案:

答案 0 :(得分:77)

示例将是 Linux

1)使用以下内容创建C个文件libtest.c

#include <stdio.h>

void print(const char *message)
{
  printf("%s\\n", message);
}

这是printf的一个简单的伪包装器。但代表您要调用的库中的任何C函数。如果您有C++函数,请不要忘记使用extern C来避免名称错误。

2)创建C#文件

using System;

using System.Runtime.InteropServices;

public class Tester
{
        [DllImport("libtest.so", EntryPoint="print")]

        static extern void print(string message);

        public static void Main(string[] args)
        {

                print("Hello World C# => C++");
        }
}

3)除非你的库libtest.so在像“/ usr / lib”这样的标准库路径中,你可能会看到一个System.DllNotFoundException,为了解决这个问题,你可以将你的libtest.so移动到/ usr / lib,或者更好的是,只需将CWD添加到库路径:export LD_LIBRARY_PATH=pwd

来自here

学分

修改

对于 Windows ,它没有太大的不同。 以here为例,您只需在*.cpp文件中附上extern "C"方法 像

这样的东西
extern "C"
{
//Note: must use __declspec(dllexport) to make (export) methods as 'public'
      __declspec(dllexport) void DoSomethingInC(unsigned short int ExampleParam, unsigned char AnotherExampleParam)
      {
            printf("You called method DoSomethingInC(), You passed in %d and %c\n\r", ExampleParam, AnotherExampleParam);
      }
}//End 'extern "C"' to prevent name mangling

然后,编译,并在您的C#文件中执行

[DllImport("C_DLL_with_Csharp.dll", EntryPoint="DoSomethingInC")]

public static extern void DoSomethingInC(ushort ExampleParam, char AnotherExampleParam);

然后只使用它:

using System;

    using System.Runtime.InteropServices;

    public class Tester
    {
            [DllImport("C_DLL_with_Csharp.dll", EntryPoint="DoSomethingInC")]

    public static extern void DoSomethingInC(ushort ExampleParam, char AnotherExampleParam);

            public static void Main(string[] args)
            {
                    ushort var1 = 2;
                    char var2 = '';  
                    DoSomethingInC(var1, var2);
            }
    }

答案 1 :(得分:23)

更新 - 2019年2月22日:由于这个答案已经得到了不少赞成,我决定用更好的方法来调用C方法来更新它。以前我曾建议使用unsafe代码,但安全且正确的方法是使用MarshalAs属性将.NET string转换为char*。此外,在VS2017中不再有Win32项目,您可能必须创建一个Visual C ++ DLL或空项目并进行修改。谢谢!

您可以使用P / Invoke直接从C#调用C函数 这是创建包含C dll的C#lbrary的简短方法。

  1. 创建一个新的C#Library项目(我称之为“Wrapper”)
  2. 将Win32项目添加到解决方案中,将应用程序类型设置为:DLL(我称之为“CLibrary”)

    • 您可以删除所有其他cpp / h文件,因为我们不需要它们
    • 将CLibrary.cpp文件重命名为CLibrary.c
    • 添加CLibrary.h头文件
  3. 现在我们需要配置CLibrary项目,右键单击它并转到属性,然后选择配置:“所有配置”

    • 在配置属性&gt; C / C ++&gt;预编译头,将预编译头设置为:“不使用预编译头”
    • 在同一个C / C ++分支中,转到Advanced,将Compile as更改为:“Compile as C code(/ TC)”
    • 现在在Linker分支中,转到General,并将Output File更改为:“$(SolutionDir)Wrapper \ $(ProjectName).dll”,这会将构建的C DLL复制到C#项目根目录。
  4. <强> CLibrary.h

    __declspec(dllexport) unsigned long ConnectSession(unsigned long   handle,
                                                       unsigned char * publicKey,
                                                       unsigned char   publicKeyLen);
    

    <强> CLibrary.c

    #include "CLibrary.h"
    
    unsigned long ConnectSession(unsigned long   handle,
                                 unsigned char * publicKey,
                                 unsigned char   publicKeyLen)
    {
        return 42;
    }
    
    • 右键单击CLibrary项目,构建它,以便我们在C#项目目录中获取DLL
    • 右键单击C#Wrapper项目,添加现有项目,添加CLibrary.dll
    • 单击CLibrary.dll,转到属性窗格,将“复制到输出目录”设置为“始终复制”

    让Wrapper项目依赖于CLibrary是一个好主意,所以首先构建CLibrary,你可以通过右键单击Wrapper项目,转到“Project Dependencies”并检查“CLibrary”来实现。 现在为实际的包装代码:

    <强> ConnectSessionWrapper.cs

    using System.Runtime.InteropServices;
    
    namespace Wrapper
    {
        public class ConnectSessionWrapper
        {
            [DllImport("CLibrary.dll", CallingConvention = CallingConvention.Cdecl)]
            static extern uint ConnectSession(uint handle,
                [MarshalAs(UnmanagedType.LPStr)] string publicKey,
                char publicKeyLen);
    
            public uint GetConnectSession(uint handle, 
                                          string publicKey,
                                          char publicKeyLen)
            {
                return ConnectSession(handle, publicKey, publicKeyLen);
            }
        }
    }
    

    现在只需致电GetConnectSession,它就会返回42

    结果:
    Testing wrapper library in a console application

答案 2 :(得分:3)

好的,打开VS 2010,转到文件 - &gt;新 - &gt;项目 - &gt; Visual C ++ - &gt; Win32 - &gt; Win32项目并给它一个名字(在我的情况下是HelloWorldDll),然后在 Application Type 下面的窗口中选择'DLL'并在下面添加选项选择'空项目'

现在转到你的解决方案资源管理器标签,通常是VS窗口的右侧,右键单击源文件 - &gt;添加项目 - &gt; C ++文件(.cpp)并给它一个名字(在我的例子中是HelloWorld)

然后在新类中粘贴此代码:

#include <stdio.h>

extern "C"
{
  __declspec(dllexport) void DisplayHelloFromDLL()
  {
    printf ("Hello from DLL !\n");
  }
}

现在构建项目,导航到您的项目 DEBUG 文件夹后,您应该找到: HelloWorldDll.dll

现在,让我们创建我们的C#应用​​程序,它将访问dll,转到文件 - &gt;新 - &gt;项目 - &gt; Visual C# - &gt;控制台应用程序并为其命名(CallDllCSharp),现在将此代码复制并粘贴到您的主要文件中:

using System;
using System.Runtime.InteropServices;
...
        static void Main(string[] args)
        {
            Console.WriteLine("This is C# program");
            DisplayHelloFromDLL();
            Console.ReadKey();
        }

并构建程序,现在我们已经构建了两个应用程序,可以使用它们,将* .dll和 .exe(bin / debug / .exe)放在同一目录中,执行应用程序输出应该是

  

这是C#程序

     

你好DLL!

希望能解决你的一些问题。

<强>参考

答案 3 :(得分:0)

注意:下面的代码用于DLL中的多种方法。

[DllImport("MyLibc.so")] public static extern bool MyLib_GetName();
[DllImport("MyLibc.so")] public static extern bool MyLib_SetName(string name);
[DllImport("MyLibc.so")] public static extern bool MyLib_DisplayName(string name);

public static void Main(string[] args)
{
string name = MyLib_GetName();
MyLib_SetName(name);
MyLib_DisplayName(name);
}

答案 4 :(得分:0)

到目前为止,P/Invoke method已被广泛重复地描述。 我在这里缺少的是C ++ / CLI方法具有很大的优势:调用安全性。 与P / Invoke相反,在C函数中的调用就像是向天空盲目射击(如果允许此比较),因此在调用C函数时没有人会检查函数参数。 在这种情况下,使用C ++ / CLI通常意味着,要在要使用的函数原型中包含头文件。如果您使用错误的参数/太多/很少的参数调用C函数,编译器会告诉您。

我认为您通常不能说哪种方法更好,说实话,我不喜欢其中任何一个。