我做了一下C++的DLL,导出一个类的指针,然后根据这个类的基类函数进行操作,如上面的模式。
typedef class FTPClientAPI
{
protected:
/**
 * 函数名称:构造函数
 * 函数功能:主是防止用户自己创建此接口,不需要实现
 * 参数列表:
 * 修改记录: 20100514 赵海杰 创建
 */
FTPClientAPI()
{}
/**
 * 函数名称:拷贝构造函数
 * 函数功能:主是防止用户自己创建此接口,不需要实现
 * 参数列表:
 * 修改记录: 20100514 赵海杰 创建
 */
FTPClientAPI(FTPClientAPI&);
/**
 * 函数名称:柝构函数
 * 函数功能:主是防止用户自己创建此接口
 * 参数列表:
 * 修改记录: 20100514 赵海杰 创建
 */
virtual ~FTPClientAPI()
{}
public:
/**
 * 函数名称:释放
 * 函数功能:释放对象
 * 参数列表:
 * @Result : 返回接口版本号
 * 修改记录: 20100521 赵海杰 创建
 **/
virtual int __stdcall free() = 0;
/**
 * 函数名称:准备
 * 函数功能:申请各种资源
 * 参数列表:
 * @Result : Z_OK—成功,<0—失败
 * 修改记录: 20100514 赵海杰 创建
 **/
virtual int __stdcall Prepare() = 0;
/**
 * 函数名称:解除准备
 * 函数功能:释放各种资源
 * 参数列表:
 * @Result : Z_OK—成功,<0—失败
 * 修改记录: 20100514赵海杰 创建
 **/
virtual int __stdcall UnPrepare() = 0;
/**
 * 函数名称:连接服务器
 * 函数功能:连接上级服务器
 * 参数列表:
 * @szIPAddr : IP地址
 * @usIPPort : IP端口
 *  @Result : 返回接口版本号
 * 修改记录: 20100520 赵海杰 创建
 **/
virtual int __stdcall Connect(char* szIPAddr, unsigned short usIPPort) = 0;
/**
 * 函数名称:断开连接
 * 函数功能:断开连接上级服务器
 * 参数列表:
 * @szIPAddr : IP地址
 * @usIPPort : IP端口
 * @Result : 返回接口版本号
 * 修改记录: 20100520 赵海杰 创建
 **/
virtual int __stdcall DisConnect() = 0;
/**
 * 函数名称:设置信息到达时的回调函数
 * 函数功能:释放各种资源
 * 参数列表:
 * @lpPubInfSPI: 回调函数
 * @Result : Z_OK—成功,<0—失败
 * 修改记录: 20100514赵海杰 创建
 **/
virtual int __stdcall SetPubInfoSPI(LPFTPPUBINFOSPI lpPubInfSPI) = 0;
/**
 * 业务处理接口
 **/
// 查指令
virtual int __stdcall QueryOrder(IN const LPFTPQRYORDER lpQryOrder, OUT LPFTPQRYORDERRSP lpQryOrderRsp, INOUT int& iRowCount, char* szErrInfo) = 0;
// 查交易
virtual int __stdcall QueryTrade(IN const LPFTPQRYTRADE lpQryTrade, OUT LPFTPQRYTRADERSP lpQryTradeRsp, INOUT int& iRowCount, char* szErrInfo) = 0;
// 查组件
virtual int __stdcall  QueryComp(IN const LPFTPQRYCOMP lpQryComp, OUT LPFTPQRYCOMPRSP lpQryCompRsp, INOUT int& iRowCount, char* szErrInfo) = 0;
// 查回报
virtual int __stdcall QueryInfo(IN const LPFTPQRYINFO lpQryInfo, OUT LPFTPQRYINFORSP lpQryInfoRsp, INOUT int& iRowCount, char* szErrInfo) = 0; // 添加指令
virtual int __stdcall AddOrder(IN const LPFTPADDORDER lpAddOrder, OUT LPFTPADDORDERRSP lpAddOrderRsp, char* szErrInfo) = 0;
// 删除指令
virtual int __stdcall DelOrder(IN const LPFTPDELORDER lpDelOrder, OUT LPFTPDELORDERRSP lpDelOrderRsp, char* szErrInfo) = 0;
// 操作指令
virtual int __stdcall OrderAction(IN const LPFTPORDERACT lpOrderAct, OUT LPFTPORDERACTRSP lpOrderActRsp, char* szErrInfo) = 0; // 获取参数
virtual int __stdcall GetParam(IN const LPFTPGETPARAM lpGetParam, OUT LPFTPGETPARAMRSP lpGetParamRsp, char* szErrInfo) = 0;
// 设置参数
virtual int __stdcall SetParam(IN const LPFTPSETPARAM lpSetParam, OUT LPFTPSETPARAMRSP lpSetParamRsp, char* szErrInfo) = 0; // 控制命令
virtual  int __stdcall CtrlCmd(IN const LPFTPCTRLCMD lpCtrlCmd, OUT LPFTPCTRLCMDRSP lpCtrlCmdRsp, char* szErrInfo) = 0; // 订阅委托信息
virtual int __stdcall SubOrderInfo(IN const LPFTPSUBORDER lpSubOrder, OUT LPFTPSUBORDERRSP lpSubOrderRsp, char* szErrInfo) = 0;
// 订阅持仓信息
virtual int __stdcall SubHoldInfo(IN const LPFTPSUBHOLDINFO lpSubHoldInfo, OUT LPFTPSUBHOLDINFORSP lpSubHoldInfoRsp, char* szErrInfo) = 0;
// 订阅资金信息
virtual int __stdcall SubBalance(IN const LPFTPSUBBALANCE lpSubBalance, OUT LPFTPSUBBALANCERSP lpSubBalanceRsp, char* szErrInfo) = 0;
// 订阅私有信息
virtual int __stdcall SubPrivate(IN const LPFTPSUBPRIVATE lpSubPrivate, OUT LPFTPSUBPRIVATERSP lpSubPrivateRsp, char* szErrInfo) = 0;
// 订阅指令状态
virtual int __stdcall SubOrderStatus(IN const LPFTPORDERSTATUS lpSubOrderSatus, OUT LPFTPSUBORDERSTATUSRSP lpSubOrderSatusRsp, char* szErrInfo) = 0;
} FTPCLIENTAPI, *LPFTPCLIENTAPI;
此处为导出函数
LPFTPCLIENTAPI __stdcall GetFTPClientAPI();SetPubInfoSPI(LPFTPPUBINFOSPI lpPubInfSPI)函数设置一个类对象的地址,作为回调函数的处理。回调的类声明如下:
typedef class FTPPubInfoSPI
{
public:
enum COMM_STATUS
{
CS_CONNECT = 'c',
CS_DISCONNECT = 'd',
};
virtual ~FTPPubInfoSPI()
{
}
// 控制消息
virtual int __stdcall OnConnStatus(COMM_STATUS);
// 委托信息
virtual int __stdcall OnOrderInfo(IN LPFTPPUBORDERINFO lpOrderInfo, IN int iCount)
{
}
// 持仓信息
virtual int __stdcall OnHoldInfo(IN LPFTPPUBHOLDINFO lpHoldInfo, IN int iCount)
{
}
// 资金信息
virtual int __stdcall OnBalance(IN LPFTPPUBBALANCE lpBalance, IN int iCount)
{
}
// 私有信息
virtual int __stdcall OnPrivate(IN LPFTPPUBPRIVATE lpPrivate)
{
}
// 指令状态
virtual int __stdcall OnOrderStatus(IN LPFTPPUBORDERSTATUS lpOrderStatus, IN int iCount)
{
}
} FTPPUBINFOSPI, *LPFTPPUBINFOSPI;
现在我在C#中用同时声明了两个上面类相似的结构,分别存放着delegate对象,对应着上面两个类的虚函数指针,下面是操作的声明(delegate的声明就不写了):
ublic struct APIFuncEntry
    {
        private IntPtr           FuncDestroy;
        public FuncFree          Free;
        public FuncPrepare       Prepare;
        public FuncUnPrepare     UnPrepare;
        public FuncConnect       Connect;
        public FuncDisConnect    DisConnect;
        public FuncSetPubInfoSPI SetPubInfoSPI;        public FuncQueryOrder    QueryOrder;
        public FuncQueryTrade    QueryTrade;
        public FuncQueryComp     QueryComp;
        public FuncQueryInfo     QueryInfo;        public FuncAddOrder      AddOrder;
        public FuncDelOrder      DelOrder;
        public FuncActOrder      OrderAct;        public FuncGetParam      GetParam;
        public FuncSetParam      SetParam;        public FuncCtrlCmd       CtrlCmd;        public FuncSubOrderInfo  SubOrderInfo;
        public FuncSubHoldInfo   SubHoldInfo;
        public FuncSubBalance    SubBalance;
        public FuncSubPrivate    SubPrivate;
        public FuncSubOrderStatus  SubOrderStatus;
    }
    [StructLayout(LayoutKind.Sequential)]
    public struct FTPAPIStruct
    {
        public IntPtr ptr;
    }
回调类的相似。我用GetFTPClientAPI取到C++中对象的指针,然后根据他的内容填充上面的两个结构
this.APIPtr = GetFTPClientAPI();
            FTPAPIStruct APIStruct = (FTPAPIStruct)Marshal.PtrToStructure(this.APIPtr, typeof(FTPAPIStruct));
            this.FuncEntry = (APIFuncEntry)Marshal.PtrToStructure(APIStruct.ptr, typeof(APIFuncEntry));
这样就可以直接使用FuncEntry 中的函数指针进行操作了,
同样我设置好回调结构中的对象,然后把回调结构的头指向返写到C++对象中,如下:
            this.SPIPtr.ptr = Marshal.AllocHGlobal(Marshal.SizeOf(this.FuncEntry));
            this.SPIFunc.OnConnStatus = OnConnStatus;
            this.SPIFunc.OnOrderInfo = OnOrderInfo;
            this.SPIFunc.OnHoldInfo = OnHoldInfo;
            this.SPIFunc.OnBalance = OnBalance;
            this.SPIFunc.OnPrivate = OnPrivate;
            this.SPIFunc.OnOrderStatus = OnOrderStatus;            Marshal.StructureToPtr(this.SPIFunc, this.SPIPtr.ptr, false);
            this.FuncEntry.SetPubInfoSPI(this.APIPtr, ref this.SPIPtr);
这样设置好后,
我开始都是可以用的,但用一段时间后,就会发现在调用回调函数的时候,就会出现访问异常的情况
我想可能是this.SPIFunc对象被提前释放了,但没有确切的证据,有哪位兄弟,对这方面比较了解,能解释一下吗?

解决方案 »

  1.   


    对的,你自己的分析是正确的。所以不要传指针之类的东西,那个不靠谱。除非你确保那个对象在当前作用域有效。这个错误很多程序员都爱犯。因为这种错误有可能调试的时候发现不到,因为对象释放在.net里面是随机的。所以完全可能调试正确。你可以看微软的代码,微软返回指针的时候一般都是返回对象成员指针,因为对象只要存在,里面成员一定在,就不会错。如果不是这样情况,微软都会送一个对象进去,然后回来一个对象的指针,这样也不会错。因为回来的就是送进来的对象,当前作用域是一定存在的。
      

  2.   

    代码太长了,看得费力. 能不能简化成最少,只有一个回调函数来描述你的问题. 如果要保证回调成功,你代码里的this 那个对象要保证是活动的,这样它下面的delegate对象也是有效的才能回调
      

  3.   

    还有Marshal.StructureToPtr(this.SPIFunc, this.SPIPtr.ptr, false)只是把SPIFunc的内容拷贝到了unmanaged内存中,但是SPIFunc里面的那些delegate对象的指针会被GC在运行时改变的,我感觉C#代码的APIFuncEntry中使用GCHandle封装的delegate对象的结果IntPtr比较好,这可以保证delegate对象的内存地址不变.
    也就是:public struct APIFuncEntry
    {
    ......
    public IntPtr Connect;
    ......
    }
    ......
    //需要保存GCHandle对象, 在不用的时候释放,防止内存泄露
    int i = 0;
    this.SPIHandles[i] = GCHandle.Alloc(new ConnectDelegate(OnConnect));
    this.SPIFunc.Connect = GCHandle.ToIntPtr(this.SPIHandles[i]);
    i++;
    ......
    //最后释放
    foreach(GCHandle h in this.SPIHandles)
    {
        h.Free();
    }一般pInvoke的方法有delegate类型的参数的时候,.net会自动pinning参数对象.
      

  4.   

    我又试了下,发现this.SPIPtr.ptr 这个值会发生改变。变成NULL