将 C# 数据类型参数传递给用 C++ 编写的 dll?
Posted
技术标签:
【中文标题】将 C# 数据类型参数传递给用 C++ 编写的 dll?【英文标题】:Passing C# data type parameters to dll written in C++? 【发布时间】:2009-03-26 01:04:08 【问题描述】:仍在解决从这里开始的问题 Calling C++ dll function from C#: Of structs, strings and wchar_t arrays.,但采用不同的方法。
按照示例Calling Managed Code from Unmanaged Code and vice-versa,我用 C++ 编写了一个托管包装器来访问非托管 C++ dll 中的 unmanages 类。
看起来像这样:
//in header file
public __gc class TSSLDllWrapper
public:
TSSLDllWrapper();
//this is the unmanaged class
CcnOCRsdk * _sdk;
bool convertHKID_Name(char *code, RECO_DATA *o_data);
;
//in .cpp file
TSSLDllWrapper::TSSLDllWrapper(void)
_sdk = new CcnOCRsdk();
bool TSSLDllWrapper::convertHKID_Name(char *code, RECO_DATA *o_data)
return _sdk->convertHKID_Name(code, o_data);
//C++ RECO_DATA structure definition:
struct RECO_DATA
wchar_t FirstName[200];
wchar_t Surname[200];
;
现在我有了一个可以导入到 C# 项目中的 dll。
但问题是: 当我想从 dll 文件中调用方法时,像这样:
TSSLDllWrapper wrapper = new TSSLDllWrapper();
bool res = wrapper.convertHKID_NameSimple( //need to pass parameters here );
它需要 C++ 参数 - 指向 char 和 RECO_DATA 的指针。
如何解决这个问题并从 C# 代码传递 C++ 类型?
【问题讨论】:
【参考方案1】:转换大多数 C 数据类型的一种方法是使用PInvoke Interop Assitant。它将为大多数 C 结构创建适当的 C#/VB.Net 类型。这是 RECO_DATA 的输出
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential, CharSet=System.Runtime.InteropServices.CharSet.Unicode)]
public struct RECO_DATA
/// wchar_t[200]
[System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.ByValTStr, SizeConst=200)]
public string FirstName;
/// wchar_t[200]
[System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.ByValTStr, SizeConst=200)]
public string Surname;
对于 char* 参数,您可以传递 IntPtr.Zero 或使用 Marshal.StringToCoTaskMemAnsi 来完成工作。
【讨论】:
【参考方案2】:我在创建 dll 包装器时偶然发现的几点:
我不得不调用非托管类的成员函数。花了一些时间发现我不能直接使用 DllImport 来做到这一点,而必须自己编写一个“包装器”。 在包装器本身中,仅包装成员函数也是不够的。我必须能够创建指向 C++ 类的指针,所以我必须将指针导出到构造函数(至少,我是这么理解的,也许这并不完全正确。)。我最初尝试只导出成员函数,它编译但在运行时返回“AccessViolationException”。也卡在那里一段时间。因此,我的包装器看起来像这样(如using a class defined in a c++ dll in c# code 中所建议的那样):
public class __declspec(dllexport) Wrapper
public:
CcnOCRsdk* SDKCreate()
return new CcnOCRsdk();
bool CcnOCRsdk_HKID(CcnOCRsdk* pSDK, char *code, RECO_DATA *o_data)
return pSDK->convertHKID_Name(code, o_data);
void SDKDelete(CcnOCRsdk* pSDK)
delete pSDK;
;
__declspec(dllexport) 在类级别导出类的所有公共成员。 SDKCreate() 从我必须调用的成员函数的非托管 dll 返回指向该 CcnOCRsdk 类的指针。 CcnOCRsdk_HKID 调用该成员函数。请注意,传递了指向 CcnOCRsdk 的指针。
代码构建到 dll 中后,我必须使用 DUMPBIN 来找出我编写的包装 dll 的“损坏”入口点。
对于我的包装器,结果如下所示
1 0 00001240 ??4Wrapper@TSSL@@QAEAAV01@ABV01@@Z = __t2m@??4Wrapper@TSSL@@QAEAAV01@ABV01@@Z ([T2M] public: class TSSL::Wrapper & __thiscall TSSL::Wrapper::operator=(class TSSL::Wrapper const &)) 2 1 00001220 ?CcnOCRsdk_HKID@Wrapper@TSSL@@QAE_NPAVCcnOCRsdk@@PADPAURECO_DATA@@@Z = __t2m@?CcnOCRsdk_HKID@Wrapper@TSSL@@QAE_NPAVCcnOCRsdk@@PADPAURECO_DATA@@@Z ([T2M] public: bool __thiscall TSSL::Wrapper::CcnOCRsdk_HKID(class CcnOCRsdk *,char *,struct RECO_DATA *)) 3 2 00001200 ?SDKCreate@Wrapper@TSSL@@QAEPAVCcnOCRsdk@@XZ = ?SDKCreate@Wrapper@TSSL@@QAEPAVCcnOCRsdk@@XZ (public: class CcnOCRsdk * __thiscall TSSL::Wrapper::SDKCreate(void)) 4 3 00001410 ?SDKDelete@Wrapper@TSSL@@QAEXPAVCcnOCRsdk@@@Z = ?SDKDelete@Wrapper@TSSL@@QAEXPAVCcnOCRsdk@@@Z (public: void __thiscall TSSL::Wrapper::SDKDelete(class CcnOCRsdk *))
现在我终于准备好在 C# 中使用我的包装器了。当我在没有完全按照 dumpbin 的输出指定入口点的情况下执行此操作时,出现“无法找到入口点”错误。
[DllImport(@"TSSLWrapper.dll", EntryPoint = "?SDKCreate@Wrapper@TSSL@@QAEPAVCcnOCRsdk@@XZ")]
public static extern IntPtr SDKCreate();
[DllImport(@"TSSLWrapper.dll", EntryPoint = "?CcnOCRsdk_HKID@Wrapper@TSSL@@QAE_NPAVCcnOCRsdk@@PADPAURECO_DATA@@@Z")]
public static extern bool CcnOCRsdk_HKID(IntPtr ptr, string num, out RECO_DATA o_data);
RECO_DATA 被定义为 JaredPar 建议的。
最后一步是享受结果。 首先我必须调用类构造函数,然后将指针传递给函数的实际调用
RECO_DATA recoData = new cnOCRsdk.RECO_DATA();
string num = "262125355174";
IntPtr ptr = cnOCRsdk.SDKCreate();
bool res = cnOCRsdk.CcnOCRsdk_HKID(ptr, num, out recoData);
我的 res 返回 true,我在 recoData 中得到了我期望的结果。
【讨论】:
以上是关于将 C# 数据类型参数传递给用 C++ 编写的 dll?的主要内容,如果未能解决你的问题,请参考以下文章
将包含结构的 c++ 函数传递给 C#,并使用 Marshal.PtrToStructure 将其转换为 c# 函数