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


70

我有一个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);
    }

2
是的,它是有可能的,如果您使用Google进行搜索,有很多示例,但是从有限的信息中不知道您的问题是什么
Keith Nicholas

1
如果在执行X时感觉很痛,请停止执行X。重命名C ++包装器方法,以将其排除在问题之外。并发布您的代码:-)
Eric J.

我有供应商提供的ac库,该库具有ConnectToMachine(unsigned char * psig,char apt)之类的功能。我想从我的C#类中校准此函数。
Chinjoo 2012年

2
查看PInvoke
Mark Hall

3
问:是否可以从C#.Net调用C函数?A:是的,显然!问:C ++ / CLI包装器是否可行?我个人认为C ++ / CLI是可憎的。但是人们在这样的场景中使用它。我宁愿直接使用Interop(即PInvoke)-所有C#,一直到本机的非托管C / C ++。有很多教程,这并不难。当然不比从VB6调用C / C ++更困难;)恕我直言..
paulsm4

Answers:


99

该示例将针对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);
            }
    }

7
好的例子!这是使用Interop的一个很好的例子。显然是Linux(不是Windows),但是原理是一样的。这是EXACTLY我建议你到OP的办法。
paulsm4 2012年

有人可以编辑答案吗?我遇到了包含问题,但没有显示stdio.h
贡萨洛。-2012年

猜测应该起作用..但不确定如何从vs2010.so获取输出。.它仅提供.lib和.dll,在给定格式错误的情况下,如果设置为dllimport,则再次提供.dll
而又 Chinjoo 2012年

如您所见,en.csharp-online.net /…您可以使用.dll来做同样的事情
Gonzalo。-2012年

1
@Chinjoo考虑使用此codeproject.com/Articles/6912/…。您是否修改了C代码,添加了_declspec(dllexport)?如果这个例子对你的作品,我将它添加到答案
Gonzalo.-

36

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

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

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

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

    • 在“配置属性”>“ C / C ++”>“预编译头”中,将“预编译头”设置为:“不使用预编译头”
    • 在同一C / C ++分支中,转到“高级”,将“编译为”更改为:“编译为C代码(/ TC)”
    • 现在在Linker分支中,转到“常规”,然后将“输出文件”更改为:“ $(SolutionDir)Wrapper \ $(ProjectName).dll”,这会将生成的C DLL复制到C#项目根目录。

图书馆

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

图书馆

#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

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


这对我有用,但是我还必须将.NET项目的更改Properties->Build->Platform Targetx64
约瑟夫

4

好的,打开VS 2010,转到“文件”->“新建”->“项目”->“ Visual C ++”->“ Win32”->“ Win32 Project”,并为其命名(在我的情况下为HelloWorldDll),然后在“应用程序类型下面的窗口中选择“ DLL”。 ”,然后在“其他选项下选择“空项目”

现在,通常在VS窗口的右侧转到“解决方案资源管理器”选项卡,右键单击“源文件”->“添加项目”->“ C ++文件(.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您好!

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

参考文献


我无法编译C#程序,因为我的项目找不到DLL文件。因此,我在DllImport语句中使用了完整路径:[DllImport("C:\\Users\\...full path...\\HelloWorldDll\\Debug\\HelloWorldDll.dll")]
Quazi Irfan

不断获取“试图加载格式错误的程序。(来自HRESULT的异常:0x8007000B)。还有其他人遇到这个问题吗?
最多

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

0

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

我认为您通常不能说哪种方法更好,说实话,我都不喜欢这两种方法。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.