求助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++的接口是这样的
C++ C# 指针 非托管 --------------------编程问答-------------------- --------------------编程问答-------------------- 一下午,仍弄不出来(头痛啊!),求助! --------------------编程问答-------------------- 除 --------------------编程问答-------------------- 上面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;
//--还有许多方法略
可正常调到dll函数方法。
现在主要问题是CManagerInterface **man 它是不是叫接口的指针的指针呢? --------------------编程问答-------------------- 1、如果你有C++的例子,最好是用C++包装出一个dll,化繁为简,然后再给C#调用。
2、如果你要坚持用C#直接调用。那么,有几点可以尝试:
C++ CManagerInterface显示它只是IUnknown,因此interface须是IUnknown,而且接口函数的次序很重要(只要IUnknown后面的,即从Release后面开始):
--------------------编程问答-------------------- 谢谢gomoku的回复,
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[ComImport]
interface CManagerInterface
{
void MemFree(IntPtr ptr);
IntPtr ErrorDescription(int code);
...
}
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#