【教程】关于C#中调用Cpp编写的dll

dll文件
dll文件用的图片

前几天写了一篇文章,讲了如何建立dll以及如何调用dll,文章中使用的是C/C++,今天就说说怎么使用C#调用C++的dll。


首先建立一个C#工程。然后再原文件夹中的Debug文件夹中放入我们做好的dll,这里我们使用前一篇文章中建立的dll:“basic_dll.dll”,然后就是写代码。

在C#中如果要调用dll,就要using一个空间:System.Runtime.InteropServices,包含进来就好了。


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;

这里包含进了一些无关的东西,不用管它,我一建立工程就有这些了,懒得删-_-……

如果要载入dll,就要在声明方法之前加上


[DllImport("basic_dll.dll", EntryPoint = "add",CallingConvention = CallingConvention.Cdecl)]

其中,“basic_dll.dll”是我们要引入的dll的名字,要么它在程序的同一目录下,要么在系统中已经注册过,后面的“EntryPoint”是dll的入口函数,也就是我们要导入的函数名称,再后面的CallingConvention是调用约定,如果不加上这个调用约定的话,会导致编译时出现”对 PInvoke 函数“xxFunction()”的调用导致堆栈不对称。原因可能是托管的 PInvoke 签名与非托管的目标签名不匹配。请检查 PInvoke 签名的调用约定和参数与非托管的目标签名是否匹配“这样的错误,原因一会说。

然后的然后就是声明一下我们的函数了,在这里同样只有一个add(int, int)   -_-

一下是一段简单到要死的源代码


namespace Csharp调用
{

class Program
{
[DllImport("basic_dll.dll", EntryPoint = "add",CallingConvention = CallingConvention.Cdecl)]
public static extern int add(int a, int b);

static void Main(string[] args)
{
Console.WriteLine(add(3, 5));
Console.ReadKey();
}
}
}

这段代码极其简单以至于我就跳过解读了,然后说说关于刚才提到的堆栈不对称的问题。

众所周知,在c++WIN32程序中有三种calling convention(调用约定):__cdecl, __stdcall, __fastcall,默认为__cdecl。而c#中默认为CallingConvention =CallingConvention.Winapi,如果不对调用约定进行说明的话,会出现调用约定不一致的错误。当然,在dll中,要传出的方法应该放在extern ”C“中,不然会因为C++更改函数名而导致调用错误。

【教程】dll动态链接库学习第一课:使用C++创建并调用dll

很长时间以来都总想着要写技术文章,今天就先了了这个想法,写一篇关于动态链接库学习的感想。

关于dll动态链接库的介绍我就不说了,百度上360百科上写的很详细。之前看过《Windows核心编程(第五版)》,但是无奈里面写的有些晦涩。


 

废话少说,先说说怎么写一个dll。

先打开vs或者vc,新建一个Win32项目(具体的方法:新建项目->Visual C++ -> Win32 ->Win32项目),然后名称和位置填上(注意名称中最好只包括英文符号,不然极有可能出现错误)。我新建的名字是basic_dll

然后在弹出的框中点下一步,然后在“应用程序类型”中选择“DLL”,在“附加选项”中,勾上“导出符号”,完成即可。

然后剩下的事情就是编写你的文件了。在这里我只在里面放一个

int add(int, int);

相信大家看到名字也就能猜出来他是干什么的了!没错,就是返回两个int的和。具体怎么做呢?

在“basic.h”(<工程名字>就是你刚刚填写的名称,一下均遵从次约定)的最后,添加上这样一句话:

BASCI_DLL_API int add(int, int);

这句话中,BASIC_DLL_API的定义参见上面的#ifdef块。然后打开basic_dll.cpp,在里面添加定义

BASIC_DLL_API int add(int a, int b)

{

int ans = a + b;

return ans;

}

然后我们似乎大功告成啦!先生成……

啊嘞?怎么提示“无法启动程序”?

如果看到这个的话,你一定是点了调试(按F5或者是那个绿色的三角),别担心。首先,dll本身是不能独立运行的,其次,我只让你生成,没让你调试啊!!!给我好好看着!

咳咳……当你看到“1成功”的时候,就说明已经生成了你要的dll。此时还会生成一个lib文件。

然后到你的文件目录中去寻找 basic_dll.dll、basic_dll.lib、basic_dll.h 这三个文件,一会我们要用。


 

然后咱们再说说怎么用这个dll和这个lib。

首先你得新建一个控制台程序吧~步骤我就不细说了。这里假定我们新建的工程名称是“basic_exe”。

然后是准备工作。把上面的三个文件,拷贝到你的工程目录中的basic_exe文件夹中,也就是说如果你的工程文件夹是“F:\basic_exe”,那么你应该放在“F:\basic_exe\basic_exe”中,然后再把basic_dll.dll放在“F:\basic_dll\Debug”中一份,以免一会运行的时候提示找不到dll。

然后开始写我们的程序啦。先静态调用,也就是调用lib。

先把刚才的“basic_dll.h”包含进来,当然在这里要在工程中引入这个文件了~

然后下面我们开始编写代码

#include <iostream>

#include "basic_dll.h"

using namespace std;

int main()

{

int a = 1;

int b = 2;

int ans = add(a, b);

cout << ans << endl;

return 0;

}

然后调试……

咦?怎么有一个错误?1个无法解析的外部命令?这是什么鬼?

不用担心,这是因为我们虽然告诉了编译器有一个头文件,但是没有告诉它库文件.lib在哪里啊~我们只需要把它包含进来即可。项目属性-》配置属性-》连接器-》输入-》在右边的附加依赖项中加入你的lib的地址就可以了。

但是似乎还有?

这是因为—–我们没有加上extern “C”。

我们打开刚才写的dll,然后在basic_dll.h中的函数add外面,加上extern,也就是变成这样


extern "C"
{
BASIC_DLL_API int add(int a, int b);
}

然后就OK了~

然后再调试……OK!


然后再说说动态调用dll的。

先包含进库的说


#include <Windows.h>

#include <iostream>

#include "basic_dll.h"

当然这里你也要把basic_dll.h包含进工程

然后是正文。


using namespace std;

typedef int(*MYPROC) (int, int)

int CallDll()

{

HINSTANCE hinst;

MYPROC myproc;

hinst = LoadLibrary(L"basic_dll.dll");

if (hinst != NULL)

{

myproc = (MYPROC)GetProcAddress(hinst, "add");

int a = 0;

a = myproc(3, 4);

cout << "3 + 4 =" << a << endl;

FreeLibrary(hinst);

}

else

{

cout << "加载dll失败,因为hinst空" << endl;

}

return 0;

}

int _tmain(int argc, _TCHAR* argv[])

{

CallDll();

return 0;

}

大功告成!调试……

然后我就不说了~大概就到这里,千里之行属于足下,这是dll学习的第一课~