C#调用C++ DLL

来源:互联网 发布:淘宝二手书怎么卖 编辑:程序博客网 时间:2024/06/14 21:58

一、创建C++ myDLL工程

1.myDLL.h头文件定义

#ifndef __MYDLL_H__#define __MYDLL_H__#ifdef __cplusplusextern "C"{#endif#define ONEDLL_API __declspec(dllexport)//Demo_01ONEDLL_API int add(int a, int b);//Demo_02ONEDLL_API int __stdcall addA(int* a, int* b);//Demo_03ONEDLL_API void createInstance(void** ppInstance, int n);ONEDLL_API void instanceAddN(void* pInstance, int n);ONEDLL_API void deleteInstance(void** pInstance);#ifdef __cplusplus}#endif#endif

知识点1:

为了避免同一个头文件被包含(include)多次,C/C++中有两种宏实现方式:一种是#ifndef方式;另一种是#pragma once方式。

//方式一:#ifndef __MYDLL_H__#define __MYDLL_H__... ... // 声明、定义语句#endif//方式二:#pragma once... ... // 声明、定义语句

    #ifndef由C/C++语言标准支持,依赖于宏名字不能冲突,这不光可以保证同一个文件不会被包含多次,也能保证内容完全相同的两个文件不会被不小心同时包含。当然,缺点就是如果不同头文件的宏名不小心“撞车”,可能就会导致头文件明明存在,编译器却硬说找不到声明的状况


    #pragma once则由编译器提供保证:同一个文件不会被包含多次。注意这里所说的“同一个文件”是指物理上的一个文件,而不是指内容相同的两个文件。带来的好处是,你不必再费劲想个宏名了,当然也就不会出现宏名碰撞引发的奇怪问题。对应的缺点就是如果某个头文件有多份拷贝,本方法不能保证他们不被重复包含。当然,相比宏名碰撞引发的“找不到声明”的问题,重复包含更容易被发现并修正。


参考资料:http://www.cppblog.com/szhoftuncun/archive/2012/03/13/35356.html


知识点2:

#ifdef __cplusplusextern "C"{#endif... ... // 声明、定义语句#ifdef __cplusplus}#endif

    __cplusplus是cpp中的自定义宏,如果定义了这个宏,则表示这是一段cpp的代码;

   extern "C"的主要作用就是为了能够正确实现C++代码调用其他C语言代码,加上extern "C"后, 会指示编译器这部分代码按C语言的进行编译,而不是C++的。


参考资料:http://blog.csdn.net/xupan_jsj/article/details/9028759

知识点3:

C 和C++ 对应不同的调用约定,产生的修饰符也各不相同,如下:

调用约定extern "C" 或 .c 文件.cpp、.cxx 或 /TPC 命名约定 (__cdecl)_test?test@@ZAXXZFastcall 命名约定 (__fastcall)@test@0?test@@YIXXZ标准调用命名约定 (__stdcall)_test@0?test@@YGXXZ

__cdecl 是C Declaration的缩写(declaration,声明),表示C语言默认的函数调用方法。

__stdcall是StandardCall的缩写,是C++的标准调用方式,经常在Windows API函数中使用。Windows .net平台默认使用__stdcall,C#默认方式亦如此。

//Demo_01ONEDLL_API int add(int a, int b);//Demo_02ONEDLL_API int __stdcall addA(int* a, int* b);//Demo_03ONEDLL_API void createInstance(void** ppInstance, int n);ONEDLL_API void instanceAddN(void* pInstance, int n);

Demo_01是使用 __cdecl

Demo_02是使用 __stdcall

Demo_03也是使用 __cdecl

知识点4:

__declspec(dllexport) 声明DLL导出函数

参考资料:https://msdn.microsoft.com/zh-cn/library/a90k134d.aspx

2.myDLL.cpp文件定义

#include "stdafx.h"#include "myDLL.h"#include <stdio.h>class MyClass{public:    MyClass(int n):num(n){};    int num;};ONEDLL_API int add(int a, int b){    return a + b;}ONEDLL_API int __stdcall addA(int* a, int* b){    (*a) ++;    (*b) ++;    int sum = *a + *b;    return sum;}ONEDLL_API void createInstance(void** ppInstance, int n){    *ppInstance = new MyClass(n);    MyClass* pInstance = (MyClass* )(*ppInstance);        printf("pInstance->num=%d\n", pInstance->num);}ONEDLL_API void instanceAddN(void* pInstance, int n){    ((MyClass* )pInstance)->num += n;        printf("pInstance->num=%d\n", ((MyClass* )pInstance)->num);}ONEDLL_API void deleteInstance(void** pInstance){    delete *pInstance;    *pInstance = NULL;}


二、创建C# invokeDLL工程

1.MyDLL.cs文件定义

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;using System.Runtime.InteropServices; namespace invokeDLL{    unsafe class MyDLL    {        [DllImport(@"E:/VS2012/myDLL/Debug/myDLL.dll", CallingConvention = CallingConvention.Cdecl)]          public static extern int add(int a, int b);        [DllImport(@"E:/VS2012/myDLL/Debug/myDLL.dll", CallingConvention = CallingConvention.StdCall)]        public static extern int addA(ref int a, ref int b);        [DllImport(@"E:/VS2012/myDLL/Debug/myDLL.dll", EntryPoint = "addA")]        public static extern int addAA(ref int a, ref int b);        [DllImport(@"E:/VS2012/myDLL/Debug/myDLL.dll", CallingConvention = CallingConvention.Cdecl)]        public static extern void createInstance(IntPtr* pInstance, int n);        [DllImport(@"E:/VS2012/myDLL/Debug/myDLL.dll", CallingConvention = CallingConvention.Cdecl)]        public static extern void instanceAddN(IntPtr pInstance, int n);        [DllImport(@"E:/VS2012/myDLL/Debug/myDLL.dll", CallingConvention = CallingConvention.Cdecl)]        public static extern void deleteInstance(IntPtr* pInstance);    }}


知识点1:

unsafe 关键字表示不安全上下文,该上下文是任何涉及指针的操作所必需的。unsafe可以用来修饰类、类的成员函数、类的全局变量,但不能用来修饰类成员函数内的局部变量。


unsafe在C#程序中的使用场合:
1)实时应用,采用指针来提高性能;
2)引用非.net DLL提供的如C++编写的外部函数,需要指针来传递该函数;
3)调试,用以检测程序在运行过程中的内存使用状况。


知识点2:

C#使用DLLImport调用DLL基本格式:

[DllImport("文件路径")]

DLLImport会按照如下顺序查找DLL文件:程序当前目录 -> System32目录 -> 环境变量Path所设置路径

也可指定DLL文件绝对路径,如:

[DllImport(@"E:/myDLL/myDLL.dll")]

DLLImport其他属性:

字段

说明

BestFitMapping

启用或禁用最佳匹配映射。

CallingConvention

指定用于传递方法参数的调用约定。 默认值为 WinAPI,该值对应于基于 32 位 Intel 的平台的 __stdcall。

CharSet

控制名称重整以及将字符串参数封送到函数中的方式。 默认值为 CharSet.Ansi。

EntryPoint

指定要调用的 DLL 入口点。

ExactSpelling

控制是否应修改入口点以对应于字符集。 对于不同的编程语言,默认值将有所不同。

PreserveSig

控制托管方法签名是否应转换成返回 HRESULT 并且返回值有一个附加的 [out, retval] 参数的非托管签名。

默认值为 true(不应转换签名)。

SetLastError

允许调用方使用 Marshal.GetLastWin32Error API 函数来确定执行该方法时是否发生了错误。 在 Visual Basic 中,默认值为 true;在 C# 和 C++ 中,默认值为 false。

ThrowOnUnmappableChar

控件引发的异常,将无法映射的 Unicode 字符转换成一个 ANSI"?"字符。


EntryPoint字段按名称或序号指定 DLL 函数。如果函数在方法定义中的名称与入口点在 DLL 的名称相同,则不必用 EntryPoint 字段来显式地标识函数。

CallingConvention字段指定调用DLL中函数的方式,必须指定与DLL中定义相同的方式。


参考资料:

https://msdn.microsoft.com/zh-cn/library/w4byd5y4.aspx

http://blog.csdn.net/ycl295644/article/details/48239759

知识点3:

C#调用DLL文件时参数对应表


Wtypes.h 中的非托管类型非托管 C 语言类型托管类名说明HANDLEvoid*System.IntPtr32 位BYTEunsigned charSystem.Byte8 位SHORTshortSystem.Int1616 位WORDunsigned shortSystem.UInt1616 位INTintSystem.Int3232 位UINTunsigned intSystem.UInt3232 位LONGlongSystem.Int3232 位BOOLlongSystem.Int3232 位DWORDunsigned longSystem.UInt3232 位ULONGunsigned longSystem.UInt3232 位CHARcharSystem.Char用 ANSI 修饰。LPSTRchar*System.String 或 System.StringBuilder用 ANSI 修饰。LPCSTRConst char*System.String 或 System.StringBuilder用 ANSI 修饰。LPWSTRwchar_t*System.String 或 System.StringBuilder用 Unicode 修饰。LPCWSTRConst wchar_t*System.String 或 System.StringBuilder用 Unicode 修饰。FLOATFloatSystem.Single32 位DOUBLEDoubleSystem.Double64 位

参考资料:

https://www.cnblogs.com/toto0473/archive/2013/01/14/2860281.html

https://www.cnblogs.com/rwzhou/p/5961095.html

http://blog.csdn.net/superhackerzhang/article/details/7648360


比较常用的:

char *  ——StringBuilder

int * ——ref int

int & ——ref int

void * ——IntPtr

void ** ——IntPtr *


2.Progams.cs文件定义

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;namespace invokeDLL{    class Program    {        unsafe static void Main(string[] args)        {            Console.WriteLine("====== add ======");            int sum = MyDLL.add(1, 2);            Console.WriteLine("sum={0}", sum);            Console.WriteLine("====== addA ======");            int a = 1;            int b = 1;            sum = MyDLL.addA(ref a, ref b);            Console.WriteLine("a={0}", a);            Console.WriteLine("b={0}", b);            Console.WriteLine("sum={0}", sum);            Console.WriteLine("====== addAA ======");            sum = MyDLL.addA(ref a, ref b);            Console.WriteLine("a={0}", a);            Console.WriteLine("b={0}", b);            Console.WriteLine("sum={0}", sum);            Console.WriteLine("====== createInstance ======");            IntPtr pInstance = IntPtr.Zero;            MyDLL.createInstance(&pInstance, 1);            Console.WriteLine("====== instanceAddN ======");            MyDLL.instanceAddN(pInstance, 1);            Console.WriteLine("====== deleteInstance ======");            MyDLL.deleteInstance(&pInstance);            Console.ReadLine();        }    }}

Demo_01,Demo_02不再做详细解释,现主要分析下Demo_3中的createInstance、instanceAddN方法。

createInstance过程

IntPtr pInstance = IntPtr.Zero;C#新建变量pInstancepInstance变量的值为0,pInstance变量在内存中的地址为0x05F6E72C,即内存中地址为0x05F6E72C的区域存储的内容为0

MyDLL.createInstance(&pInstance, 1);↓↓调用↓↓ONEDLL_API void createInstance(void** ppInstance, int n)主调函数(即C#)会将pInstance的地址作为参数值传递给被调函数(即DLL),被调函数的形参(ppInstance)作为局部变量在栈中开辟了临时内存空间,存放的是由主调函数放进来的实参的值即0x05F6E72C。



*ppInstance = new MyClass(n);

通过C++构造函数,在堆内存中新建MyClass对象,该对象在内存中的地址为0x05244C70,将该值(0x05244C70)存储至于地址为0x05F6E72C的内存区域。


DLL中被调函数执行完毕,自动释放为存储局部变量申请的栈空间,返回C#中主调函数,此时变量pInstance的值已从调用DLL前的0,变为0x05244C70。

instanceAddN过程

MyDLL.instanceAddN(pInstance, 1);↓↓调用↓↓ONEDLL_API void instanceAddN(void* pInstance, int n)主调函数(即C#)会将pInstance的值(0x05244C70)作为参数值传递给被调函数(即DLL),被调函数的形参作为局部变量在栈中开辟了内存空间,存放的是由主调函数放进来的实参的值即0x05244C70 。


总结

         1. 由于C#端保存着对MyClass对象的一份引用,故C#可调用DLL对MyClass对象进行多次修改。 使用完毕后,务必释放对象,指针置为空。参考资料:https://www.cnblogs.com/dragon2012/p/3847966.html


         2. 该例子适用于C#端不依赖于DLL中对象数据,DLL端自行管理自身数据的场景,减小了C#与DLL之间的耦合程度。

原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 做试管取精困难怎么办 sw过膝靴往下掉怎么办 如果被绑架了该怎么办 怀孕了%2c怎么办%3f 狗狗拉肚子怎么办带血 同学们不和我玩怎么办 懒癌和拖延症怎么办 有严重的拖延症怎么办 有拖延症的人怎么办 10多天月经不停怎么办 20多天月经不停怎么办 月经来了十几天不停怎么办 例假20多天不停怎么办 苹果6dns被劫持怎么办 工地欠工资不给怎么办 买的狗得病了怎么办 剖腹产9天刀口痒怎么办 剖腹产6天刀口痒怎么办 剖腹产2年刀口痒怎么办 破腹产9天刀口痒怎么办 破腹产7天刀口痒怎么办 剖腹产8天伤口痒怎么办 刨腹产9天刀口痒怎么办 破腹产8天刀口痒怎么办 二胎刀口1.1mm薄怎么办 土豪养成记2闪退怎么办 爱上不该爱的人怎么办? 有妇之夫爱上我,怎么办 全身起疙瘩很痒怎么办 怀孕了肚子很痛怎么办 月经来肚子疼的厉害怎么办 孕妇9个月肚子疼怎么办 斗鱼身上长白点怎么办 8个月宝宝肚子疼怎么办 1岁半宝宝肚子痛怎么办 3岁宝宝肚子疼怎么办啊 吃的太辣肚子疼怎么办 2岁半宝宝肚子疼怎么办 2岁宝宝肚子痛哭怎么办 怀孕3个月拉肚子怎么办 一岁宝宝消化不良拉肚子怎么办