raindust

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

转自:http://www.cnblogs.com/tallman/archive/2009/03/07/735948.html

 

最近由于项目需要,需要做基于协议的压力测试程序,手头上有协议相关的CPP文件和头文件,而网络通讯部分我是用C#实现的,如果用C#语言重新改写C++里的方法,耗时巨大而且也很不方便,所以想着是否可以把C++的方法封装成DLL供C#调用,在网上查了下,发现这方面的资料还是不少的,不过具体做起来会遇到什么问题自己也不是很清楚,由于自己对C++不是很熟悉,并且手头上的CPP文件和头文件都是基于linux上服务器端运行的,所以首先要对C++封装成DLL这一步进行改造,编译的时候发现有100多个错误,提示都是什么找不到定义的类,但定义好的类的头文件我已经引入了,后来在VS2005的项目编译选项里取消了预便宜头文件,编译通过(有点不可理解 呵呵)
由于程序中设计到一些转序的方法(主机序和网络序之间的转换)所以要更换成windows平台下的转序方法,添加如下代码:
//windows转序
#include <stdlib.h>
#include 
<winsock.h>
#pragma comment(lib, 
"ws2_32.lib")

编译通过后放在C#工程的运行目录下执行,系统提示找不到相关函数的入口点,这个时候有点疑惑,在博客园的里搜了下,看到了一篇讲C#调用C++的DLL找不到入口函数的原因是因为,函数在便宜成DLL后函数的名称就发生了改变:会在函数的前后产生一些字符

 [DllImport("PetCppTest.dll", EntryPoint = "WriteCommonHeader", CallingConvention = CallingConvention.StdCall)]

注:其中"WriteCommonHeader"就是原来C++里函数的名称,文章中提到可以用一个叫eXeScope的软件查出函数在编译后具体变成了什么名称,只要将这些名称输入完全就可以正确找到入口函数

最有意思的地方出现了,当我用eXeScope去查看我编译成功的DLL却惊奇的发现我的DLL没有导出函数,没有导出函数也就意味着就是神仙也调用不了.....

我被卡住了,由于对C++不熟悉,就只好又去搜资料,终于让我知道了如何定义导出函数,下面是个例子:
示例DLL和应用程序
这是一个在Visual C++中用“Win32动态链接库”项目类型创建的示例程序。
// myDLL.cpp
//

#include 
"stdafx.h"
#define EXPORTING_DLL
#include 
"sampleDLL.h"

BOOL APIENTRY DllMain( HANDLE hModule, 
                       DWORD  ul_reason_for_call, 
                       LPVOID lpReserved
                     )
{
    
return TRUE;
}


void HelloWorld()
{
    MessageBox( NULL, TEXT(
"Hello World"), TEXT("In a DLL"), MB_OK);
}

// File: myDLL.h
//
#ifndef INDLL_H
#define INDLL_H

#ifdef EXPORTING_DLL
extern __declspec(dllexport) void HelloWorld() ;
#else
extern __declspec(dllimport) void HelloWorld() ;
#endif

#endif

下面的程序是一个win32应用程序,该程序调用myDLL中的导出函数HelloWorld。
// myApp.cpp 
//

#include 
"stdafx.h"
#include 
"myDLL.h"

int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine,
                     
int       nCmdShow)
{     
    HelloWorld();
    
return 0;
}

看明白了吗 虽然是很简单的代码 却让我明白了要定义导出函数的方法:
1.首先要在头文件里定义方法:
#ifdef EXPORTING_DLL
extern __declspec(dllexport) void HelloWorld() ;
#else
extern __declspec(dllimport) void HelloWorld() ;
#endif
2.其次在CPP文件里使用定义:
#include "stdafx.h"
#define EXPORTING_DLL
#include 
"sampleDLL.h"
OK 按找这样的方法我在头文件里也加入了相关方法的声明:
#ifndef ADOPTPROTOCOL_H
#define ADOPTPROTOCOL_H
#include 
"AdoptDefine.h"
#ifdef EXPORT_WRITECOMMONHEADER
extern __declspec(dllexport) UINT32 WriteCommonHeader(UINT8 *pMemory, const CommonHeader &stCommonHeader) ;
#else
extern __declspec(dllimport) UINT32 WriteCommonHeader(UINT8 *pMemory, const CommonHeader &stCommonHeader) ;
#endif

typedef 
struct tag_CommonHeader
{
    UINT8 byVersion;
    UINT8 byFlag;
    UINT16 ushCheckSum;
    UINT32 unInvokeID;
    UINT32 unRedirectIP;
    UINT16 ushRedirectPort;
    UINT16 ushLength;

    tag_CommonHeader():byVersion(
0),byFlag(0),ushCheckSum(0),unInvokeID(0),unRedirectIP(0),
        ushRedirectPort(
0),ushLength(0{}
}
 CommonHeader;

然后在CPP文件里使用定义:

#include <stdio.h>
#include 
<string.h>
//----------导出方法必须的-----
#define EXPORT_WRITECOMMONHEADER
//--------------------------------
//windows转序
#include <stdlib.h>
#include 
<winsock.h>


编译的时候却提示找不到参数类型,想了下可能是这个原因:参数CommonHeader 是一个结构体,可能是声明放在了结构体的定义的签名 自然认为参数类型没有定义(C++这点比起C#显得有点小白了)调整了声明的顺序后果然编译成功,但是用eXeScope查看编译好的DLL依然没有找到导出函数,看了看网上资料提到的方法除了参数类型和返回类型和我有区别以外,定义的方法都是没问题的,难道说不支持结构体的参数....应该没有这么弱智,后来发现竟然是CPP文件里方法前面的类名称是问题之所在:
原方法定义是这样的:

//------------------------------------------
//要想在产生导出函数必须去掉在头文件里的声明
//可以取消类的应用CClientProtocol::
//-----------------------------------------

UINT32 CClientProtocol::WriteCommonHeader(UINT8 
*pMemory, const CommonHeader &stCommonHeader)
{
    UINT32 unOffset 
= 0;

    
//依次写入每个字段
    unOffset += CMemUtils::WriteUINT8(pMemory + unOffset, stCommonHeader.byVersion);
    unOffset 
+= CMemUtils::WriteUINT8(pMemory + unOffset, stCommonHeader.byFlag);
    unOffset 
+= CMemUtils::WriteUINT16(pMemory + unOffset, stCommonHeader.ushCheckSum);
    unOffset 
+= CMemUtils::WriteUINT32(pMemory + unOffset, htonl(stCommonHeader.unInvokeID));
    unOffset 
+= CMemUtils::WriteUINT32(pMemory + unOffset, htonl(stCommonHeader.unRedirectIP));
    unOffset 
+= CMemUtils::WriteUINT16(pMemory + unOffset, htons(stCommonHeader.ushRedirectPort));
    unOffset 
+= CMemUtils::WriteUINT16(pMemory + unOffset, htons(stCommonHeader.ushLength));

    
return unOffset;
}
去掉CClientProtocol::以后 用eXeScope成功的看到了导出函数的名字 这个时候名字已经不再是WriteCommonHeader而是变成了?WriteCommonHeader@@YAIPAEABUtag_CommonHeader@@@Z
在C#的调用方更改了入口点函数的名字后调用成功:
[DllImport("PetCppTest.dll", EntryPoint = "?WriteCommonHeader@@YAIPAEABUtag_CommonHeader@@@Z", CallingConvention = CallingConvention.StdCall)]
        
public static extern UInt32 WriteCommonHeader(ref byte pMemory, ref byte[] CommonHeader);

下面是一个简单的调用方法的例子项目:

public struct CommonHeader
    
{
        Byte byVersion;
        Byte byFlag;
        UInt16 ushCheckSum;
        UInt32 unInvokeID;
        UInt32 unRedirectIP;
        UInt16 ushRedirectPort;
        UInt16 ushLength;
        
public CommonHeader(Byte p_byVersion, Byte p_byFlag, UInt16 p_ushCheckSum, UInt32 p_unInvokeID, UInt32 p_unRedirectIP, UInt16 p_ushRedirectPort, UInt16 p_ushLength)
        
{
            byVersion 
= p_byVersion;
            byFlag 
= p_byFlag;
            ushCheckSum 
= p_ushCheckSum;
            unInvokeID 
= p_unInvokeID;
            unRedirectIP 
= p_unRedirectIP;
            ushRedirectPort 
= p_ushRedirectPort;
            ushLength 
= p_ushLength;
        }

    }
 

    
public class Program
    
{
        [DllImport(
"PetCppTest.dll", EntryPoint = "?WriteCommonHeader@@YAIPAEABUtag_CommonHeader@@@Z", CallingConvention = CallingConvention.StdCall)]
        
public static extern UInt32 WriteCommonHeader(ref byte pMemory, ref byte[] CommonHeader);
        
        
static void Main(string[] args)
        
{
            Byte ver 
= 8;
            Byte flag 
= 1;
            UInt16 checkSum 
= 9;
            UInt32 invokeId 
= 20;
            UInt32 ip 
= 5000;
            UInt16 port 
= 80;
            UInt16 length 
= 40;
            CommonHeader myCommonHeader 
= new CommonHeader(ver, flag, checkSum, invokeId, ip, port, length);
            
byte[] coverComHeader = StructToBytes(myCommonHeader);
            
byte location = 0;
            UInt32 reslut 
=  WriteCommonHeader(ref location, ref coverComHeader);
            Console.WriteLine(
"the reslut is :{0}", reslut);
            Console.ReadLine();
        }

        

评论

1楼

由于你用的是C++编译器命名约定,所以才会后面有一大堆乱七八糟的字符串(用来指示有多少个参数以及他们的类型,说白了就是方法签名Method Signature)

不过非常抱歉,具体应该怎样正确导出我已经忘了。
可以试试用extern "C" 来修饰,好像更加准确的方法是用一个特殊的描述文件来描述,忘了忘了,老了……
2007-05-04 23:45 | Sumtec      
2楼
extern "C" 就可以
2007-05-05 00:00 | hyifeng [未注册用户]
3楼
可以考虑使用def文件
2007-05-05 16:36 | kisskiki [未注册用户]
4楼
nice,就是extern "C"
2007-05-05 16:36 | 死神128 [未注册用户]
5楼
extern "C" 我使用过了
写这篇文章的根本问题是在:写好的C++用软件查看时候 发现没有导出函数
那即便你是C#中用了extern "C" 也查询不到入口函数
主要是想要讲下C++中定义
2007-05-06 00:21 | Bughunter      
6楼
还是def文件最方便,另外,编译好可以用depend工具检查下接口
2007-05-06 12:38 | Z [未注册用户] 
7楼 
win depend工具检公开有什么方法,然后使用P/Ivoke调用一下不就行了,搞得太麻烦了
2007-05-06 19:03 | yi [未注册用户]
8楼
建议使用def文件,可以同时定义导出函数名或序号
2007-05-06 21:50 | 海阔天空      
9楼
p/invoke本来就很麻烦,两年前做pos机软件的时候也是由于不会c++没法移植,采用p/invoke从托管平台去调用动态库,msdn和所有可以查询的资料都基本看完了才把所有接口都映射过来,而且有几个方法还是采取了其他措施才得以使用的,具体也不记得了.现在看着那分代码都还感叹....要是现在,我肯定直接就用c++写了或直接移植了.
个人感觉p/invoke就是一个鸡肋,而且还有性能损失.对应同一个方法还可能映射出不同得多个托管方法....

[DllImport(conConversion, SetLastError=true), SuppressUnmanagedCodeSecurity]
internal static extern bool Initialization(int MyAddr, int TagAddr);

[DllImport(conConversion, SetLastError=true), SuppressUnmanagedCodeSecurity]
internal static extern int Conversion(ref int Command, [MarshalAs(UnmanagedType.AsAny)] Object objDataStruct, int nSizeOfParameter);

[DllImport(conConversion, SetLastError=true), SuppressUnmanagedCodeSecurity]
internal static extern int Conversion(ref int Command, IntPtr buffer, int nSizeOfParameter);

[DllImport(conConversion, SetLastError=true), SuppressUnmanagedCodeSecurity]
internal static extern int GetSizeOfData();

[DllImport(conConversion, SetLastError=true), SuppressUnmanagedCodeSecurity]
internal static extern bool GetData(ref int Result, int Size);

[DllImport(conConversion, SetLastError=true), SuppressUnmanagedCodeSecurity]
internal static extern bool GetData(IntPtr Buffer, int Size);

[DllImport(conConversion, SetLastError=true), SuppressUnmanagedCodeSecurity]
internal static extern bool GetData([MarshalAs(UnmanagedType.AsAny)]
[In, Out] Object objDataStruct, int Size);
2007-05-07 09:21 | 快快乐乐的生活       
 
def文件 exports关键字
2007-05-08 07:43 | xioxu [未注册用户]
 
不是所有人都用windebug 尤其是搞C#开发的,很多人都不会用到这个工具,呵呵
2007-05-08 09:22 | Bughunter      
 
为什么不用c++ Interop呢.这样就可以直接引用了 :-)
2007-05-08 11:11 | A.Z      
 
posted on 2009-05-31 13:14  ymz  阅读(3309)  评论(0编辑  收藏  举报