将 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#

将包含结构的 c++ 函数传递给 C#,并使用 Marshal.PtrToStructure 将其转换为 c# 函数

如何将 XML 数据作为参数传递给从 C# 到 Sql 服务器的 TVP 参数

将参数传递给C#中的存储过程

如何将变量参数传递给 C++ 中的 DBUS 方法调用?

从 Swift 将文件作为参数传递给 C++ 方法