也谈c#调用C++的DLL找不到入口点

最近由于项目需要,需要做基于协议的压力测试程序,手头上有协议相关的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();
        }

        

posted on 2007-05-04 23:06  BugHunter  阅读(19271)  评论(16编辑  收藏  举报

导航