1

I'm calling c# dll unmanaged methods from a different language. I have callback set so my app gets results thru it when c# dll is done.

This causes SEHException on .Invoke:

public static async Task<IList<Models.ValueSet>> Fetch2(Uri uri)
        {
            ServicePointManager.Expect100Continue = true;
            ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;

            var client = new HttpClient();
            var response = await client.GetAsync(uri);
            response.EnsureSuccessStatusCode();
            var content = await response.Content.ReadAsStringAsync();            

            var references = JsonConvert.DeserializeObject<ValueSetReference[]>(content);
            var fetchTasks = references
                .Select( async x =>
                {
                    var perValueSetResponse = await client.GetAsync(new Uri(uri, $"{x.Hash}/"));
                    perValueSetResponse.EnsureSuccessStatusCode();
                    var perValueSetContent = await perValueSetResponse.Content.ReadAsStringAsync();
                    return JsonConvert.DeserializeObject<Models.ValueSet>(perValueSetContent);                    
                });            
            return await Task.WhenAll(fetchTasks);
        }

This does not:

public static async Task<IList<Models.ValueSet>> Fetch(Uri uri)
        {
            ServicePointManager.Expect100Continue = true;
            ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
            
            var client = new HttpClient();
            var response = client.GetAsync(uri).Result;
            response.EnsureSuccessStatusCode();
            var content = await response.Content.ReadAsStringAsync();                        
            var references = JsonConvert.DeserializeObject<ValueSetReference[]>(content);
            var fetchTasks = references
                .Select( async x =>
                {
                    var perValueSetResponse = client.GetAsync(new Uri(uri, $"{x.Hash}/")).Result;
                        perValueSetResponse.EnsureSuccessStatusCode();
                    var perValueSetContent = await perValueSetResponse.Content.ReadAsStringAsync();
                    return JsonConvert.DeserializeObject<Models.ValueSet>(perValueSetContent);                    
                });                        
            return await Task.WhenAll(fetchTasks);
        }

This is the exception that happens on Invoke:

{
    "ClassName": "System.Runtime.InteropServices.SEHException",
    "Message": "External component has thrown an exception.",
    "Data": null,
    "InnerException": null,
    "HelpURL": null,
    "StackTraceString": "   at EUDCC.Verifier.<VerifyAsync>d__4.MoveNext()",
    "RemoteStackTraceString": null,
    "RemoteStackIndex": 0,
    "ExceptionMethod": "8\nMoveNext\neudcc-verifier.lib, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null\nEUDCC.Verifier+<VerifyAsync>d__4\nVoid MoveNext()",
    "HResult": -2147467259,
    "Source": "eudcc-verifier.lib",
    "WatsonBuckets": null
}

and this is whole main code for the c# dll:

using EUDCC.Configuration;
using EUDCC.Models;
using EUDCC.Rules;
using EUDCC.Services;
using EUDCC.TrustList;
using Newtonsoft.Json;
using System;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using net.r_eg.DllExport;
using System.Diagnostics;
using System.Threading;

namespace EUDCC
{
    public static class Verifier
                 
    {
        static string result { get; set; }

        [DllExport(CallingConvention = CallingConvention.StdCall, ExportName = "GetResult")]
        [return: MarshalAs(UnmanagedType.BStr)]

        public static string GetResult()
        {
            return result;
        }

        [UnmanagedFunctionPointer(CallingConvention.StdCall)]
        public delegate void QRVerifyResult([MarshalAs(UnmanagedType.BStr)] string result);
        private static QRVerifyResult dQRVerifyResult;

        [DllExport(CallingConvention = CallingConvention.StdCall, ExportName = "SetQRVerifyResultCallback")]
        public static void SetQRVerifyResultCallback(int funcAddr)
        {
            IntPtr cbPtr = new IntPtr(funcAddr);            
            dQRVerifyResult = (QRVerifyResult)Marshal.GetDelegateForFunctionPointer(cbPtr, typeof(QRVerifyResult));            
        }
        public static async void VerifyAsync(string qrCode, string TrustListUri, string ValueSetUri, string RuleSetUri)
        {
            var sc = SynchronizationContext.Current;
            Task<VerificationReport> t = Verify(qrCode, TrustListUri, ValueSetUri, RuleSetUri);
            var verReport = await t;
            result = JsonConvert.SerializeObject(verReport);
            Trace.WriteLine("Result that INVOKE will send: " + result);
            SynchronizationContext.SetSynchronizationContext(sc);
             try
            {                
                dQRVerifyResult?.Invoke(result);
                     
            }
            catch (Exception ex)
            {
                Trace.WriteLine("Invoke Failed: " + ex.Message+" "+ex.GetBaseException());                
            }
            Trace.WriteLine("VerifySync: Invoking done");
        }

        [DllExport(CallingConvention = CallingConvention.StdCall, ExportName = "QRVerify")]
        public static void DoVerify([MarshalAs(UnmanagedType.BStr)] string qrCode,
                                  [MarshalAs(UnmanagedType.BStr)] string TrustListUri,
                                  [MarshalAs(UnmanagedType.BStr)] string ValueSetUri,
                                  [MarshalAs(UnmanagedType.BStr)] string RuleSetUri)
        {
            Trace.WriteLine("****************** START *******************");
            Trace.WriteLine("DoVerify called from CW, before VerifyAsync called");
            try
            {
                VerifyAsync(qrCode, TrustListUri, ValueSetUri, RuleSetUri);
            }
            catch (Exception ex)
            {
                Trace.WriteLine("VerifyAsyncFailed: " + ex.Message);
            }
            Trace.WriteLine("VerifyAsync Done, DoVerify from CW Done");
        }

         public static async Task<VerificationReport> Verify([MarshalAs(UnmanagedType.BStr)] string qrCode,
                                                            [MarshalAs(UnmanagedType.BStr)] string TrustListUri,
                                                            [MarshalAs(UnmanagedType.BStr)] string ValueSetUri,
                                                            [MarshalAs(UnmanagedType.BStr)] string RuleSetUri)
    {
                 Trace.WriteLine("Verify procedure called");
            _ = new Settings(
                trustListUri: new Uri(TrustListUri),
                valueSetUri: new Uri(ValueSetUri),
               rulesetUri: new Uri(RuleSetUri));
            DccCertificate dccCertificate;

            bool signatureVerified = false;
            var errorMessage = string.Empty;
            

            try
            {
                dccCertificate = DccCertificateDecoder.Decode(qrCode);
            }
            catch (Exception ex)
            {
                Trace.WriteLine("exception VerificationReport called");
                errorMessage = $"Unable to decode the QR Code to a valid EU DCC certificate. Error encountered is: {ex.Message}";
                return new VerificationReport(signatureVerified, null, null, errorMessage, null);
            }

            try
            {
                var signatureCertificate = await TrustListRepository.GetByKid(dccCertificate.SignatureData.Kid);
                signatureVerified = SignatureVerifier.Verify(signatureCertificate, dccCertificate);
                Trace.WriteLine("back from .Verify");
            }
            catch (Exception ex)
            {
                Trace.WriteLine("exception signatureVerified called");
                errorMessage = $"DCC certificate was decoded successfully, but verifying certificate signature failed. Error encountered is: {ex.Message}, " + ex.InnerException.Message;
               //return new VerificationReport(signatureVerified, null, null, errorMessage, null);
            }

            if (dccCertificate.MetaData.ExpiringDate < DateTimeOffset.UtcNow)
            {
                Trace.WriteLine("ErrorMessage called");
                errorMessage = "Certificate has expired.";
                Trace.WriteLine("Return Verify ExpiringDate");
                return new VerificationReport(signatureVerified, dccCertificate.HealthCertificate, dccCertificate.MetaData, errorMessage, null);
            } 
            else
            {
                Trace.WriteLine("ExpiringDate checked");
            }

            var healthCertificateJson = JsonConvert.SerializeObject(dccCertificate.HealthCertificate);            
            var rawHealthCertificate = JsonConvert.DeserializeObject<HealthCertificateRaw>(healthCertificateJson);
            var invalidRules = await RuleEngine.Run(rawHealthCertificate, Settings.ValueSetUri, Settings.RulesetUri, Settings.AppName);            
            if (invalidRules.Any())
            {
                errorMessage = "Some validation rules are invalid.";
                Trace.WriteLine("We got invalid rules");

            }
            Trace.WriteLine("Just before Verify return");
            return new VerificationReport(signatureVerified, dccCertificate.HealthCertificate, dccCertificate.MetaData, errorMessage, invalidRules);
        }
    }
}

Prototypes in Clarion for calling c# dll:

module('eudcc-verifier.dll')
    QRVerify(bstring,bstring,bstring,bstring), name('QRVerify'), pascal,raw,dll(true)
    SetQRVerifyResultcallback(long),pascal,raw,dll(true)
    GetResult(),bstring, pascal, raw, dll(true)
end
  QRVerifyResult(bstring result), PASCAL                

The call:

qrcodebstr=clip(qrcode)
trustlistbstr='https://...'
ValueSetBstr='https://...'
RuleSetbstr='...'          
SetQRVerifyResultCallback(ADDRESS(QRVerifyResult))
QRVerify(qrcodebstr,trustlistbstr,ValueSetBstr,RuleSetbstr)    

Callback procedure in Clarion (called back by Invoke in c#):

QRVerifyResult      PROCEDURE(bstring result)
    CODE        
    message(result)
    return

I'm not proficient in C# but why would this be so? Is there any workaround that awaiting client would still work? I prefer my app remaining responsive while the files are being downloaded by c# dll. Without "awaiting" client my app locksup for the time of download...

Thank you.

  • Your error information is a bit lacking. Check this answer to know how what info add into your question [How should you diagnose the error SEHException - External component has thrown an exception](https://stackoverflow.com/questions/1313853/how-should-you-diagnose-the-error-sehexception-external-component-has-thrown-a) – Cleptus Jan 09 '22 at 17:31
  • *"I'm calling c# dll unmanaged methods from a different language"* C# doesn't create unmanaged methods so not sure what you mean. If you mean you are calling unmanaged methods in *another* language, where are the definitions for them? – Charlieface Jan 09 '22 at 19:28
  • I've read the suggested article and updated initial question. – Bostjan Laba Jan 10 '22 at 07:19

1 Answers1

0

I have found a problem and solution. Using await with HttpClient in the above C# code creates a new thread.

In that case when Invoke is called, it calls the callback procedure from a different thread that the one the initial call to dll has been made.

In case of Clarion there is a method called AttachThreadToClarion() which take care of that. You can put this call into clarion callback procedure or prototype it in C# and call it from there.

Problem solved, everything works in async as it should have.

Peter Csala
  • 17,736
  • 16
  • 35
  • 75