C# 调用C++ DLL (二)

C#调用C++的dll及MATLAB的dll的方法(二) 在上一篇博客中我们介绍了一下如何利用C#调用C++封装的托管代码,这一篇我们介绍一下如何用C#调用MATLAB封装的代码。 MATLAB的程序完成的功能主要是人脸对齐和图像的crop操作,以便后面送给分类器进行人脸属性的预测。本来想着用C++重新实现一次,这样就可以按照上面的C#调用C++的dll方式再做一次。但是呢,无论我采用什么方式出来的结果总是和MATLAB的结果不一样。我训练模型所用的训 阅读详情

最近帮底层开发的同时用C#重新封装一下dll,也就是用C#类来封装C++Dll里的方法,以供用户使用。

之前也用到过类似的应用,大多数问题都出在类型转换上,但是这次的应用层出不穷,所以在这里总结一下,以供自己以后查阅,也希望对大家能够有所帮助。

  

首先,重复一下一些基本使用方法。具体的那些方式在这里就不重复讲了,网上很多的。比如http://blog.youkuaiyun.com/sunboyljp/archive/2009/12/31/5110639.aspx

c++ 头文件中的定义:

NPD_API int NP_Init();

C#中定义函数

[DllImport("npd_api.dll")]

public static extern int NP_Init();

基本类型转换见下表(我用到过的):

BSTR——StringBuilder

LPCTSTR ——StringBuilder

LPCWSTR ——IntPtr

handle ——IntPtr

hwnd ——IntPtr

char * ——string

int * ——ref int

int & ——ref int

void * ——IntPtrs

unsigned char * ——ref byte

BOOL ——bool

DWORD ——uint或int(我用的是uint,没出过什么问题)

我的问题来了,长期的经验教训我知道了:

1、指针做参数时在C#中一定要使用ref 或out关键字,尤其是结构体指针,要不会报内存读取错误,即使不报错数据也是不太对的。呵呵

   SIPCLIENT_API void WINAPI SCCleanup(SipClient * psip);

   [DllImport("sipclient.dll")]
public static extern void SCCleanup(ref SipClient psip);

  其中SipClient是一个结构体。 

2、重写结构体的时候,之前有指明类型长度或数组长度的地方,也要进行相应的标注,要不也会导致内存错误。       

代码
   
 typedef struct {      char sDVRIP[16 ]; /* DVR IP地址 */      char sDVRIPMask[16 ]; /* DVR IP地址掩码 */      DWORD dwNetInterface; /* 10M/100M自适应,索引 */      WORD wDVRPort; /* 端口号 */      BYTE byMACAddr[MACADDR_LEN]; /* 服务器的物理地址 */  }NET_POSA_ETHERNET;
   public struct NET_POSA_ETHERNET { [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 16 )] public string sDVRIP; //DVR IP地址 [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 16 )] public string sDVRIPMask; // DVR IP地址掩码 public uint dwNetInterface; //网络接口 1-10MBase-T 2-10MBase-T全双工 3-100MBase-TX 4-100M全双工 5-10M/100M自适应 public uint wDVRPort; //端口号 [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6 )] public byte [] byMACAddr; //[MACADDR_LEN]; //PPPoE用户名//服务器的物理地址 }

3、遇到这样一个问题,折腾了大半天时间——http://space.cnblogs.com/q/16616/。

//////////////////////////////////////////////////////////////////////////////////////////////////

一直都觉得c#调用c++dll这块很麻烦,不过要用也没办法。刚开始类型转换头疼了一阵,不过也都解决了,今天遇到的这个问题,是怎么都过不去了,希望大家指点指点!

C++原型:

struct SipClient
{
    char *    realm;
    void *    lpvoid;

    char *    from_ip;
    int        from_port;
    char *    to_ip;
    int        to_port;

    char *    client_id;
    char *    server_id;        //平台编码

    int        reg_count;

    
    SipClient(){memset(this, 0, sizeof(SipClient));}
    ~SipClient()
    {
        delete[] from_ip;
        delete[] realm;
        delete[] client_id;
        delete[] to_ip;
        delete[] server_id;
    }
};

SIPCLIENT_API SipClient* SCInit(const char * reaml, const char * from_ip, int from_port,  const char * to_ip, int to_port, const char * server_id, const char * user_id, const char * user_name, void * user_obj_param);

C#里是这么些的

  
public struct SipClient { public string realm; //鉴权码 public IntPtr lpvoid; //用户参数 public string from_ip; //本地IP public int from_port; //本地端口 public string to_ip; //SIP服务器IP public int to_port; //SIP服务器端口 public string client_id; //用户的编码 public string server_id; //SIP服务器的编码 public int reg_id; //保留,暂不使用 } [DllImport("sipclient.dll" )] public static extern SipClient SCInit(string reaml, string from_ip, int from_port, string to_ip, int to_port, string server_id, string user_id, string user_name, IntPtr user_obj_param);
//函数调用
private const string USER_ID = "180400000090000000"; private const string SERVER_ID = "180400000099000000";
SIPCLIENT_API.SCInit("192.168.10.23", "192.168.10.32", 5092, "192.168.10.23", 5090, SERVER_ID, USER_ID, USER_ID, IntPtr.Zero);

黄色标记处写作SipClient时,运行时函数调用异常:

Additional information: 方法的类型签名与 PInvoke 不兼容。

想按照网上一种做法,将SipClient改为IntPtr,运行时还是出错,不过异常是:

Additional information: 无法在 DLL“sipclient.dll”中找到名为“SCInit”的入口点。

我能够想到的办法都用了,还是不能解决这些问题,期待大家的回答!

 

/////////////////////////////////////////////////////////////////////////////////////////////////

  最后是在C++那边做了修改解决的,通过制定模块定义 (.def) 文件,统一制定导出函数对应的名称。返回值为结构体指针的函数用IntPtr也能使用了。  

代码
   
SIPCLIENT_API SipClient * SCInit(const char * reaml,
  const char * from_ip, int from_port,
  const char * to_ip, int to_port, const char * server_id,
  const char * user_id, const char * user_name, void * user_obj_param);
[DllImport("sipclient.dll" )] public static extern IntPtr SCInit(string reaml, string from_ip, int from_port, string to_ip, int to_port, string server_id, string user_id, string user_name, IntPtr user_obj_param);
  IntPtr client = IntPtr.Zero;
client = SIPCLIENT_API.SCInit(REALM, CLIENT_IP, CLIENT_PORT, SERVER_IP,
     SERVER_PORT, SERVER_ID, USER_ID, USER_ID, IntPtr.Zero);
if (client != IntPtr.Zero)
sipclient = (SipClient)Marshal.PtrToStructure(client, typeof(SipClient));
else
MessageBox.Show("SipClient初始化失败!");

4、后来还遇到个回调函数导致的崩溃问题,又耽误了大半天时间,下班了还耽搁了会终于找的解决发办法了。

////////////////////////////////////////////

WINAPI见windef.h这个头文件
#define WINAPI __stdcall
  默认情况下,我们的函数调用都是遵循__stdcall这个规则的。当然,也有诸如__cdecl、__pascal等规则。
  使用__stdcall还是__cdecl或__pascal,在纯Windows编程下并非特别需要。
__stdcall:
  1、进行函数调用,函数参数的入栈方式是最右边先入栈。
  2、同时__stdcall规定,调用者负责栈的回收。当然,这些工作是应用程序自己完成的,不需要编写者动手。汇编语言另当别论(POP SP POP BP等)
  题外话:__pascal的调用规则是从左到右,正好与__stdcall相反。
  3、C调用约定(即用__cdecl关键字说明)(The C default calling convention)按从右至左的顺序压参数入栈,由调用者把参数弹出栈。对于传送参数的内存栈是由调用者来维护的(正因为如此,实现可变参数 vararg的函数(如printf)只能使用该调用约定)。另外,在函数名修饰约定方面也有所不同。 _cdecl是C和C++程序的缺省调用方式。每一个调用它的函数都包含清空堆栈的代码,所以产生的可执行文件大小会比调用_stdcall函数的大。函数采用从右到左的压栈方式。VC将函数编译后会在函数名前面加上下划线前缀。
是MFC缺省调用约定。
  在函数调用过程中,会使用栈。__stdcall与__cdecl是两种不同的函数调用约定,定义了函数参数入栈的顺序,由调用函数还是被调用函数将参数弹出栈,以及产生函数修饰名的方法。关于这两个调用约定的详细信息,读者可参看MSDN。对于参数个数可变的函数,例如printf,使用的是__cdecl调用约定,Win32的API函数都遵循__stdcall调用约定。在VC++开发环境中,默认的编译选项是__cdecl,对于那些需要__stdcall调用约定的函数,在声明时必须显式地加上__stdcall。在Windows程序中,回调函数必须遵循__stdcall调用约定,所以我们在声明回调函数时要使用CALLBACK。使用CALLBACK而不是__stdcall的原因是为了告诉我们这是一个回调函数。注意,在Windows 98和Windows 2000下,声明窗口过程函数时,即使不使用CALLBACK也不会出错,但在Windows NT4.0下,则会出错

////////////////////////////////////////////

  刚开始同事分析出了崩溃的原因,都是回收方式惹的祸,可参见http://www.hudong.com/wiki/WINAPI,尝试使用__stdcall,但是还是没有解决问题

  后来实践证明,程序是很严谨的,半点差错都不能出才不会导致错误,思路还是__stdcall,只不过少改了东西,有两个地方需要改,才能保证不出错。

  参考http://hi.baidu.com/tease/blog/item/1fe7213802780f22b9998f5a.html。

 

  关键就是这两句话

  typedef void (_stdcall *CiCiCallBack) (bool started, void* client,char *message);
  将导出函数修改为:
  extern "C" _declspec(dllexport) bool _stdcall Test(char* fileName, CiCiCallBack callback)

  一开始的时候就只修改了定义那,却忘记了导出时的修改,差点就放弃了这条解决思路了,不过还好,所谓坚持就是胜利!

  
5、后来封装好拿到用户那里用,却总是提示说找不到C++那些dll.

  网上一查,初步定位是开发环境引起的,跟环境部署有关系。我们的开发环境是vs2008,而客户使用的vs2010,通过几次尝试,问题终于了。

  首先考虑是缺少某些C++必备的运行库,存在相互依赖关系,所以导致找不到dll。用查看Dependency Walker查看才发现真的是客户机子上少了一些东西。

    但是此路不通,将缺少的那些东西拷贝到可执行程序目录下,问题依旧没有解决。但是依旧坚持这条路~

  尝试安装vcredist_x86.exe,以排除是否还是缺少了某些运行库的可能,问题依然存在。

  后来我想起来之前搜索问题的时候,看到好像跟dll的Releas\Debug版本还有关系,所有又尝试提议让同事将他们的c++dll改为Release版的。

    因为项目是多个人一起做了,编译Release版还花了不少时间,不过好歹问题终于解决了!

  总结:直接安装vcredist_x86.exe,所有dll必须使用Release版的。如果使用Debug版的就必须保证可执行程序目录下的dll是完整的,缺一不可!

  网上详细的讲解也很多,感觉这个总结的很好http://hi.baidu.com/fairysky/blog/item/e7a8366dbaa735f3431694c8.html。

做程序就怕出现问题,出现问题就怕不知道原因,知道原因了就好找解决的办法啦!

C#使用CLR调用C++的DLL库 C#使用CLR调用C++的DLL库的windows完整解决方案 说明如下: 1、工程一:C++的DLL 2、工程二:C++的CLR的类库 3、工程三:C#的exe 立即下载

相关推荐

CTFShow萌新区全攻略:从密码破解到Web渗透的实战解析

本文为CTFShow萌新区新手提供从密码破解到Web渗透的完整实战攻略。文章深入解析了密码学、隐写术、杂项和Web安全四大核心方向,分享了实用的解题工具、思维方法和绕过技巧,旨在帮助初学者建立系统的安全竞赛解题体系,而不仅仅是提供一份题解(WP)。

weixin_29216957的博客 299

C++回调C#

C++回调C# 现在比较流行C#与C++融合:C#做GUI,开发效率高,C++做运算,运行效率高,二者兼得。但是C++与C#必然存在数据交互,C#与C++dll的数据交互从来都是一个让人头疼的问题。 从调用方式看也有两种情况: 1、C#调用C++函数 这种情况用的比较多,数据流向可以是C#流向C++,通过参数将数据传递给C++(如:SetData(double[] data));也可以是C++流向C#(如:GetData(double[] data))。 2、C++ Callback ..

jacberg的专栏 2317

巨量千川M-API实战:异步报表与批量操作自动化指南

API(应用程序编程接口)是现代软件系统间通信和数据交换的核心技术,它定义了服务间交互的协议和规范。在数字营销领域,营销API(Marketing API)通过程序化接口实现了广告账户管理、数据获取和操作自动化的能力,其技术价值在于将重复性人工操作转化为高效、准确的自动化流程,显著提升运营效率并降低人为错误。在广告投放精细化运营场景中,面对海量数据报表的获取与批量广告状态管理的需求,异步任务处理和批量操作接口成为关键技术解决方案。本文聚焦巨量千川M-API,深入解析其异步报表获取机制与批量操作实现,涵盖从任

weixin_32932149的博客 430

C#调用C++Dll封装时遇到的一系列问题

最近帮底层开发的同时用C#重新封装一下dll,也就是用C#类来封装C++Dll里的方法,以供用户使用。 之前也用到过类似的应用,大多数问题都出在类型转换上,但是这次的应用层出不穷,所以在这里总结一下,以供自己以后查阅,也希望对大家能够有所帮助。    首先,重复一下一些基本使用方法。具体的那些方式在这里就不重复讲了,网上很多的。比如http://blog.youkuaiyun.com/sunboyljp

jiangxinyu的专栏 9760

有源滤波电路设计

目录一、低通滤波器1、同相输入低通滤波器(1)一阶低通滤波电路(2)二阶低通滤波电路(压控电压源;Sallen-Key)2、反相输入低通滤波器(1)一阶低通滤波电路(2)二阶低通滤波电路(无限增益多路反馈)二、高通滤波器1、同相输入高通滤波器(1)一阶高通滤波电路(2)二阶高通滤波电路(压控电压源;Sallen-Key)2、反相输入高通滤波电路(1)二阶高通滤波电路(无限增益多路反馈) 一、低通滤波器 1、同相输入低通滤波器 (1)一阶低通滤波电路 即在一阶无源低通滤波器的基础上,加了一个集成运放。 它的

qq_41873236的博客 3万+

学习十八-UJA107xA简介及移植-0

目录 1、UJA107xA系列手册 1、介绍 2.2.1 BAT引脚功能 2.4.1 V1、V2稳压器 2.5.1 CAN收发器 3.1 Startup Operation 3.2 Normal Operation 5.1 Attached Files 5.2 NXP UJA107xA Example Software 6、main() 1、UJA107xA系列手册 看完手册关于使用还是没有头绪的,只是想SPI发送指令将V2驱动起来,进而驱动起来CAN收...

m0_56344230的博客 1707

C#调用C++带指针参数函数

C++ DLL代码 typedef void (__stdcall *fnSavedCB)(int savedLen); __declspec(dllexport) void __stdcall testSaveData(fnSavedCB fn) { for (int i = 0; i < 10; ++i) { fn((i+1)*1024); } } ...

sdhongjun的专栏 6944

REST-meta-MDD Data总结

sMRI数据使用:nwc1、nwc2、nwc3(同样数据集相关论坛中有相关信息指向需要使用wc1、wc2、wc3)多方咨询与讨论后决定使用nwc数据,这里说明wc数据相对wc数据要大的多如wc1的大小为1.33GB,而nwc1的数据却有6.90GB。VBM文件:/REST-meta-MDD/Sharing/REST-meta-MDD-VBM- phase1 -Sharing.tar.gz。1、Excel中的表型信息:1-男,2-女;-9999或空白表示缺少数据。

2404_88295660的博客 1539

vs2008,c#生成dll控件,类库

生成dll控件: 打开c:\Windows\Microsoft.NET\Framework\v2.0.50727 接着运行 c:\Windows\Microsoft.NET\Framework\v2.0.50727>csc/target:library xxx.cs(控件类) 这时候这个控件类必须在c:\Windows\Microsoft.NET\Framework\v2.0.50727

E_say_de 607

智能小车倒车入库功能的实现(python,cpp,ubuntu)

智能小车倒车入库功能的实现(python,cpp,ubuntu)

weixin_62135607的博客 5794

不藏了!Wireshark 命令、捕获、过滤器大全分享给你

Wireshark,这款广受欢迎的开源网络协议分析工具,堪称网络管理员、网络安全专家和网络爱好者的“显微镜”!它可以帮助我们捕获网络中的数据包,并提供详细的解码和分析能力。本文将为您全面剖析 Wireshark 的强大功能,从基础到高级,让你快速掌握这个神器!💡Wireshark 是一款功能强大的网络协议分析器,最早由 Gerald Combs 于 1998 年开发,经过多年发展,已成为网络诊断和分析的首选工具。

网络技术联盟站 727

C#调用C++类库的几种方式

1、 直接调用C++类库中的公共方法 使用DllImport特性对方法进行调用,比如一个C++类库SampleCppWrapper.dll中的公共方法: extern "C" __declspec(dllexport) int __stdcall Add(int n1, int n2); __stdcall表示调用约定:参数都是从右向左通过堆栈传递,函数调用在返回前要由被调用者清...

weixin_30594001的博客 1438

使用Python与OpenCV实现基于霍夫变换的圆形缺陷检测与定位详细教程

本文详细探讨了如何使用Python和OpenCV库,通过图像边缘检测和霍夫变换实现圆形对象的检测与缺陷定位。我们不仅介绍了算法的基本原理,还通过具体的代码示例展示了如何在实际应用中实现这些技术。对于工业检测和医学影像分析,本文的方法提供了高效且易于实现的解决方案。随着图像处理技术的不断发展,未来我们可以期待更多新算法和新技术的应用,如深度学习与传统方法的结合,以及多传感器融合技术的引入,这将进一步提升缺陷检测与定位的效率和准确性。

qq_38334677的博客 1297

C#生成dll给c++调用 方法二COM方式 vs2022 NO Make Assembly COM-Visible选错了 不需要clr

有些C++项目中也用了C语言.c,用方法一就无法使用【不能使用 /clr 选项编译 C 文件】。就用方法2。

chenhao0568的专栏 700

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

在C#调用C++的DLL简析(二)—— 生成托管dll

写操作之前,还是扼要的说一下托管与非托管C++的区别好了,其实我也并没有深入了解过托管C++的特点所在,其最大的特征就是可以由系统来调试回收相关的代码资源,跟C#的特性一样,只是编程风格跟C++类似而已,因此,这决定了C#与托管C++是可以完美结合在一起的。托管C++生成的dll跟C#生成的dll应该说是没区别的,之所以产生托管C++这种怪物,完全是因为微软在极力推崇C#,必须要兼顾不同语言间交互

Lce的专栏 3467

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

C# 调用C++封装的dll库函数的方法(二)

上一章讲述:C#调用C++封装的dll库函数,这里继续说明; 3、Marshal在C#中的应用(void *指针到IntPtr的转化),void *指针到IntPtr的复杂转化; (1)C++定义结构体,在这里结构体A变得复杂一点,如果它内部包含一个指向另一个结构体B的指针 struct A { wchar_t osdbuffer[100]; unsigned short ix; unsigned shor

^@^lemon tea^@^ 1642

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

C#中使用OpenGL:(二)C#调用C/C++的dll

在C#中使用OpenGL图形库为业余的图形编程人员提供了很大的便利,可是官方并没有向用户提供C#版本的OpenGL图形接口,在民间有好一些人开发了C#版的OpenGL接口,使之能够在C#中使用。这些第三方的C#版OpenGL应该说用起来还是不错的,如果说有什么缺点的话,那应该是这些OpenGL的版本都不是最新的,一般在4.0以下,而现在OpenGL都4.6版本了。如果要使用最新的OpenGL图形接

qq_28249373的博客 1万+

C#调用C++ DLL简析(二)—— 生成托管dll

写操作之前,还是扼要的说一下托管与非托管C++的区别好了,其实我也并没有深入了解过托管C++的特点所在,其最大的特征就是可以由系统来调试回收相关的代码资源,跟C#的特性一样,只是编程风格跟C++类似而已,因此,这决定了C#与托管C++是可以完美结合在一起的。托管C++生成的dll跟C#生成的dll应该说是没区别的,之所以产生托管C++这种怪物,完全是因为微软在极力推崇C#,必须要兼顾不同语言间交互

lysc_forever的专栏 1204

ue4 c++调用 c#的dll(二)

ue4中调用 C#的dll(二)——调用DLL中方法 1.参考UE4官方文档导入dll。 2.部分测试代码如下。 bool UCreateAndLinkDLLTutBFL::importDLL(FString folder, FString name) { FString filePath = *FPaths::GamePluginsDir() + folder + "/" + name; ...

yycocl的博客 2081

在C#调用C++的DLL方法(二)生成托管的DLL

写操作之前,还是扼要的说一下托管与非托管C++的区别好了,其实我也并没有深入了解过托管C++的特点所在,其最大的特征就是可以由系统来调试回收相关的代码资源,跟C#的特性一样,只是编程风格跟C++类似而已,因此,这决定了C#与托管C++是可以完美结合在一起的。托管C++生成的dll跟C#生成的dll应该说是没区别的,之所以产生托管C++这种怪物,完全是因为微软在极力推崇C#,必须要兼顾不同语言间交互...

weixin_33743880的博客 163

c#调用c++ dll(二)

当对c++几种调用方式有了解以后我们可以试着写个c++动态连接库了,我们现在来写个简单的c++求和函数并把它封装成dll,供以后的c#调用 我们写dll的时候,个人认为,要写就要把dll写好,写标准,别的地方才能用得方便,接下来就带你们写个标准的dll 第一步,确定你要写哪些方法,根据这些方法我们创建一个头文件,我这边主要写个add(); 例子如下:head.h #ifdef api ...

weixin_33728268的博客 145

c# 调用c++ dll,传递 string 路径给c++之一(宽字节字符问题见二)

采用静态调用dll的方式 在 ,cpp中 #define DLL_Nail _declspec(dllexport) CMatImgProc::CMatImgProc(void) { } CMatImgProc::~CMatImgProc(void) { } void CMatImgProc::setStringName(char* content,int ilength) { string s0; for(int i=0;i<ilength;i++) { int iIndex = i*2; s0=s

blue_xinran的博客 1665

C#调用C++生成的dll获取CPU序列号(二)

在前面一节,我们了解了C++生成dll

sunny906的专栏 3034

一篇就够【C++生成的dll给c#用的方法,使用 C++ 创建 DLL 并供 C# 调用】

生成非托管 DLL:方法: 在 Visual Studio 中创建一个 Win32 项目,选择 动态链接库 (DLL) 类型。添加以上代码,编译后生成 MyLibrary.dll 和对应的 MyLibrary.lib 文件。

qq_39030619的博客 1881
上一篇: C# 调用C++ DLL (一)
下一篇: C++中引用(&)的用法和应用实例
Le_Sam
博客等级 码龄11年 342粉丝 53原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值