80

我有一个 C 库,想从 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);
    }
4

5 回答 5

112

对于Linux,该示例将是:

1)创建一个C文件,libtest.c内容如下:

#include <stdio.h>

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

这是一个简单的 printf 伪包装器。但表示C您要调用的库中的任何函数。如果你有一个C++函数,不要忘记放 externC以避免破坏名称。

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) 除非您在“/usr/lib”之类的标准库路径中拥有库 libtest.so,否则您可能会看到 System.DllNotFoundException,要解决此问题,您可以将 libtest.so 移动到 /usr/lib,或者更好的是,只需将您的 CWD 添加到库路径:export LD_LIBRARY_PATH=pwd

来自这里的学分

编辑

对于Windows,它并没有太大的不同。从这里*.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);
            }
    }
于 2012-07-11T03:54:53.657 回答
39

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

您可以使用 P/Invoke 从 C# 中直接调用 C 函数。
下面是关于创建包含 C dll 的 C# 库的简短方法。

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

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

    • 在 Configuration Properties > C/C++ > Precompiled headers 中,将 Precompiled Headers 设置为:“Not using Precompiled Headers”
    • 在同一个 C/C++ 分支中,转到 Advanced,将 Compile As 更改为:“Compile as C code (/TC)”
    • 现在在 Linker 分支中,转到 General,并将 Output File 更改为:“$(SolutionDir)Wrapper\$(ProjectName).dll”,这会将构建的 C DLL 复制到 C# 项目根目录。

图书馆.h

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

图书馆.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 项目,转到“项目依赖项”并选中“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

结果:
在控制台应用程序中测试包装库

于 2012-07-21T15:57:42.747 回答
4

好的,打开 VS 2010,转到File -> New -> Project -> Visual C++ -> Win32 -> Win32 Project并为其命名(在我的情况下为 HelloWorldDll),然后在Application Type下的窗口中选择'DLL '并在Additonal Options下选择'Empty Project'

现在转到您的解决方案资源管理器选项卡,通常位于 VS 窗口的右侧,右键单击Source Files -> Add Item -> C++ file (.cpp)并为其命名(在我的情况下为 HelloWorld)

然后在新类中粘贴以下代码:

#include <stdio.h>

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

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

现在,让我们创建我们的 C# 应用程序,它将访问 dll,转到文件 -> 新建 -> 项目 -> Visual C# -> 控制台应用程序并为其命名(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 的您好!

希望这可以解决您的一些问题。

参考资料

于 2012-07-20T20:46:00.737 回答
2

注意:以下代码适用于 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);
}
于 2019-10-02T21:51:03.387 回答
0

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

我不认为你可以说一般来说哪个是更好的方法,老实说我不喜欢他们中的任何一个。

于 2020-12-02T14:13:39.677 回答