C#调用C++DLL返回值是个抽象类指针,如何调用其中的函数(不对外发布)?

时间:2022-08-31 09:11:11
C#调用C++DLL返回值是个抽象类指针,如何调用其中的函数(不对外发布)?

14 个解决方案

#1


反射方式调用

#2


不对外是什么意思,可以访问public的方法,受保护的不行

#3


c#调用c++dll 一般情况下能遇到的问题大概是一下两点
1. 数据类型转换问题 
2. 指针或地址参数传送问题 
你问的问题是什么意思,不太明白

#4


举例 :
dll  对外发布唯一接口类似于    void   Get(IInterface ** ppInter);   IInterface是抽象接口,继承 IUnknown,dll 内部有类继承自IInterface,并实现了功能。
问题  : c#如何获取ppInter? 如何访问IInterface声明的成员函数?(声明了哪些函数外部是知道的)

#5


引用 1 楼 kcxnvcs5 的回复:
反射方式调用


麻烦给点伪代码示例下.
[c++]
Interface test1
{
     void a();
     void b(char*a);
};

Interface test
{
     void GetTest1(test1**pptest);
     void b(char*a);
};

dll 对外发布唯一接口:
  void Get(test** a);

请问c#部分如何实现,给点伪代码展示下如何访问 test1 的 a()函数, 谢谢了!

#6


给你一个样例。
C++ 接口
struct ICForCS2
{
virtual int Login(const char* user_name, const char* password, int login_type, LoginInfo *plogin_info, const char* end_point_url, const char* local_ip = NULL) = 0;
virtual int GetUdiskInfo2(s_udiskinfo *a, int max) = 0;


};


C++ 的获取接口的 API。
CFORCS_API void GetICForCS2(ICForCS2 **p);


C# 定义对应的接口
	[StructLayout(LayoutKind.Sequential)]
struct INativeCSForC2
{
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
public delegate int _LoginHandler(IntPtr @this, [In, MarshalAs(UnmanagedType.LPStr)] string user_name, [In, MarshalAs(UnmanagedType.LPStr)] string password, [In, MarshalAs(UnmanagedType.I4)]int login_type, [In, Out] ref LoginInfo plogin_info, [In, MarshalAs(UnmanagedType.LPStr)] string end_point_url, [In, MarshalAs(UnmanagedType.LPStr)] string local_ip);
public _LoginHandler Login;

[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
public delegate int _GetUdiskInfoHandler([In] IntPtr @this, [In, Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] s_udiskinfo[] info, int max);
public _GetUdiskInfoHandler GetUdiskInfo;
}


C# 定义这个接口。
	interface ICSForC22
{
int Login([In, MarshalAs(UnmanagedType.LPStr)] string user_name, [In, MarshalAs(UnmanagedType.LPStr)] string password, [In, MarshalAs(UnmanagedType.I4)]int login_type, [In, Out] ref LoginInfo plogin_info, [In, MarshalAs(UnmanagedType.LPStr)] string end_point_url, [In, MarshalAs(UnmanagedType.LPStr)] string local_ip);
int GetUdiskInfo(s_udiskinfo[] info);
}


添加一个包装类。
	class NativeCSForC2 : ICSForC22
{
public NativeVTable<INativeCSForC2> _native;
int ICSForC22.Login(string user_name, string password, int login_type, ref LoginInfo login_info, string end_point_url, string local_ip)
{
return _native._nativeInterface.Login(_native._ptrInterface, user_name, password, login_type, ref login_info, end_point_url, local_ip);
}

int ICSForC22.GetUdiskInfo(s_udiskinfo[] info)
{
return _native._nativeInterface.GetUdiskInfo(_native._ptrInterface, info, info.Length);
}

}


C# 定义 API 函数对应的方法。
	
[DllImport(....)]
private static extern void _GetICForCS2([Out] out IntPtr ptrInterface);
public static NativeCSForC2 GetICForCS2()
{
IntPtr ptrInterface;
_GetICForCS2(out ptrInterface);
return new NativeCSForC2
{
_native = new NativeVTable<INativeCSForC2>(ptrInterface),
};
}


如果使用很多的话,定义一个 模板包装
	class NativeVTable< I > : IDisposable
{
public IntPtr _ptrInterface;
public I _nativeInterface;
private Action< IntPtr > _release = null;
public NativeVTable(IntPtr ptrInterface)
{
System.Diagnostics.Debug.Assert(ptrInterface != IntPtr.Zero);
_ptrInterface = ptrInterface;
_nativeInterface = (I)Marshal.PtrToStructure(Marshal.ReadIntPtr(ptrInterface, 0), typeof(I));
}
public NativeVTable(IntPtr ptrInterface, Action< IntPtr > release) : this(ptrInterface)
{
_release = release;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}

private bool disposed = false;
protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
if (_release != null)
{
_release(_ptrInterface);
}
disposed = true;
}
}

~NativeVTable()
{
Dispose(false);
}

}





最后使用:
	var vv = (ICSForC22)NativeImport.GetICForCS2();

#7


简单说,就是顶一个和 C 虚表一致的  [StructLayout(LayoutKind.Sequential)] 特性的接口。
这样,就可以把虚表中的方法转换到 托管内存中。

在定义个C# 的类来包装这个,并暴露一个一致的、C#模式的接口。(如果内部使用,就可以不要包装,直接调用)。

在使用的时候,使用 C 提供的 API 来获取接口,解析这个接口,获取虚表。 p->lpVtbl 就是虚表的位置。
在 C# 中,她其实就是其 0 偏移的引用。
实现 C# 接口,就是找到该方法对应的 虚表中的方法委托,然后呼叫之。

如果是 IUnknown 接口,需要自己实现更加复杂的 IUnknown 的三个方法。

我记得好像不久前,也有人问,怎么处理一个接口的问题。你可以看看。

#8


引用 7 楼 Saleayas 的回复:
简单说,就是顶一个和 C 虚表一致的  [StructLayout(LayoutKind.Sequential)] 特性的接口。
这样,就可以把虚表中的方法转换到 托管内存中。

在定义个C# 的类来包装这个,并暴露一个一致的、C#模式的接口。(如果内部使用,就可以不要包装,直接调用)。

在使用的时候,使用 C 提供的 API 来获取接口,解析这个接口,获取虚表。 p->lpVtbl 就是虚表的位置。
在 C# 中,她其实就是其 0 偏移的引用。
实现 C# 接口,就是找到该方法对应的 虚表中的方法委托,然后呼叫之。

如果是 IUnknown 接口,需要自己实现更加复杂的 IUnknown 的三个方法。

我记得好像不久前,也有人问,怎么处理一个接口的问题。你可以看看。


非常感谢你,一般的interface用你的方法是可以,还有两点请教下:
1)virtual int  _stdcall  Login()--------------会出错;
2)Iunknow 3个方法我是定义在NativeVTable里还是interface ICSForC22里。
盼回复。

#9


这个应该没法实现吧。。。

用CLI封装C++,然后暴露托管接口,这样是不是好点。

#10


可以实现的,现在就是Iunknown的三个方法不太好搞。

#11



((Action)Marshal.GetDelegateForFunctionPointer((IntPtr)(**(int**)a), typeof(Action)))();

#12


__thiscall 
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
__stdcall
[UnmanagedFunctionPointer(CallingConvention.StdCall)] //或者删除这句,因为缺省就是 StdCall。
//而 C 语言的导出函数呼叫约定缺省也应该是 __stdcall 。

我是看你的代码里面没有 __stdcall 约定才明确写上的,在C/C++ 里面定义导出最好使用 __stdcall.
这样在 C# 里面就是缺省的呼叫约定了。


至于 IUnknown 不需要在 C# 里面导出,因为 C# 的 AddRef 和 Release 是由GC 决定的。
你需要自己处理,使用 IDisposed 接口。
至于 QI 在 C# 里面是不存在的,她对应于类型转换。
当你需要 QI 除 IUnknown 之外的接口,你都需要有对应的C# 接口,然后,就可以he 样例中 API 一样,QI 到指定的接口,然后转换为对应的 C# 的接口实例。

当你的接口继承 IUnknwon 时,需要注意 INativeCSForC2 接口布局,有三个 IUnknown 的接口方法委托在前面。

#13


引用 12 楼 Saleayas 的回复:
__thiscall 
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
__stdcall
[UnmanagedFunctionPointer(CallingConvention.StdCall)] //或者删除这句,因为缺省就是 StdCall。
//而 C 语言的导出函数呼叫约定缺省也应该是 __stdcall 。

我是看你的代码里面没有 __stdcall 约定才明确写上的,在C/C++ 里面定义导出最好使用 __stdcall.
这样在 C# 里面就是缺省的呼叫约定了。


至于 IUnknown 不需要在 C# 里面导出,因为 C# 的 AddRef 和 Release 是由GC 决定的。
你需要自己处理,使用 IDisposed 接口。
至于 QI 在 C# 里面是不存在的,她对应于类型转换。
当你需要 QI 除 IUnknown 之外的接口,你都需要有对应的C# 接口,然后,就可以he 样例中 API 一样,QI 到指定的接口,然后转换为对应的 C# 的接口实例。

当你的接口继承 IUnknwon 时,需要注意 INativeCSForC2 接口布局,有三个 IUnknown 的接口方法委托在前面。


谢谢 Saleayas 的细心回答,在你的指导下  c#里调用c++的接口貌似功能函数方面可以调用了 ,引用计数的问题,后面我再 研究研究。

现在有个新的问题 ,例如:

【c++】
  声明接口,不实现:
   ITest :IUnknown
{
       void SetInfo(void** pInfo);
}

IBase
{
     void   SetITest(ITest*   pITest);
}

[c#]
需要实现ITest, 我该如何定义呢?

#14


[c#实现代码如下:]

[StructLayout(LayoutKind.Sequential)]
    public class ITest
{
    [UnmanagedFunctionPointer(CallingConvention.StdCall)]
    public delegate int QueryInterfaceHandler(ref Guid riid, out IntPtr ppvObject);
    public QueryInterfaceHandler QueryInterface = new QueryInterfaceHandler(QueryInterfaceFunc);

    [UnmanagedFunctionPointer(CallingConvention.StdCall)]
    public delegate uint AddRefHandler(IntPtr @this);
    public AddRefHandler AddRef ;

    [UnmanagedFunctionPointer(CallingConvention.StdCall)]
    public delegate uint ReleaseHandler(IntPtr @this);
    public ReleaseHandler Release;

    [UnmanagedFunctionPointer(CallingConvention.StdCall)]
    public delegate void SetInfoHandler(ref IntPtr pInfo);
    public SetInfoHandler SetInfo= newSetInfoHandler(SetInfoFunc);


第一次传入c++是可以调用到addref/Release等函数,出来后,再调用发现对应的vtable都是野指针看着像是释放了。
求高人指点?

#1


反射方式调用

#2


不对外是什么意思,可以访问public的方法,受保护的不行

#3


c#调用c++dll 一般情况下能遇到的问题大概是一下两点
1. 数据类型转换问题 
2. 指针或地址参数传送问题 
你问的问题是什么意思,不太明白

#4


举例 :
dll  对外发布唯一接口类似于    void   Get(IInterface ** ppInter);   IInterface是抽象接口,继承 IUnknown,dll 内部有类继承自IInterface,并实现了功能。
问题  : c#如何获取ppInter? 如何访问IInterface声明的成员函数?(声明了哪些函数外部是知道的)

#5


引用 1 楼 kcxnvcs5 的回复:
反射方式调用


麻烦给点伪代码示例下.
[c++]
Interface test1
{
     void a();
     void b(char*a);
};

Interface test
{
     void GetTest1(test1**pptest);
     void b(char*a);
};

dll 对外发布唯一接口:
  void Get(test** a);

请问c#部分如何实现,给点伪代码展示下如何访问 test1 的 a()函数, 谢谢了!

#6


给你一个样例。
C++ 接口
struct ICForCS2
{
virtual int Login(const char* user_name, const char* password, int login_type, LoginInfo *plogin_info, const char* end_point_url, const char* local_ip = NULL) = 0;
virtual int GetUdiskInfo2(s_udiskinfo *a, int max) = 0;


};


C++ 的获取接口的 API。
CFORCS_API void GetICForCS2(ICForCS2 **p);


C# 定义对应的接口
	[StructLayout(LayoutKind.Sequential)]
struct INativeCSForC2
{
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
public delegate int _LoginHandler(IntPtr @this, [In, MarshalAs(UnmanagedType.LPStr)] string user_name, [In, MarshalAs(UnmanagedType.LPStr)] string password, [In, MarshalAs(UnmanagedType.I4)]int login_type, [In, Out] ref LoginInfo plogin_info, [In, MarshalAs(UnmanagedType.LPStr)] string end_point_url, [In, MarshalAs(UnmanagedType.LPStr)] string local_ip);
public _LoginHandler Login;

[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
public delegate int _GetUdiskInfoHandler([In] IntPtr @this, [In, Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] s_udiskinfo[] info, int max);
public _GetUdiskInfoHandler GetUdiskInfo;
}


C# 定义这个接口。
	interface ICSForC22
{
int Login([In, MarshalAs(UnmanagedType.LPStr)] string user_name, [In, MarshalAs(UnmanagedType.LPStr)] string password, [In, MarshalAs(UnmanagedType.I4)]int login_type, [In, Out] ref LoginInfo plogin_info, [In, MarshalAs(UnmanagedType.LPStr)] string end_point_url, [In, MarshalAs(UnmanagedType.LPStr)] string local_ip);
int GetUdiskInfo(s_udiskinfo[] info);
}


添加一个包装类。
	class NativeCSForC2 : ICSForC22
{
public NativeVTable<INativeCSForC2> _native;
int ICSForC22.Login(string user_name, string password, int login_type, ref LoginInfo login_info, string end_point_url, string local_ip)
{
return _native._nativeInterface.Login(_native._ptrInterface, user_name, password, login_type, ref login_info, end_point_url, local_ip);
}

int ICSForC22.GetUdiskInfo(s_udiskinfo[] info)
{
return _native._nativeInterface.GetUdiskInfo(_native._ptrInterface, info, info.Length);
}

}


C# 定义 API 函数对应的方法。
	
[DllImport(....)]
private static extern void _GetICForCS2([Out] out IntPtr ptrInterface);
public static NativeCSForC2 GetICForCS2()
{
IntPtr ptrInterface;
_GetICForCS2(out ptrInterface);
return new NativeCSForC2
{
_native = new NativeVTable<INativeCSForC2>(ptrInterface),
};
}


如果使用很多的话,定义一个 模板包装
	class NativeVTable< I > : IDisposable
{
public IntPtr _ptrInterface;
public I _nativeInterface;
private Action< IntPtr > _release = null;
public NativeVTable(IntPtr ptrInterface)
{
System.Diagnostics.Debug.Assert(ptrInterface != IntPtr.Zero);
_ptrInterface = ptrInterface;
_nativeInterface = (I)Marshal.PtrToStructure(Marshal.ReadIntPtr(ptrInterface, 0), typeof(I));
}
public NativeVTable(IntPtr ptrInterface, Action< IntPtr > release) : this(ptrInterface)
{
_release = release;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}

private bool disposed = false;
protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
if (_release != null)
{
_release(_ptrInterface);
}
disposed = true;
}
}

~NativeVTable()
{
Dispose(false);
}

}





最后使用:
	var vv = (ICSForC22)NativeImport.GetICForCS2();

#7


简单说,就是顶一个和 C 虚表一致的  [StructLayout(LayoutKind.Sequential)] 特性的接口。
这样,就可以把虚表中的方法转换到 托管内存中。

在定义个C# 的类来包装这个,并暴露一个一致的、C#模式的接口。(如果内部使用,就可以不要包装,直接调用)。

在使用的时候,使用 C 提供的 API 来获取接口,解析这个接口,获取虚表。 p->lpVtbl 就是虚表的位置。
在 C# 中,她其实就是其 0 偏移的引用。
实现 C# 接口,就是找到该方法对应的 虚表中的方法委托,然后呼叫之。

如果是 IUnknown 接口,需要自己实现更加复杂的 IUnknown 的三个方法。

我记得好像不久前,也有人问,怎么处理一个接口的问题。你可以看看。

#8


引用 7 楼 Saleayas 的回复:
简单说,就是顶一个和 C 虚表一致的  [StructLayout(LayoutKind.Sequential)] 特性的接口。
这样,就可以把虚表中的方法转换到 托管内存中。

在定义个C# 的类来包装这个,并暴露一个一致的、C#模式的接口。(如果内部使用,就可以不要包装,直接调用)。

在使用的时候,使用 C 提供的 API 来获取接口,解析这个接口,获取虚表。 p->lpVtbl 就是虚表的位置。
在 C# 中,她其实就是其 0 偏移的引用。
实现 C# 接口,就是找到该方法对应的 虚表中的方法委托,然后呼叫之。

如果是 IUnknown 接口,需要自己实现更加复杂的 IUnknown 的三个方法。

我记得好像不久前,也有人问,怎么处理一个接口的问题。你可以看看。


非常感谢你,一般的interface用你的方法是可以,还有两点请教下:
1)virtual int  _stdcall  Login()--------------会出错;
2)Iunknow 3个方法我是定义在NativeVTable里还是interface ICSForC22里。
盼回复。

#9


这个应该没法实现吧。。。

用CLI封装C++,然后暴露托管接口,这样是不是好点。

#10


可以实现的,现在就是Iunknown的三个方法不太好搞。

#11



((Action)Marshal.GetDelegateForFunctionPointer((IntPtr)(**(int**)a), typeof(Action)))();

#12


__thiscall 
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
__stdcall
[UnmanagedFunctionPointer(CallingConvention.StdCall)] //或者删除这句,因为缺省就是 StdCall。
//而 C 语言的导出函数呼叫约定缺省也应该是 __stdcall 。

我是看你的代码里面没有 __stdcall 约定才明确写上的,在C/C++ 里面定义导出最好使用 __stdcall.
这样在 C# 里面就是缺省的呼叫约定了。


至于 IUnknown 不需要在 C# 里面导出,因为 C# 的 AddRef 和 Release 是由GC 决定的。
你需要自己处理,使用 IDisposed 接口。
至于 QI 在 C# 里面是不存在的,她对应于类型转换。
当你需要 QI 除 IUnknown 之外的接口,你都需要有对应的C# 接口,然后,就可以he 样例中 API 一样,QI 到指定的接口,然后转换为对应的 C# 的接口实例。

当你的接口继承 IUnknwon 时,需要注意 INativeCSForC2 接口布局,有三个 IUnknown 的接口方法委托在前面。

#13


引用 12 楼 Saleayas 的回复:
__thiscall 
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
__stdcall
[UnmanagedFunctionPointer(CallingConvention.StdCall)] //或者删除这句,因为缺省就是 StdCall。
//而 C 语言的导出函数呼叫约定缺省也应该是 __stdcall 。

我是看你的代码里面没有 __stdcall 约定才明确写上的,在C/C++ 里面定义导出最好使用 __stdcall.
这样在 C# 里面就是缺省的呼叫约定了。


至于 IUnknown 不需要在 C# 里面导出,因为 C# 的 AddRef 和 Release 是由GC 决定的。
你需要自己处理,使用 IDisposed 接口。
至于 QI 在 C# 里面是不存在的,她对应于类型转换。
当你需要 QI 除 IUnknown 之外的接口,你都需要有对应的C# 接口,然后,就可以he 样例中 API 一样,QI 到指定的接口,然后转换为对应的 C# 的接口实例。

当你的接口继承 IUnknwon 时,需要注意 INativeCSForC2 接口布局,有三个 IUnknown 的接口方法委托在前面。


谢谢 Saleayas 的细心回答,在你的指导下  c#里调用c++的接口貌似功能函数方面可以调用了 ,引用计数的问题,后面我再 研究研究。

现在有个新的问题 ,例如:

【c++】
  声明接口,不实现:
   ITest :IUnknown
{
       void SetInfo(void** pInfo);
}

IBase
{
     void   SetITest(ITest*   pITest);
}

[c#]
需要实现ITest, 我该如何定义呢?

#14


[c#实现代码如下:]

[StructLayout(LayoutKind.Sequential)]
    public class ITest
{
    [UnmanagedFunctionPointer(CallingConvention.StdCall)]
    public delegate int QueryInterfaceHandler(ref Guid riid, out IntPtr ppvObject);
    public QueryInterfaceHandler QueryInterface = new QueryInterfaceHandler(QueryInterfaceFunc);

    [UnmanagedFunctionPointer(CallingConvention.StdCall)]
    public delegate uint AddRefHandler(IntPtr @this);
    public AddRefHandler AddRef ;

    [UnmanagedFunctionPointer(CallingConvention.StdCall)]
    public delegate uint ReleaseHandler(IntPtr @this);
    public ReleaseHandler Release;

    [UnmanagedFunctionPointer(CallingConvention.StdCall)]
    public delegate void SetInfoHandler(ref IntPtr pInfo);
    public SetInfoHandler SetInfo= newSetInfoHandler(SetInfoFunc);


第一次传入c++是可以调用到addref/Release等函数,出来后,再调用发现对应的vtable都是野指针看着像是释放了。
求高人指点?