C#调用C++编译的DLL详解


C#调用C++编译的DLL详解

每种编程语言调用DLL的方法都不尽相同,在此只对用C#调用DLL的方法进行介绍。首先,您需要了解什么是托管,什么是非托管。一般可以认为:非托管代码主要是基于win 32平台开发的DLL,activeX的组件,托管代码是基于.net平台开发的。如果您想深入了解托管与非托管的关系与区别,及它们的运行机制,请您自行查找资料,本文件在此不作讨论。

(一) 调用DLL中的非托管函数一般方法

首先,应该在C#语言源程序中声明外部方法,其基本形式是:

[DLLImport(“DLL文件”)]

修饰符 extern 返回变量类型 方法名称 (参数列表)

其中:

DLL文件:包含定义外部方法的库文件。
修饰符: 访问修饰符,除了abstract以外在声明方法时可以使用的修饰符。
返回变量类型:在DLL文件中你需调用方法的返回变量类型。
方法名称:在DLL文件中你需调用方法的名称。
参数列表:在DLL文件中你需调用方法的列表。

注意:需要在程序声明中使用System.Runtime.InteropServices命名空间。

DllImport只能放置在方法声明上。

DLL文件必须位于程序当前目录或系统定义的查询路径中(即:系统环境变量中Path所设置的路径)。
返回变量类型、方法名称、参数列表一定要与DLL文件中的定义相一致。
若要使用其它函数名,可以使用EntryPoint属性设置,如:

[DllImport("user32.dll", EntryPoint="MessageBoxA")]

static extern int MsgBox(int hWnd, string msg, string caption, int type);


其它可选的 DllImportAttribute 属性:

CharSet 指示用在入口点中的字符集,如:CharSet=CharSet.Ansi;

SetLastError 指示方法是否保留 Win32"上一错误",如:SetLastError=true;

ExactSpelling 指示 EntryPoint 是否必须与指示的入口点的拼写完全匹配,如:ExactSpelling=false;

PreserveSig指示方法的签名应当被保留还是被转换, 如:PreserveSig=true;

CallingConvention指示入口点的调用约定, 如:CallingConvention=CallingConvention.Winapi;

此外,关于“数据封送处理”及“封送数字和逻辑标量”请参阅其它一些文章[2]。

C#例子:

1.       启动VS.NET,新建一个项目,项目名称为“Tzb”,模板为“Windows 应用程序”。

2.       在“工具箱”的“ Windows 窗体”项中双击“Button”项,向“Form1”窗体中添加一个按钮。

3.       改变按钮的属性:Name为 “B1”,Text为 “用DllImport调用DLL弹出提示框”,并将按钮B1调整到适当大小,移到适当位置。

4.       在类视图中双击“Form1”,打开“Form1.cs”代码视图,在“namespace Tzb”上面输入“using System.Runtime.InteropServices;”,以导入该命名空间。

5.       在“Form1.cs[设计]”视图中双击按钮B1,在“B1_Click”方法上面使用关键字 static 和 extern 声明方法“MsgBox”,将 DllImport 属性附加到该方法,这里我们要使用的是“user32.dll”中的“MessageBoxA”函数,具体代码如下:

[DllImport("user32.dll", EntryPoint="MessageBoxA")]

static
extern int MsgBox(int hWnd, string msg, string caption, int type);


然后在“B1_Click”方法体内添加如下代码,以调用方法“MsgBox”:

MsgBox(0," 这就是用 DllImport 调用 DLL 弹出的提示框哦! "," 挑战杯 ",0x30);


6.按“F5”运行该程序,并点击按钮B1,便弹出如下提示框:


(二)     动态装载、调用DLL中的非托管函数

在上面已经说明了如何用DllImport调用DLL中的非托管函数,但是这个是全局的函数,假若DLL中的非托管函数有一个静态变量S,每次调用这个函数的时候,静态变量S就自动加1。结果,当需要重新计数时,就不能得出想要的结果。下面将用例子说明:

1.        DLL的创建

1)        启动Visual C++ 6.0;

2)        新建一个“Win32 Dynamic-Link Library”工程,工程名称为“Count”;

3)        在“Dll kind”选择界面中选择“A simple dll project”;

4)        打开Count.cpp,添加如下代码:

// 导出函数,使用“ _stdcall ” 标准调用
extern "C" _declspec(dllexport)int _stdcall count(int init);
int _stdcall count(int init)
{
   //count 函数,使用参数 init 初始化静态的整形变量 S ,并使 S 自加 1 后返回该值
   static int S=init;

   S++;

   return S;
}

5)        按“F7”进行编译,得到Count.dll(在工程目录下的Debug文件夹中)。

2.         用DllImport调用DLL中的count函数

1)        打开项目“Tzb”,向“Form1”窗体中添加一个按钮。

2)        改变按钮的属性:Name为 “B2”,Text为 “用DllImport调用DLL中count函数”,并将按钮B1调整到适当大小,移到适当位置。

3)        打开“Form1.cs”代码视图,使用关键字 static 和 extern 声明方法“count”,并使其具有来自 Count.dll 的导出函数count的实现,代码如下:

[DllImport("Count.dll")]

static extern int count(int init);

4)        在“Form1.cs[设计]”视图中双击按钮B2,在“B2_Click”方法体内添加如下代码:
MessageBox.Show(" 用 DllImport 调用 DLL 中的 count 函数, n 传入的实参为 0 ,得到的结果是: "+count(0).ToString()," 挑战杯 ");

MessageBox.Show(" 用 DllImport 调用 DLL 中的 count 函数, n 传入的实参为 10 ,得到的结果是: "+count(10).ToString()+"n 结果可不是想要的 11 哦!!! "," 挑战杯 ");

MessageBox.Show(" 所得结果表明: n 用 DllImport 调用 DLL 中的非托管 n 函数是全局的、静态的函数!!! "," 挑战杯 ");

5)        把Count.dll复制到项目“Tzb”的binDebug文件夹中,按“F5”运行该程序,并点击按钮B2,便弹出如下三个提示框:

第1个提示框显示的是调用“count(0)”的结果,第2个提示框显示的是调用“count(10)”的结果,由所得结果可以证明“用DllImport调用DLL中的非托管函数是全局的、静态的函数”。所以,有时候并不能达到我们目的,因此我们需要使用下面所介绍的方法:C#动态调用DLL中的函数。

3.        C#动态调用DLL中的函数

因为C#中使用DllImport是不能像动态load/unload assembly那样,所以只能借助API函数了。在kernel32.dll中,与动态库调用有关的函数包括[3]:

①LoadLibrary(或MFC 的AfxLoadLibrary),装载动态库。

②GetProcAddress,获取要引入的函数,将符号名或标识号转换为DLL内部地址。

③FreeLibrary(或MFC的AfxFreeLibrary),释放动态链接库。

它们的原型分别是:

HMODULE LoadLibrary(LPCTSTR lpFileName);

FARPROC GetProcAddress(HMODULE hModule, LPCWSTR lpProcName);

BOOL FreeLibrary(HMODULE hModule);


现在,我们可以用IntPtr hModule=LoadLibrary(“Count.dll”);来获得Dll的句柄,用IntPtr farProc=GetProcAddress(hModule,”_count@4”);来获得函数的入口地址。

但是,知道函数的入口地址后,怎样调用这个函数呢?因为在C#中是没有函数指针的,没有像C++那样的函数指针调用方式来调用函数,所以我们得借助其它方法。经过研究,发现我们可以通过结合使用System.Reflection.Emit及System.Reflection.Assembly里的类和函数达到我们的目的。为了以后使用方便及实现代码的复用,我们可以编写一个类。

1)        dld类的编写:

1.       打开项目“Tzb”,打开类视图,右击“Tzb”,选择“添加”-->“类”,类名设置为“dld”,即dynamic loading dll 的每个单词的开头字母。

2.       添加所需的命名空间及声明参数传递方式枚举:

using System.Runtime.InteropServices; // 用 DllImport 需用此 命名空间
using System.Reflection; // 使用 Assembly 类需用此 命名空间
using System.Reflection.Emit; // 使用 ILGenerator 需用此 命名空间

//在“public class dld”上面添加如下代码声明参数传递方式枚举:

/// <summary>
/// 参数传递方式枚举 ,ByValue 表示值传递 ,ByRef 表示址传递
/// </summary>

public enum ModePass
{
   ByValue = 0x0001, 
   ByRef = 0x0002
}


3.       声明LoadLibrary、GetProcAddress、FreeLibrary及私有变量hModule和farProc:

/// <summary>
/// 原型是 :HMODULE LoadLibrary(LPCTSTR lpFileName);
/// </summary>
/// <param name="lpFileName">DLL 文件名 </param>
/// <returns> 函数库模块的句柄 </returns>
[DllImport("kernel32.dll")]
static extern IntPtr LoadLibrary(string lpFileName);

/// <summary>
/// 原型是 : FARPROC GetProcAddress(HMODULE hModule, LPCWSTR lpProcName);
/// </summary>
/// <param name="hModule"> 包含需调用函数的函数库模块的句柄 </param>
/// <param name="lpProcName"> 调用函数的名称 </param>
/// <returns> 函数指针 </returns>

[DllImport("kernel32.dll")]
static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName);

/// <summary>
/// 原型是 : BOOL FreeLibrary(HMODULE hModule);
/// </summary>
/// <param name="hModule"> 需释放的函数库模块的句柄 </param>
/// <returns> 是否已释放指定的 Dll</returns>

[DllImport("kernel32",EntryPoint="FreeLibrary",SetLastError=true)]
static extern bool FreeLibrary(IntPtr hModule);

/// <summary>
/// Loadlibrary 返回的函数库模块的句柄
/// </summary>
private IntPtr hModule=IntPtr.Zero;

/// <summary>
/// GetProcAddress 返回的函数指针
/// </summary>
private IntPtr farProc=IntPtr.Zero;


4.       添加LoadDll方法,并为了调用时方便,重载了这个方法:

/// <summary>
/// 装载 Dll
/// </summary>
/// <param name="lpFileName">DLL 文件名 </param>
public void LoadDll(string lpFileName)
{
   hModule=LoadLibrary(lpFileName);
   if(hModule==IntPtr.Zero)
   throw(new Exception(" 没有找到 :"+lpFileName+"." ));
}

//若已有已装载Dll的句柄,可以使用LoadDll方法的第二个版本:
public void LoadDll(IntPtr HMODULE)
{
   if(HMODULE==IntPtr.Zero)
   
   throw(new Exception(" 所传入的函数库模块的句柄 HMODULE 为空 ." ));
   
   hModule=HMODULE;
}


5.       添加LoadFun方法,并为了调用时方便,也重载了这个方法,方法的具体代码及注释如下:

/// <summary>
/// 获得函数指针
/// </summary>
/// <param name="lpProcName"> 调用函数的名称 </param>
public void LoadFun(string lpProcName)
{
// 若函数库模块的句柄为空,则抛出异常

if(hModule==IntPtr.Zero)

throw(new Exception(" 函数库模块的句柄为空 , 请确保已进行 LoadDll 操作 !"));

// 取得函数指针
farProc = GetProcAddress(hModule,lpProcName);

// 若函数指针,则抛出异常
if(farProc==IntPtr.Zero)

throw(new Exception(" 没有找到 :"+lpProcName+" 这个函数的入口点 "));
}

/// <summary>
/// 获得函数指针
/// </summary>
/// <param name="lpFileName"> 包含需调用函数的 DLL 文件名 </param>
/// <param name="lpProcName"> 调用函数的名称 </param>
public void LoadFun(string lpFileName,string lpProcName)
{

// 取得函数库模块的句柄
hModule=LoadLibrary(lpFileName);

// 若函数库模块的句柄为空,则抛出异常
if(hModule==IntPtr.Zero)

throw(new Exception(" 没有找到 :"+lpFileName+"." ));

// 取得函数指针
farProc = GetProcAddress(hModule,lpProcName);

// 若函数指针,则抛出异常
if(farProc==IntPtr.Zero)
throw(new Exception(" 没有找到 :"+lpProcName+" 这个函数的入口点 "));

}


6. 添加UnLoadDll及Invoke方法,Invoke方法也进行了重载:

/// <summary>
/// 卸载 Dll
/// </summary>
public void UnLoadDll()
{
   
   FreeLibrary(hModule);
   
   hModule=IntPtr.Zero;
   
   farProc=IntPtr.Zero;
   
}

文章转自 cnblogs  点滴积累-实践梦想
http://www.cnblogs.com/my-blog/archive/2008/11/14/1333454.html

本文来源:
版权声明:本文为开发框架文库发布内容,转载请附上原文出处连接
C/S框架网
上一篇:[转帖]C#如何实现DataGridView到DataGridView的拖拽
下一篇:[转帖]C# const和static readonly有什么区别?
评论列表

发表评论

评论内容
昵称:
关联文章

C#调用C++编译DLL详解
Delphi程序调用C#.Net编译DLL并打开窗体(详解)
C#调用Delphi编译DLL函数库返回大文本数据
C# 动态编译调用WebService方法
C#.Net 调用Delphi 编译ActiveX COM组件(原)
C# WebService异步处理/异步调用详解
通过反射,调用DLL程序集某个类静态方法打开窗体
购买框架是购买源码还是编译dll
C#直接使用DllImport外部Dll方法
C#代码混淆及反编译
C#代码混淆及反编译(转)
C#.NET通过CodeDom.Compiler动态编译计算表达式
C#.Net 类同名方法重载调用顺序
C# 子窗体中调用父窗体中方法(或多窗体之间方法调用)
C#调用类成员
推荐C#.Net逆向反编译四大软件工具
VS编译生成解决方案失败,但是检查源码完全正常奇异现象
C# WebService代理类详解
WinFramework添加工程项目Project新建业务模块(DLL库)
C/S框架用到Dev组件,发布程序到用户机器时候需要那些DLL?

热门标签
.NET5 .NET6 .NET7 APP Auth-软件授权注册系统 Axios B/S B/S开发框架 Bug Bug记录 C#加密解密 C#源码 C/S CHATGPT CMS系统 CodeGenerator CSFramework.DB CSFramework.EF CSFrameworkV1学习版 CSFrameworkV2标准版 CSFrameworkV3高级版 CSFrameworkV4企业版 CSFrameworkV5旗舰版 CSFrameworkV6.0 DAL数据访问层 Database datalock DbFramework Demo教学 Demo下载 DevExpress教程 DOM EF框架 Element-UI EntityFramework ERP ES6 Excel FastReport GIT HR IDatabase IIS JavaScript LINQ MES MiniFramework MIS NavBarControl Node.JS NPM OMS ORM PaaS POS Promise API Redis SAP SEO SQL SQLConnector TMS系统 Token令牌 VS2022 VSCode VUE WCF WebApi WebApi NETCore WebApi框架 WEB开发框架 Windows服务 Winform 开发框架 Winform 开发平台 WinFramework Workflow工作流 Workflow流程引擎 版本区别 报表 踩坑日记 操作手册 代码生成器 迭代开发记录 基础资料窗体 架构设计 角色权限 开发sce 开发技巧 开发教程 开发框架 开发平台 开发指南 客户案例 快速搭站系统 快速开发平台 秘钥 密钥 权限设计 软件报价 软件测试报告 软件简介 软件开发框架 软件开发平台 软件开发文档 软件体系架构 软件下载 软著证书 三层架构 设计模式 生成代码 实用小技巧 收钱音箱 数据锁 数据同步 微信小程序 未解决问题 文档下载 喜鹊ERP 喜鹊软件 系统对接 详细设计说明书 行政区域数据库 需求分析 疑难杂症 蝇量级框架 蝇量框架 用户管理 用户开发手册 用户控件 在线支付 纸箱ERP 智能语音收款机 自定义窗体 自定义组件 自动升级程序