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

遊戲王千金 2013-05-20 04:54:12
调用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;
//--还有许多方法略
...全文
441 13 打赏 收藏 转发到动态 举报
写回复
用AI写文章
13 条回复
切换为时间正序
请发表友善的回复…
发表回复
遊戲王千金 2013-05-28
  • 打赏
  • 举报
回复
处理方案 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
gomoku 2013-05-23
  • 打赏
  • 举报
回复
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);
}
遊戲王千金 2013-05-23
  • 打赏
  • 举报
回复
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;
遊戲王千金 2013-05-21
  • 打赏
  • 举报
回复
大伙在开发过程,应该也会遇到调非托管dll的接口,接口从实现中分离出来,这种机制对于已后扩展很有用。 能否给个类似简单例子。
真相重于对错 2013-05-20
  • 打赏
  • 举报
回复
潘爱民有本书叫做com本质论,建议楼主阅读一下
遊戲王千金 2013-05-20
  • 打赏
  • 举报
回复
接口函数的次序,我已经完整将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);
wenbin 2013-05-20
  • 打赏
  • 举报
回复
若是非要用C++类,还可以从托管C++入手 连接native C++ 和C# 使用COM调用也是一种方式 不过通常人们都应该会使用C的方式进行操作吧。
遊戲王千金 2013-05-20
  • 打赏
  • 举报
回复
谢谢gomoku的回复, mtmanapi.dll真很坑。 IUnKnown 在C#如何用,即使用上unsafe 也无所谓。
gomoku 2013-05-20
  • 打赏
  • 举报
回复
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);
  ...
}
遊戲王千金 2013-05-20
  • 打赏
  • 举报
回复
上面c#调用非托管的函数类,是参考传送门 可正常调到dll函数方法。 现在主要问题是CManagerInterface **man 它是不是叫接口的指针的指针呢?
遊戲王千金 2013-05-20
  • 打赏
  • 举报
回复
一下午,仍弄不出来(头痛啊!),求助!
七神之光 2013-05-20
  • 打赏
  • 举报
回复

110,534

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 C#
社区管理员
  • C#
  • Web++
  • by_封爱
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

让您成为最强悍的C#开发者

试试用AI创作助手写篇文章吧