关于c#调用c++的dll遇到的问题

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

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

  

首先,重复一下一些基本使用方法。具体的那些方式在这里就不重复讲了,网上很多的。比如http://blog.csdn.net/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++那边做了修改解决的,通过制定模块定义 (.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、后来还遇到个回调函数导致的崩溃问题,又耽误了大半天时间,下班了还耽搁了会终于找的解决发办法了。

  刚开始同事分析出了崩溃的原因,都是回收方式惹的祸,可参见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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C#调用 C++ DLL 的步骤如下: 1. 在 C++ 中定义一个 DLL 导出函数,该函数需要使用 extern "C" 声明,并使用 __declspec(dllexport) 修饰符导出。该函数的参数和返回值类型需要与 C# 中的声明一致。 2. 在 C# 中声明 DLLImport 属性,用于指定 C++ DLL 的名称和函数签名。 3. 在 C#调用 C++ DLL 中的函数。 下面是一个简单的示例,演示如何在 C#调用 C++ DLLC++ DLL 代码: ```cpp // example.cpp #include "stdafx.h" extern "C" __declspec(dllexport) int add(int a, int b) { return a + b; } ``` C# 代码: ```csharp using System.Runtime.InteropServices; class Program { [DllImport("example.dll", CallingConvention = CallingConvention.Cdecl)] public static extern int add(int a, int b); static void Main(string[] args) { int result = add(1, 2); System.Console.WriteLine(result); } } ``` 在上面的示例中,我们首先在 C++ DLL 中定义了一个名为 add 的函数,并使用 __declspec(dllexport) 修饰符导出。然后我们在 C# 中使用 DllImport 属性指定了 example.dll 的名称和 add 函数的签名。最后,我们在 Main 函数中调用了 add 函数,并将结果打印到控制台上。 需要注意的是,在使用 C++ DLL 时,需要注意函数的调用约定。C++ 默认使用的是 __cdecl 调用约定,而 C# 默认使用的是 __stdcall 调用约定。因此,在使用 C++ DLL 时,需要使用 CallingConvention 属性指定函数的调用约定,以免出现调用错误的情况。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值