1、编写一个简单的DLL
设置为导出函数,并采用C风格。函数前加extern "C" __declspec(dllexport)。定义函数在退出前自己清空堆栈,在函数前加__stdcall。
新建一个头文件,在头文件中:
/* 加入任意你想加入的函数定义*/
extern "C" _declspec(dllexport) int _stdcall add(int *x,int *y); // 声明为C编译、链接方式的外部函数
extern "C" _declspec(dllexport) int _stdcall sub(int x,int y); // 声明为C编译、链接方式的外部函数
新建一个.cpp文件
#include "mydll.h"//貌似这两个头文件的顺序不能颠倒。我试了很多次,但是不能确定。
int add(int *x,int *y)//是否可以理解为,VS2010已经默认是 _stdcall,所以函数不用添加该修饰符
{
return *x+*y;
}
int sub(int x,int y)
{
return x-y;
}
把导出函数名称变为标准名称,需加模块定义文件,就是.def文件。
内容如下:(需要注释,前面加分号就可以了,注释需要单独行)
LIBRARY "TEST"
EXPORTS
;add函数
add
;sub函数
sub
LIBRARY 库名称
EXPORTS 需要导出的各个函数名称
重新编译之后,再用Depends工具看一下,函数已经变成标准add。这个在动态加载时很有用,特别是在GetProcAddress函数寻找入库函数的时候。
2、静态调用
新建一个C#的控制台项目,
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;//添加
namespace mytest
{
class Program
{
//----------------------------------------------------------------------------------------------
[DllImport("mydll.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)]
extern static int add(ref int a, ref int b);
[DllImport("mydll.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)]
extern static int sub(int a, int b);
//--------------------------------------------------------------------------------------------------
static void Main(string[] args)
{
int a, b,c,d;
a = 1;
b = 2;
c=d= 0;
Console.WriteLine(add(ref a,ref b).ToString());
Console.WriteLine(sub(b,a).ToString());
Console.Read();
}
}
}
3、动态调用
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;//添加
namespace mytest
{
class Program
{
[DllImport("kernel32.dll")]
private extern static IntPtr LoadLibrary(String path);//path 就是dll路径 返回结果为0表示失败。
[DllImport("kernel32.dll")]
private extern static IntPtr GetProcAddress(IntPtr lib, String funcName);//lib是LoadLibrary返回的句柄,funcName 是函数名称 返回结果为0标识失败。
[DllImport("kernel32.dll")]
private extern static bool FreeLibrary(IntPtr lib);
//声明委托
[UnmanagedFunctionPointerAttribute(CallingConvention.Cdecl)]
delegate int ADD(ref int x, ref int y);
[UnmanagedFunctionPointerAttribute(CallingConvention.Cdecl)]
delegate int SUB(int x, int y);
static void Main(string[] args)
{
string dllPath = "G:\\VS2010软件学习\\c#调用C_dll\\mytest\\mytest\\mydll.dll";
string apiName1 = "add";
string apiName2 = "sub";
//使用动态加载
IntPtr hLib = LoadLibrary(dllPath);//加载函数
IntPtr apiFunction1 = GetProcAddress(hLib, apiName1);//获取函数地址
IntPtr apiFunction2 = GetProcAddress(hLib, apiName2);//获取函数地址
int i = Marshal.GetLastWin32Error();
if (apiFunction1.ToInt32() == 0)//0表示函数没找到
return;
if (apiFunction2.ToInt32() == 0)//0表示函数没找到
return;
//获取函数接口,相当于函数指针
ADD add1 = (Delegate)Marshal.GetDelegateForFunctionPointer(apiFunction1, typeof(ADD)) as ADD;
SUB sub1 = (SUB)Marshal.GetDelegateForFunctionPointer(apiFunction2, typeof(SUB));
// //调用函数
int a, b,c;
a = 1;
b = 2;
c= 0;
//add1(ref a,ref b);
c=sub1(b,a);
// //释放句柄
FreeLibrary(hLib );
Console.WriteLine(c.ToString());
//Console.WriteLine(add(ref a,ref b).ToString());
//Console.WriteLine(sub(10,2).ToString());
Console.Read();
}
}
}
注意:
C#时常需要调用C/C++DLL,当传递参数时时常遇到问题,尤其是传递和返回字符串时。VC++中主要字符串类型为:LPSTR,LPCSTR, LPCTSTR, string, CString, LPCWSTR, LPWSTR等,但转为C#类型却不完全相同。
类型对照:
C/C++----------C#
BSTR --------- StringBuilder
LPCTSTR --------- StringBuilder
LPCWSTR --------- IntPtr
handle---------IntPtr
hwnd-----------IntPtr
char *----------string
int * -----------ref int
int &-----------ref int
void *----------IntPtr
unsigned char *-----ref byte
Struct需要在C#里重新定义一个Struct
CallBack回调函数需要封装在一个委托里,delegate static extern int FunCallBack(string str);
注意在每个函数的前面加上public static extern +返回的数据类型,如果不加public ,函数默认为私有函数,调用就会出错。
在C#调用C++ DLL封装库时会出现两个问题:
1. 数据类型转换问题
2. 指针或地址参数传送问题