none
C# application crashes after calling C++ Method to get return value in Windows 7 64bit RRS feed

  • Question

  • There is an issue on Windows 7 64 bit OS, It only happens in this OS.

     It works fine on windows 2008 32/64bit OS.

    1) Client codes , C# codes:

    1.1) The definition of GetDeviceInfo in C#,

    [DllImport("DeviceAccess.dll", CharSet = CharSet.Auto)]
    public staticexternbool GetDeviceInfo(ITopologyPath pPath, string networkAddress, string TypeId, bool displayErrorMsg, ref StringBuilder deviceName, ref StringBuilder firmwareRevision, ref StringBuilder catalogInfo, ref StringBuilder hardwareType, ref StringBuilder hardwareRevision, ref StringBuilder manufacturer);

    1.2) to Call C++  Server method in C#:
    public void TestGetDeviceInfo()
            {
                StringBuilder deviceName = new StringBuilder();
                StringBuilder firmwareRevision = new StringBuilder();
                StringBuilder catalogInfo = new StringBuilder(networkInfo.CatalogInfo);
                StringBuilder hardwareType = new StringBuilder();
                StringBuilder hardwareRevision = new StringBuilder();
                try
                {
                    bool bRetVal = GetDeviceInfo(topPath, topPath.Name, TypeID, false, ref deviceName, ref firmwareRevision, ref catalogInfo, ref hardwareType, ref hardwareRevision, ref manufacturer);
                    System.Diagnostics.Trace.WriteLine("Get return values");
                }
                catch (Exception exp)
                {
                    MessageBox.Show(exp.Message, Resources.Strings.BrowserError, MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
            }
    • Edited by ScottQ Friday, March 30, 2012 3:03 AM
    Friday, March 30, 2012 2:14 AM

Answers

  • Hello ScottQ,

    1. I assume that the GetDeviceInfo() is actually a global exported function despite the fact that it is defined as a member function of a CDeviceAccessApp class. I assume this is a typo error

    2. There are several invalid parameter declarations for the GetDeviceInfo() function DllImport declaration in C# :

    2.1 The deviceName, firmwareRevision, catalogInfo, hardwareType, hardwareRevision and manufacturer parameters in the C++ function are BSTR pointers which imply that they are return values. That is, they are "out" parameters.

    2.2 The corresponding parameters in the C# declaration are defined as reference parameters ("ref") which indicate that they are to be marshaled into and out of the function. It may seem trivial but it is not trivial. It is not correct. They should have been defined as "out" parameters.

    2.3 The parameters in the C# declaration are defined to be StringBuilder types. This is very wrong. They should be declared as strings and attributed with "[MarshalAs(UnmanagedType.BStr)]".

    2.4 The "displayErrorMsg" parameter should be attributed with "[MarshalAs(UnmanagedType.Bool)]".

    2.5 The return type for the GetDeviceInfo() DllImport declaration should be attributed with "[return: MarshalAs(UnmanagedType.Bool)]".

    3. The following is a correct declaration for GetDeviceInfo() :

    [DllImport("DeviceAccess.dll", CharSet = CharSet.Auto)]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool GetDeviceInfo
    (
      ITopologyPath pPath, 
      string networkAddress, 
      string TypeId, 
      [MarshalAs(UnmanagedType.Bool)] bool displayErrorMsg, 
      [MarshalAs(UnmanagedType.BStr)] out string deviceName, 
      [MarshalAs(UnmanagedType.BStr)] out string firmwareRevision, 
      [MarshalAs(UnmanagedType.BStr)] out string catalogInfo, 
      [MarshalAs(UnmanagedType.BStr)] out string hardwareType, 
      [MarshalAs(UnmanagedType.BStr)] out string hardwareRevision, 
      [MarshalAs(UnmanagedType.BStr)] out string manufacturer
    );

    4. Concerning StringBuilders :

    4.1 The StringBuilder, when used as a parameter for PInvoke purposes, should be viewed as a holder of character buffers (Ansi or Unicode depending on the specified CharSet).

    4.2 Furthermore, a StringBuilder must be initialized with a size for its internal buffer before it is to be used in the call to the external DLL function.

    4.3 The parameter for the unmanaged function is typically defined as "char*" or "wchar_t*".

    4.4 In fact, the internal buffer of the StringBuilder is directly passed to the API as a "char*" or a "wchar_t*".

    4.4 The unmanaged code will typically directly assign character values into the character buffer (from the StringBuilder). It must not do things like assigning the character pointers to values returned from ::SysAllocString() or CString::AllocSysString().

    - Bio.


    Please visit my blog : http://limbioliong.wordpress.com/

    • Proposed as answer by Mike FengModerator Saturday, March 31, 2012 5:46 AM
    • Marked as answer by ScottQ Monday, April 2, 2012 12:25 AM
    Friday, March 30, 2012 8:05 AM

All replies

  • 2)Server code, C++:

    __declspec(dllexport) BOOL CDeviceAccessApp::GetDeviceInfo(ITopologyPath* pPath, BSTR networkAddress, BSTR TypeId, BOOL displayErrorMsg, BSTR* deviceName, BSTR* firmwareRevision, BSTR* catalogInfo, BSTR* hardwareType, BSTR* hardwareRevision, BSTR* manufacturer)
    {
    	BOOL bRetVal = FALSE;  // initially failure.
    	CString csDeviceName("");
    	CString csFirmwareRevision("");
    	CString csCatalogInfo("");
    	CString csHardwareType("");
    	CString csHardwareRevision("");
    	CString csManufacturer("");
    	
    	CString TypeIdent = TypeId;	
           if ( 0 == TypeIdent.CompareNoCase( CString( "543A776B703C" ) ) )	
    	{
    		
    	   csCatalogInfo.Format(IDS_CatalogInfo1);
    		
    	}
    	else ( 0 == TypeIdent.CompareNoCase( CString( "F28F6AAE6F" ) ) )
    	{
               csCatalogInfo.Format(IDS_CatalogInfo2);
    	}	
    	*deviceName		= csDeviceName.AllocSysString();
    	*firmwareRevision	= csFirmwareRevision.AllocSysString();
    	*hardwareType		= csHardwareType.AllocSysString();
    	*hardwareRevision	= csHardwareRevision.AllocSysString();
    	*manufacturer		= csManufacturer.AllocSysString();
    	*catalogInfo = csCatalogInfo.AllocSysString(); OutputDebugString(_T("To return values"));
    	return bRetVal;
    }
     Sometime it will crash in C# application (not 100%), _T("To return values")) can be seen in debug viewer;

     However in c# Client code System.Diagnostics.Trace.WriteLine("Get return values") cannot be seen in debug viewer. It does not step into Catch code either.  In a word, appliction crashes at GetDeviceInfo in C# Client code without stepping into (Catching) code.

    Thanks

    Scott


    • Edited by ScottQ Friday, March 30, 2012 4:47 AM
    Friday, March 30, 2012 2:54 AM
  • Hello ScottQ,

    1. I assume that the GetDeviceInfo() is actually a global exported function despite the fact that it is defined as a member function of a CDeviceAccessApp class. I assume this is a typo error

    2. There are several invalid parameter declarations for the GetDeviceInfo() function DllImport declaration in C# :

    2.1 The deviceName, firmwareRevision, catalogInfo, hardwareType, hardwareRevision and manufacturer parameters in the C++ function are BSTR pointers which imply that they are return values. That is, they are "out" parameters.

    2.2 The corresponding parameters in the C# declaration are defined as reference parameters ("ref") which indicate that they are to be marshaled into and out of the function. It may seem trivial but it is not trivial. It is not correct. They should have been defined as "out" parameters.

    2.3 The parameters in the C# declaration are defined to be StringBuilder types. This is very wrong. They should be declared as strings and attributed with "[MarshalAs(UnmanagedType.BStr)]".

    2.4 The "displayErrorMsg" parameter should be attributed with "[MarshalAs(UnmanagedType.Bool)]".

    2.5 The return type for the GetDeviceInfo() DllImport declaration should be attributed with "[return: MarshalAs(UnmanagedType.Bool)]".

    3. The following is a correct declaration for GetDeviceInfo() :

    [DllImport("DeviceAccess.dll", CharSet = CharSet.Auto)]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool GetDeviceInfo
    (
      ITopologyPath pPath, 
      string networkAddress, 
      string TypeId, 
      [MarshalAs(UnmanagedType.Bool)] bool displayErrorMsg, 
      [MarshalAs(UnmanagedType.BStr)] out string deviceName, 
      [MarshalAs(UnmanagedType.BStr)] out string firmwareRevision, 
      [MarshalAs(UnmanagedType.BStr)] out string catalogInfo, 
      [MarshalAs(UnmanagedType.BStr)] out string hardwareType, 
      [MarshalAs(UnmanagedType.BStr)] out string hardwareRevision, 
      [MarshalAs(UnmanagedType.BStr)] out string manufacturer
    );

    4. Concerning StringBuilders :

    4.1 The StringBuilder, when used as a parameter for PInvoke purposes, should be viewed as a holder of character buffers (Ansi or Unicode depending on the specified CharSet).

    4.2 Furthermore, a StringBuilder must be initialized with a size for its internal buffer before it is to be used in the call to the external DLL function.

    4.3 The parameter for the unmanaged function is typically defined as "char*" or "wchar_t*".

    4.4 In fact, the internal buffer of the StringBuilder is directly passed to the API as a "char*" or a "wchar_t*".

    4.4 The unmanaged code will typically directly assign character values into the character buffer (from the StringBuilder). It must not do things like assigning the character pointers to values returned from ::SysAllocString() or CString::AllocSysString().

    - Bio.


    Please visit my blog : http://limbioliong.wordpress.com/

    • Proposed as answer by Mike FengModerator Saturday, March 31, 2012 5:46 AM
    • Marked as answer by ScottQ Monday, April 2, 2012 12:25 AM
    Friday, March 30, 2012 8:05 AM
  • Hi Bio,

    Thanks Very much for your help.

    Scott

    Monday, April 2, 2012 12:29 AM