Applikation beendet sich ohne weitere Meldung [AnyCPU/x86 Buildunterschiede]



  • Hi,
    ich habe hier einen Projektor welcher über die Cypress USB Schnittstelle (CY7C65215) angesteuert wird und Befehle per I2C weitergibt. Die DLL CyUsbSerial wurde vermutlich in C++ geschrieben und bietet leider keine .NET Schnittstelle, was einen DLLImport erforderlich macht. Leider liegt auch hier (anscheinend) das Problem.

    Meine Testsoftware beendet sich ohne eine Exception. Abhängig vom Build läuft das Programm als AnyCPU mit dem 'Prefer 32-bit Flag' stabil. Ohne diesem Flag wird die Executable als 64-Bit Applikation gebaut und funktioniert verständlicherweise nicht mit der 32-Bit-DLL (eine 64-Bit Version existiert übrigens nicht). Setzt man den Build auf x86, dann beendet sich die Software recht schnell ohne eine Meldung zurückzugeben.

    Ich weiß nicht, ob das mit /LARGEADDRESSAWARE (x86) in irgendeiner Weise zusammenhängen könnte.
    Leider wird auch keine AccessViolationException geworfen...oder zumindest nicht sichtbar geworfen.

    Im nachfolgenden die DLLImports, evtl. versteckt sich ja bereits hier der Fehler.

    [DllImport("cyusbserial.dll", CallingConvention = CallingConvention.Cdecl, SetLastError = true)]
    		public static extern CY_RETURN_STATUS CyGetListofDevices(out int numberOfDevices);
    
    		[DllImport("cyusbserial.dll", CallingConvention = CallingConvention.Cdecl, SetLastError = true)]
    		public static extern CY_RETURN_STATUS CyGetDeviceInfo([In] byte DeviceNumber, out CY_DEVICE_INFO pDeviceInfo);
    
    		[DllImport("cyusbserial.dll", CallingConvention = CallingConvention.Cdecl, SetLastError = true)]
    		public static extern CY_RETURN_STATUS CyOpen(byte DeviceNumber, byte interfaceNum, out IntPtr hHandle);
    
    		[DllImport("cyusbserial.dll", CallingConvention = CallingConvention.Cdecl, SetLastError = true)]
    		public static extern CY_RETURN_STATUS CyClose(IntPtr hHandle);
    
    		[DllImport("cyusbserial.dll", CallingConvention = CallingConvention.Cdecl, SetLastError = true)]
    		public static extern CY_RETURN_STATUS CyGetI2cConfig(IntPtr hHandle, out CY_I2C_CONFIG I2CConfig);
    
    		[DllImport("cyusbserial.dll", CallingConvention = CallingConvention.Cdecl, SetLastError = true)]
    		public static extern CY_RETURN_STATUS CySetI2cConfig(IntPtr hHandle, ref CY_I2C_CONFIG I2CConfig);
    
    		[DllImport("cyusbserial.dll", CallingConvention = CallingConvention.Cdecl, SetLastError = true)]
    		public static extern CY_RETURN_STATUS CyI2cRead(IntPtr hHandle, ref CY_I2C_DATA_CONFIG dataConfig, ref CY_DATA_BUFFER ReadBuffer, UInt32 timeout);
    
    		[DllImport("cyusbserial.dll", CallingConvention = CallingConvention.Cdecl, SetLastError = true)]
    		public static extern CY_RETURN_STATUS CyI2cWrite(IntPtr hHandle, ref CY_I2C_DATA_CONFIG dataConfig, ref CY_DATA_BUFFER WriteBuffer, UInt32 timeout);
    
    		[DllImport("cyusbserial.dll", CallingConvention = CallingConvention.Cdecl, SetLastError = true)]
    		public static extern CY_RETURN_STATUS CyI2cReset(IntPtr hHandle, byte resetMode);
    
    		[DllImport("cyusbserial.dll", CallingConvention = CallingConvention.Cdecl, SetLastError = true)]
    		public static extern CY_RETURN_STATUS CyGetGpioValue(
                IntPtr hHandle,		//Valid device handle
                byte gpioNumber,	//GPIO number
                out byte value		//Current state of the GPIO
                );
    
    		[DllImport("cyusbserial.dll", CallingConvention = CallingConvention.Cdecl, SetLastError = true)]
    		public static extern CY_RETURN_STATUS CySetGpioValue(
                IntPtr hHandle,     //Valid device handle
                byte gpioNumber,    //GPIO number
                byte value          //Value that needs to be set
                );
    

    Die Verbindung wird einmalig per CyOpen geöffnet, die I2CConfig mit CySetI2cConfig geschrieben und ansonsten überwiegend CyI2cRead und CyI2Write benutzt.

    Kommandos werden wie folgt gesendet

    private bool submitCommands(List<byte> commandStream, byte slaveAddress = (0x36 >> 1))
    		{
    			uint timeout = 500;
    			//byte retry = 5;
    			//bool handshakeSuccess = true;
    
    			CyUSBLib.CY_I2C_DATA_CONFIG dataConfig;
    			CyUSBLib.CY_DATA_BUFFER dataBuffer = new CY_DATA_BUFFER();
    
    			dataConfig.isStopBit = 1;
    			dataConfig.isNakBit = 1;
    			dataConfig.slaveAddress = slaveAddress;
    
    			dataBuffer.length = (uint)commandStream.Count;
    			dataBuffer.buffer = Marshal.AllocHGlobal(commandStream.Count);
    
    			try
    			{
    				Marshal.Copy(commandStream.ToArray(), 0, dataBuffer.buffer, commandStream.Count);
    			}
    			catch (Exception e)
    			{
    				throw new Exception("Marshalling exception");
    			}
    
    			CY_RETURN_STATUS returnStatus = 0;
    
    			for (int _retry = 5; _retry > 0; _retry--)
    			{
    				try
    				{
    					returnStatus = CyUsbLib.CyI2cWrite(DeviceHandle, ref dataConfig, ref dataBuffer, timeout);
    				}
    				catch (AccessViolationException ave)
    				{
    					throw new AccessViolationException("Fehler bei CyI2cWrite: ", ave);
    				}
    
    
    				if (returnStatus == CY_RETURN_STATUS.CY_SUCCESS)
    				{
    					Marshal.FreeHGlobal(dataBuffer.buffer);
    
    					/*
    						--Delay--
    						If the transactions are too fast, the board might be overwhelmed
    						and executes the patterns inappropriately
    					*/
    					HelperFunctions.DoFixedDelay(5);
    					ResetI2CConnection();
    					return true;
    				}
    				else
    				{
    					if (returnStatus == CY_RETURN_STATUS.CY_ERROR_I2C_NAK_ERROR)
    					{
    						if (!CheckHandshake()) { I2CGPIOHandshake(); }
    					}
    
    					HelperFunctions.DoRandomDelay(1, 10);
    				}
    			}
    
    			ByConnectionError("Error in submitCommands: " + commandStream[0].ToString("X"));
    			Marshal.FreeHGlobal(dataBuffer.buffer);
    			ResetI2CConnection();
    
    			return false;
    		}
    

    Edit: Die Testapplikation ist eine WindowsForm, falls das ein Problem (Threading etc..) sein könnte.


  • Administrator

    Bist du schon mal mit dem Debugger durchgegangen? Wenn ein C# Programm abstürzt mit angehängtem Debugger, dann zeigt der das normalerweise an. Ansonsten kannst du ja mal Schritt für Schritt durchgehen, um zumindest herauszufinden, bei welcher Zeile der Absturz passiert.



  • Das ist genau das seltsame daran: Er schmiert meistens einfach ab und beendet sich selbst ohne eine Meldung herauszugeben.
    Allerdings hatte ich eher zufälligerweise einmal eine 'Can't step' Fehlermeldung erhalten und einmal einen HRESULT, welcher auf Heap corrupted zurückzuführen ist. Im Moment vermute ich dass ich irgendwo (unsafe) Speicher anfordere, nicht sonderlich vorsichtig mit diesem umgehe und diesen entweder zum falschen Zeitpunkt freigebe oder eben zum falschen Zeitpunkt darauf zugreife. Dies würde aber dann vermutlich bedeuten, dass eine AccessViolationException auftauchen sollte.

    Vielleicht noch als zusätzliche Info: Das Handle, mit dem ich das Gerät ansteuere, ist ein IntPtr. Könnte es sein, dass die Verbindung (aus welchen Gründen auch immer) zusammenbricht und mir dadurch die Software abstürzt? Das würde aber nicht unbedingt erklären wieso die Software als x86 schlechter läuft im Gegensatz zu AnyCPU + Prefer 32-bit.



  • Es wird wohl eher ein Absturz in der nativen DLL sein und dieser beendet dann den gesamten Prozess.
    Evtl. passen die übergebenen Parameter (Marshalling) bei den DllImport-Methoden nicht 100%ig.


  • Administrator

    @Spacemuck Aber du solltest ja trotzdem herausfinden können, bei welcher Zeile es zum Absturz kommt? Wenn du durch deine submitCommands mit dem Debugger durchgehst, dann sollte es klar sein, bei welchem Step in welcher Zeile der Absturz passiert, egal ob eine Fehlermeldung kommt oder nicht?

    Was Th69 sagt ist sehr gut möglich. Daher wäre es erst recht interessant zu wissen, bei welchem Funktionsaufruf es passiert 🙂



  • @Dravere Das ist auch so ein Ding: Es passiert auch nicht sofort bzw. bei einer bestimmten Zeile, aaaber hier kommt dann wahrscheinlich @Th69 ins Spiel. Parameter werden möglicherweise falsch übergeben, der Garbage Collector (nur eine Vermutung) räumt auf und später kracht es, da noch auf die Variable zugegriffen wird. Konnte aber leider mit einem GC.Force (hab leider gerade nicht die Methode im Kopf) reproduziert werden.

    Sobald ich wieder Zugriff auf den Quellcode habe, poste ich mal ein ausführlicheres Beispiel.



  • Manche dieser Methoden erwarten einen pointer auf eine struktur.
    Wie sieht die definition der entsprechenden struct in C# aus, nicht das es da ein alignment problem gibt



  • @firefly

        public static class CyConst
        {
            internal const int STRING_LENGTH = 256;
        }
    
        public enum CY_RETURN_STATUS
        {
            ...
        };
    
        public struct CY_VID_PID
        {
            public ushort vid;
            public ushort pid;
        };
    
        public struct CY_LIBRARY_VERSION
        {
            byte majorVersion;
            byte minorVersion;
            UInt16 patch;
            byte buildNumber;
        };
    
        public enum CY_DEVICE_CLASS
        {
            ...
        };
    
        public enum CY_DEVICE_TYPE
        {
           ...
        }
    
        public enum CY_DEVICE_SERIAL_BLOCK
        {
           ...
        }
    
        public struct CY_DATA_BUFFER
        {
    		public IntPtr buffer;
            public UInt32 length;
            public UInt32 transfercount;
    	};
    
        public struct CY_I2C_CONFIG
        {
            public uint Frequency;
            public uint SlaveAddress;
            public int isMaster;                               
            public int isClockStretch;                    
        };
    
        public struct CY_I2C_DATA_CONFIG
        {
            public uint slaveAddress;
            public int isStopBit;
            public int isNakBit;
        };
    
        public struct CY_DEVICE_INFO
        {
            public CY_VID_PID VidPid;
            public byte NumInterfaces;
    	[MarshalAs(UnmanagedType.ByValTStr, SizeConst = CyConst.STRING_LENGTH)]
    	public string ManufacturerName;
    
    	[MarshalAs(UnmanagedType.ByValTStr, SizeConst = CyConst.STRING_LENGTH)]
    	public string ProductName;
    
    	[MarshalAs(UnmanagedType.ByValTStr, SizeConst = CyConst.STRING_LENGTH)]
    	public string SerialName;
    
    	[MarshalAs(UnmanagedType.ByValTStr, SizeConst = CyConst.STRING_LENGTH)]
    	public string DeviceFriendlyName;
    
    	public CY_DEVICE_TYPE eDeviceType1;
    	public CY_DEVICE_TYPE eDeviceType2;
            public CY_DEVICE_TYPE eDeviceType3;
            public CY_DEVICE_TYPE eDeviceType4;
            public CY_DEVICE_TYPE eDeviceType5;
    
            public CY_DEVICE_CLASS eDeviceClass1;
            public CY_DEVICE_CLASS eDeviceClass2;
            public CY_DEVICE_CLASS eDeviceClass3;
            public CY_DEVICE_CLASS eDeviceClass4;
            public CY_DEVICE_CLASS eDeviceClass5;
    
    		public CY_DEVICE_SERIAL_BLOCK DeviceBlock;
        };
    
    

    Ich denke am interessantesten ist das struct CY_DATA_BUFFER.



  • Dir fehlen einige attribute lies dir das hier mal durch:
    https://docs.microsoft.com/en-US/dotnet/framework/interop/marshaling-classes-structures-and-unions

    Ansonsten wäre es noch möglich via C++/CLI (.Net C++ Erweiterung von MS) einen wrapper zu schreiben.

    Und beachte auch das Strings in C# UTF-16 sind. Aber die C-API char* strings erwartet, und in char* strings kann man kein UTF-16 packen.



  • @firefly
    Ich habe noch einmal die Datentypen durchgeschaut und festgestellt, dass ich anscheinend irgendwann mal der Meinung war diese ändern zu müssen. Bspw. erwartet die CY_I2C_DATA_CONFIG (UCHAR, BOOL, BOOL) während ich (uint, int, int) übergebe. Das habe ich an vermutlich an mehreren Stellen gemacht. Wäre das evtl. die Ursache des Problems (meine: würde das Sinn machen)?

    Mit der Attributgeschichte bin ich noch nicht ganz fertig, bleibe aber dran, danke dir!



  • Du könntest auch mal den PInvoke Interop Assistant ausprobieren, welcher (u.a.) aus C (bzw. C++) Headerdateien passende C#-Marshalling Strukturen und Methodendeklarationen erzeugt.

    Unter The PInvoke tool you've been looking for all this time... the "PInvoke Interop Assistant" gibt es noch einen kurzen Artikel dazu (sowie einen Link auf eine Setup-Datei davon - dann im Tool "winsiggen.exe" den 3. Tab "SigImp Translate Snippet" wählen).


Log in to reply