简体   繁体   中英

Passing C# data type parameters to dll written in C++?

Still working on a problem that started from here Calling C++ dll function from C#: Of structs, strings and wchar_t arrays. , but with a different approach.

Following the example Calling Managed Code from Unmanaged Code and vice-versa I wrote a managed wrapper in C++ to access the unmanages class in the unmanaged C++ dll.

It looks like this:

//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];
};

Now I have a dll that I can import into my C# project.

Here is the problem however: When I want to call the method from the dll file, like this:

TSSLDllWrapper wrapper = new TSSLDllWrapper();
bool res = wrapper.convertHKID_NameSimple( //need to pass parameters here );

It expects the C++ parameters - pointers to char and RECO_DATA.

How can I fix this and pass C++ types from C# code?

One way to convert most C data types is to use the PInvoke Interop Assitant . It will create proper C# / VB.Net types for most C structures. Here is the output for 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;
}

For the char* parameter, you can pass IntPtr.Zero or use Marshal.StringToCoTaskMemAnsi to get the job done.

A couple of points I stumbled on while creating a dll wrapper:

  • I had to call a member function of the unmanaged class. Took some time to find out that I can not do this by using DllImport directly, but have to write a 'wrapper' myself.
  • In the wrapper itself, it is also not enough to wrap just the member function. I have to be able to create a pointer to the C++ class, so I have to export the pointer to the constructor (at least, that's how I understand it, maybe that's not exactly correct.). I tried just exporting the member function initially, it compiled but returned "AccessViolationException" at runtime. Got stuck there for a while too.

Therefore, my wrapper looks like this (as suggested in 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) on the class level exports all public members of the class. SDKCreate() returns a pointer to that CcnOCRsdk class from the unmanaged dll which member function I have to call. CcnOCRsdk_HKID calls that member function. Note that the pointer to the CcnOCRsdk is passed.

After the code builds into the dll, I have to use the DUMPBIN to find out what are the 'mangled' entry points for the wrapper dll I wrote.

For my wrapper, the results look like this

  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 *)) 

Now I'm finally ready to use my wrapper in the C#. When I did this without specifying the entry point exactly as in my output from dumpbin, I got 'unable to find entry point' error.

    [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);

The RECO_DATA is defined as JaredPar suggested.

And the last step is to enjoy the results. First I have to call the class constructor, and then pass the pointer to the actual call to the function

  RECO_DATA recoData = new cnOCRsdk.RECO_DATA();
  string num = "262125355174";

  IntPtr ptr = cnOCRsdk.SDKCreate();
  bool res = cnOCRsdk.CcnOCRsdk_HKID(ptr, num, out recoData);

My res returns true, and I get the results I expected in recoData.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM