如何在C#中调用C++ DLL

24

我用Dev C++编写了一个DLL,它的名字是“DllMain.dll”,包含两个函数:HelloWorldShowMe。头文件如下:

```c++ // DllMain.h
#ifdef DLLMAIN_EXPORTS #define DLLMAIN_API __declspec(dllexport) #else #define DLLMAIN_API __declspec(dllimport) #endif
DLLMAIN_API void HelloWorld(); DLLMAIN_API void ShowMe();
```
DLLIMPORT  void HelloWorld();
DLLIMPORT void ShowMe();

而源文件看起来像这样:

DLLIMPORT void HelloWorld ()
{
  MessageBox (0, "Hello World from DLL!\n", "Hi",MB_ICONINFORMATION);
}

DLLIMPORT void ShowMe()
{
 MessageBox (0, "How are u?", "Hi", MB_ICONINFORMATION);
}

我将代码编译为DLL并从C#中调用这两个函数。C#代码如下:

[DllImport("DllMain.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void HelloWorld();

[DllImport("DllMain.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void ShowMe();
当我调用函数"HelloWorld"时,它可以正常运行并弹出一个消息框,但当我调用函数ShowMe时,会发生EntryPointNotFoundException异常。我该如何避免这个异常?是否需要在头文件中添加extern "C"

1
请问您能否发布您的C++代码? - Chris Neave
你应该将调用约定更改为 CallingConvention.StdCall - Henrik
3个回答

17
在VS 2012中,以下代码能正常运行:
#include <Windows.h>
extern "C"
{
    __declspec(dllexport) void HelloWorld ()
    {
        MessageBox (0, L"Hello World from DLL!\n", L"Hi",MB_ICONINFORMATION);
    }
    __declspec(dllexport) void ShowMe()
    {
        MessageBox (0, L"How are u?", L"Hi", MB_ICONINFORMATION);
    }
}

注意: 如果我移除 extern "C",会出现异常。


19
这不解答如何从C#调用C++(即名字混淆的代码)函数。 - Hi-Angel
1
@Hi-Angel 我不知道什么是混淆代码,也不知道如何从C#中调用它。如果你想补充我的答案,请发表评论或者你可以发布自己的答案。 - atoMerz
@atoMerz 我想知道答案:D 嗯,根据我昨天找到的资料——因为我正在寻找它——唯一的方法是在dll中找到混淆的名称,并将这些名称写入而不是通常的名称。但当然它不会是可移植的,因为名称混淆没有标准化,所以GCC的名称混淆与Visual Studio的名称混淆不兼容,两者都与ICC不兼容,等等。如果您想同时使用两个编译器,可能首先要尝试一个编译器的函数名称,捕获无此函数的情况的异常,然后尝试下一个编译器的函数名称,类似于这样。 - Hi-Angel
@atoMerz 顺便说一下,我觉得你解决了这个问题,只是通过指令 extern "C"{} 从对象文件中删除名称混淆,但你仍然不知道名称混淆是什么:D 在 C++ 编译器中,不能将函数命名为像“HelloWorld”这样的对象,因为可能存在两个同名但接收不同参数类型的函数。名称混淆就是为了解决这个问题:它根据自己的算法在函数名后附加一些符号。而这些算法(以及生成的名称)在编译器中是不同的。 - Hi-Angel
据我所知,C++ DLL在不同的平台上是不可移植的。当我开始学习dll/so时,我了解到C++ DLL/so在不同编译器之间是不可移植的。因此,我被引导使用extern "C"{}将签名转换为C代码,这样就不太可能遇到可移植性问题。直到昨天,我还不知道为什么会出现这种情况,但感谢您的评论和一点研究,我现在想我知道了。 - atoMerz
显示剩余4条评论

8
using System;
using System.Runtime.InteropServices;

namespace MyNameSpace
{
    public class MyClass
    {
        [DllImport("DllMain.dll", EntryPoint = "HelloWorld")]
        public static extern void HelloWorld();

        [DllImport("DllMain.dll", EntryPoint = "ShowMe")]
        public static extern void ShowMe();
    }
}

1
好的,我已经修改了代码,问题已经解决了。非常感谢。 - user1333098
2
它无法工作,因为动态库中的“HelloWorld”已被混淆。 - Hi-Angel
@mjb 你实际上需要“EntryPoint”吗?还是它是隐含的?我见过一些不使用它的代码。那是不好的事情吗? - progLearner
1
@progLearner,“您可以使用DllImportAttribute.EntryPoint字段按名称或序数指定DLL函数。如果方法定义中的函数名称与DLL中的入口点相同,则无需使用EntryPoint字段显式标识函数。” https://learn.microsoft.com/en-us/dotnet/framework/interop/specifying-an-entry-point#renaming-a-function-in-c-and-c - paretech

3

有用的东西:

  • 在.h文件中使用:extern "C" { function declarations here in h file } 可以禁用C++名称编码,这样c#就能找到函数。

  • 在C声明中使用__stdcall或在C#声明中使用CallingConvention.Cdecl。

  • 也许可以使用BSTR/_bstr_t作为字符串类型并使用其他vb类型。http://support.microsoft.com/kb/177218/EN-US

  • 下载“PInvoke Interop助手”https://clrinterop.codeplex.com/releases/view/14120,将.h文件中的函数声明粘贴到第3个选项卡中=c#声明。将 替换为dll文件名。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接