当前位置:编程学习 > C#/ASP.NET >>

求助C#兼C++高手, 非托管动态调用基类,指针接口问题

调用mtmanapi.dll时,用depends工具查看该dll函数,仅发现MtManVersion、MtManCreate
用C#动态加载C++写的DLL的二个函数,在调用非托管的函数MtManCreate 出现的异常,尚未实现 (异常来自 HRESULT:0x80004001 (E_NOTIMPL))
而调用MtManVersion可正常返回数值。

遇到问题
1、C#调用非托管C++,能用ref 返回指针接口,并使用该接口的基类的方法吗?
2、C#能不能调用C++函数返回接口,Marshal.GetComInterfaceForObjectInContext能用上吗.
3、若取得到CManagerInterface指针接口,自己能继承它,重载方法吗?

相关代码
c#调用非托管的函数类,代码跟网上很像,如http://bbs.csdn.net/topics/390381634

 public class TManagerFactory
    {
        private int FLib = 0;
        private TMTManGetVersion FGetVersion = null;
        private TMTManGetInterface FGetInterface = null;
        protected IManagerInterface FResult = null;

        public TManagerFactory(string dll) : base()
        {
            FLib = LoadLibrary(dll);        
         
            if (FLib == 0)
                Console.WriteLine("加載出錯");

            FGetVersion = (TMTManGetVersion)GetAddress(FLib, "MtManVersion", typeof(TMTManGetVersion));

            FGetInterface = (TMTManGetInterface)GetAddress(FLib, "MtManCreate", typeof(TMTManGetInterface));
        }
        public IManagerInterface CreateAPI()
        {

            if ((FGetVersion != null) && (FGetInterface != null))
            {

                FGetInterface(FGetVersion(), ref FResult);
            }
            else
            {

                FResult = null;
            }
            return FResult;
        }   
     

        public int GetTMTManGetVersion()
        {
            return FGetVersion();
        }

    } // end TManagerFactory

    public delegate int TMTManGetVersion();
    public delegate int TMTManGetInterface(int Version, ref IManagerInterface Obj);
    public interface IManagerInterface
    {   
        
        int Connect(string Server);
        int Login(int Login, string Password);
        int PasswordChange(string Password, int IsInvestor);
        //还有许多略
    }

TManagerFactory factory;
            IManagerInterface manapi;
            factory = new TManagerFactory("mtmanapi.dll");   
            MessageBox.Show("GetVersion: " + factory.GetTMTManGetVersion());
            manapi = factory.CreateAPI(); //程序执行到这里出错
 下面代码是若调取MtManCreate的成功后要问的(后话),若不能调用不用议论

 public class ManagerInterface : IManagerInterface
    {
        #region IManagerInterface 成员

        public virtual int Connect(string Server)
        {
            return Connect(Server);
        }

        public virtual int Disconnect()
        {
            return Disconnect();
        }
        public virtual int IsConnected()
        {
            return IsConnected();

        }
}
 public class ConnectionApi : ManagerInterface
    {
        public override int PasswordChange(string Password, int IsInvestor)
        {
            string pathFile = AppDomain.CurrentDomain.BaseDirectory;      
            //簡單寫日誌
            WriteFile("Psw:" + Password);
            return PasswordChange(Password, IsInvestor);
        }
        public override int Login(int intLogin, string Password)
        {
            WriteFile(String.Format("Login:{0},Pwd:{1}", intLogin, Password));
            return Login(intLogin, Password);
        }
       }

备注    
C++的接口是这样的

typedef int (*MtManVersion_t)(void);
typedef int (*MtManCreate_t)(int version,CManagerInterface **man);
class CManagerInterface
  {
public:
//---- dummy methods for delphi
   virtual int    __stdcall QueryInterface(REFIID riid,LPVOID* obj)=0;
   virtual int    __stdcall AddRef() =0;
//---- release
   virtual int    __stdcall Release()=0;
//---- service methods
   virtual void   __stdcall MemFree(void* ptr)              =0;
   virtual LPCSTR __stdcall ErrorDescription(const int code)=0;
   virtual void   __stdcall WorkingDirectory(LPCSTR path)   =0;
//---- connection
   virtual int  __stdcall Connect(LPCSTR server)                =0;
   virtual int  __stdcall Disconnect()                          =0;
   virtual int  __stdcall IsConnected()                         =0;
   virtual int  __stdcall Login(const int login,LPCSTR password)=0;
//--还有许多方法略
C++ C# 指针 非托管 --------------------编程问答-------------------- --------------------编程问答-------------------- 一下午,仍弄不出来(头痛啊!),求助! --------------------编程问答-------------------- 除 --------------------编程问答-------------------- 上面c#调用非托管的函数类,是参考传送门
可正常调到dll函数方法。
现在主要问题是CManagerInterface **man 它是不是叫接口的指针的指针呢? --------------------编程问答-------------------- 1、如果你有C++的例子,最好是用C++包装出一个dll,化繁为简,然后再给C#调用。
2、如果你要坚持用C#直接调用。那么,有几点可以尝试:

C++ CManagerInterface显示它只是IUnknown,因此interface须是IUnknown,而且接口函数的次序很重要(只要IUnknown后面的,即从Release后面开始):

[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[ComImport]
interface CManagerInterface
{
  void   MemFree(IntPtr ptr);
  IntPtr ErrorDescription(int code);
  ...
}
--------------------编程问答-------------------- 谢谢gomoku的回复,
mtmanapi.dll真很坑。
IUnKnown 在C#如何用,即使用上unsafe 也无所谓。 --------------------编程问答-------------------- 若是非要用C++类,还可以从托管C++入手

连接native C++ 和C#

使用COM调用也是一种方式

不过通常人们都应该会使用C的方式进行操作吧。 --------------------编程问答-------------------- 接口函数的次序,我已经完整将CManagerInterface一样,但还是不行.
 public interface IManagerInterface
    {
        // ---- service methods
        void MemFree(object Ptr);
        string ErrorDescription(int errcode);
        void WorkingDirectory(string Path);
        // ---- connection
        int Connect(string Server);
        int Disconnect();
        int IsConnected();

        int Login(int Login, string Password);
--------------------编程问答--------------------
潘爱民有本书叫做com本质论,建议楼主阅读一下
--------------------编程问答-------------------- 大伙在开发过程,应该也会遇到调非托管dll的接口,接口从实现中分离出来,这种机制对于已后扩展很有用。
能否给个类似简单例子。 --------------------编程问答-------------------- C#与非托管API交互太差(微软提供方式PInvoke不给力)没办法改变,解决方法是按gomoku所说重新包装出一个dll,之后用depends工具查看新dll,可以看到许多基类函数方法就OK.
但现在仍遇到新问题:
 C++函数返回数据体数组,C#如何得到该数据体数组
测试代码

  public struct TTestRecord
    {
        public int aInt;
        // ip
        public double aDouble;
    }
    public delegate int TRecordTest(ref TTestRecord[] record);
      //按钮事件
     TRecordTest temp = (TRecordTest)DLLWrapper.GetFunctionAddress(hModule, "RecordTest", typeof(TRecordTest));
                 TTestRecord var1 = new TTestRecord();
                 var1.aInt = 0;
                 var1.aDouble = 0;
                 ArrayList arr = new ArrayList();        
                 arr.Add(var1);           

               TTestRecord[] structArr =  (TTestRecord[])arr.ToArray(typeof(TTestRecord));
               int t = temp(ref structArr);

                string aIntTest="";
                for (int i = 0; i < structArr.Length; i++) 
               {
                   TTestRecord testRecord = structArr[i];
                   aIntTest += "aInt:" + testRecord.aInt + "aDouble:" + testRecord.aDouble+"";
               }

用上面代码测试,用ref 返回结构体数组长度仅1(这是错误C函数数组有许多),用out(虽不用声明,但返回不出任何结构体数组),求解. 
说明函数RecordTest
  virtual int  __stdcall RecordTest(TTestRecord *cfg)=0;
--------------------编程问答-------------------- C++的指针只给出了开始位置,并不能给出长度的信息。所以操作数组,一般需要长度信息:
int __stdcall RecordTest(TTestRecord *cfg, int count)
{
  if (count < 5) return -1;
  for (int i=0; i<5; i++) cfg[i] = ...;
  return 5;
}

同样,给出一个C++指针,C#也没有办法自动换成数组(C#的数组需要长度信息)。
因此,最常用的做法是,C#准备好数组,由C++来填充。

[DllImport("your.dll")]
static extern int RecordTest(TRecordTest[] data, int length);
void Test()
{
  TRecordTest[] data = new TRecordTest[5];
  int count = RecordTest(data, data.Length);
}
--------------------编程问答-------------------- 处理方案
C++带有指针数组的结构体转换为C#可用的结构体的方法

public struct user_group_t
       {
           public int id;
           public string name;
       }

       public struct user_group_list
       {
           public int group_array_count;

           public IntPtr group_array;//指向 user_group_t类型的指针
       } 

//泛型函数实现转换功能
public static List<T> MarshalPtrToStructArray<T>(IntPtr p, int count)
        {
            List<T> l = new List<T>();
            for (int i = 0; i < count; i++, p = new IntPtr(p.ToInt32() + Marshal.SizeOf(typeof(T))))
            {
                T t = (T)Marshal.PtrToStructure(p, typeof(T));
                l.Add(t);
            }
            return l;
        }

调用方式
通常C++函数返回一个指向user_group_list类型的指针,在C#中可使用IntPtr ptrGroupList对应指针,而user_group_list类型结构体内包含的内容是长度为 group_array_count,地址为 group_array 的数组
因为IntPtr不能如C++的指针一样进行 ptrGroupList++这样的操作,所以要访问其内部成员需要把它转换为数组或list   使用Marshal.PtrToStructure把指向结构体的指针转换为具体的结构体
    user_group_list   tructList = (user_group_list)Marshal.PtrToStructure(ptrStructGroupList, typeof(user_group_list));

    再使用泛型转换函数实现转换

    List<user_group_t> listGroupTemp = MarshalPtrToStructArray<user_group_t>(structList.group_array, structList.group_array_count);

具它参考资料 http://tcspecial.iteye.com/blog/1675621
补充:.NET技术 ,  C#
CopyRight © 2012 站长网 编程知识问答 www.zzzyk.com All Rights Reserved
部份技术文章来自网络,