4 votes

Application de service WCF - L'utilisation d'un appel d'objet C++ provoque le blocage du fichier DLL de Visual Basic 6.0

Nous sommes actuellement en train de déplacer un système pour utiliser WCF et nous avons rencontré un problème que nous n'arrivons pas à résoudre. Il s'agit d'un fichier DLL C# qui englobe un fichier DLL C++ et un fichier DLL Visual Basic 6.0. Le fichier DLL C# possède des enveloppes pour ces deux objets et les instancie. L'objet C++ est initialisé (il saisit les données des fichiers) et est ensuite transmis à un objet Visual Basic 6.0, qui exécute un rapport en utilisant les données de l'objet C++. Tout cela se passe en tant qu'application de service WCF et, pour l'essentiel, fonctionne parfaitement, mais lorsque le code Visual Basic 6.0 appelle une méthode dans l'objet C++, tout se bloque.

J'ai fait un test en utilisant une simple application qui appelle le même fichier DLL C# (en dehors de WCF), et cela fonctionne parfaitement. Donc, il y a quelque chose qui se passe avec WCF et ce fichier DLL C++, mais nous ne pouvons pas comprendre quoi. J'ai changé le fichier DLL de Visual Basic 6.0 pour employer Exécution sans surveillance y Stocker en mémoire (pour pouvoir l'utiliser en filetage), mais cela ne semble pas avoir d'importance.

Quelqu'un a-t-il fait l'expérience de ce phénomène, ou a-t-il une idée de la raison pour laquelle il serait suspendu ? Je pense que le service WCF verrouille d'une manière ou d'une autre le fichier DLL, et c'est pourquoi lorsque le fichier DLL de Visual Basic 6.0 l'utilise, il ne peut pas y accéder, ce qui provoque un blocage.

C++ Wrapper

    public interface ISummaryWrapper
    {
        void LoadInfo(Application info);
        SummaryApp GetSummary();
    }

    public class SummaryWrapper : ISummaryWrapper
    {
        private SummaryApp _summary;

        public SummaryWrapper()
        {
            _summary = new SummaryApp();
        }

        public SummaryWrapper(Application info)
        {
            _summary = new SummaryApp();
            LoadInfo(info);
        }

        public void LoadInfo(Application info)
        {
            _summary.Initialize(info);
        }

        public SummaryApp GetSummary()
        {
            return _summary;
        }
    }

L'objet info contient des informations sur ce que l'objet Summary doit générer. Il n'est utilisé que dans la méthode Initialize.

L'objet Visual Basic 6.0 est chargé par le biais d'une interface :

public void LoadPageObject(Application info)
{
    _pageInfo = new PageInformation();
    _pageInfo.oInfo = info;
    _pageInfo.oSummary = _summary;
}

Donc maintenant l'objet PageInformation de Visual Basic 6.0 a l'objet résumé.

Ensuite, nous appelons la méthode pour générer le rapport :

_pageInfo.BuildReport();

Cela va à l'intérieur du fichier DLL de Visual Basic 6.0, et au moment où le code essaie d'utiliser l'objet résumé, il se bloque.

// Omitted actual params for brevity, though all the params exist
double value = oSummary.GetData(string parm1, string parm2)

Si j'utilise ce même appel en C#, les données sont récupérées sans problème.

double value = _summary.GetData(string parm1, string parm2);

Encore une fois, quand j'utilise ce wrapper en dehors de WCF, il passe par le code bien. C'est seulement quand il est exécuté dans WCF qu'il se bloque.

Il semble qu'il s'agisse d'un problème d'exécution en MTA, et je ne suis pas sûr qu'une application de service WCF exécutée sur IIS puisse être configurée pour être exécutée en STA. Est-ce possible ?


SOLVÉ : J'ai trouvé ma réponse dans cette question de Stack Overflow :

Comment rendre un service WCF STA (single-threaded)

Ce qui m'a conduit à l'article XXX .

En gros, j'ai dû créer un thread qui est défini comme STA, et y exécuter l'API (mon fichier DLL C#). Étant donné que j'exécute tout cela avec TaskFactory (pour pouvoir annuler les appels et exécuter plusieurs requêtes), c'était un peu délicat. Maintenant, j'ai toujours la possibilité d'exécuter plusieurs rapports en même temps dans MTA, mais chaque rapport est exécuté dans STA. De plus, je ne perds pas non plus ma fonctionnalité d'annulation de WCF.

Voici le code (j'ai encore un peu de nettoyage à faire) :

public class Builder
{
    public string OperationId { get; set; }
    public IServiceCallback CallBack { get; set; }
    public Dictionary<string, CancellationTokenSource> Queue { get; set; }

    public void BuildReport()
    {
        OperationContext context = OperationContext.Current;
        Thread thread = new Thread(
            new ThreadStart(
                delegate
                    {
                        using (OperationContextScope scope = new OperationContextScope(context))
                        {
                            try
                            {
                                CancellationToken token = Queue[OperationId].Token;

                                CallBack.SendStatus(OperationId, Status.Processing);

                                IAPI api = new API(token);

                                api.MessagingEvents += MessageEvent;

                                // Build Report
                                CallBack.SendStatus(OperationId, Status.BuildingReport);
                                if (!api.BuildReport())
                                    return;

                                CallBack.SendStatus(OperationId, Status.Completed);
                            }
                            catch (OperationCanceledException oc)
                            {
                                // Sending this on the method that receives the cancel request, no need to send again
                            }
                            catch (Exception ex)
                            {
                                // May not be able to use callback if it's a Timeout Exception, log error first
                                // TODO: Log Error
                                CallBack.SendMessage(OperationId, MessageType.Error, ex.Message);
                                CallBack.SendStatus(OperationId, Status.Error);
                            }
                            finally
                            {
                                Queue.Remove(OperationId);
                            }
                        }
                    }));
        thread.SetApartmentState(ApartmentState.STA);
        thread.Start();
        thread.Join();
    }
}

Et mes appels de service ce via :

// I initialize taskfactory when the service is created, omitting other code for brevity

public void BuildReport(ReportRequest request)
{
    CallBack.SendReportStatus(request.OperationId, Status.Received);
    CancellationTokenSource cancelSource = new CancellationTokenSource();
    Queue.Add(request.OperationId, cancelSource);

    Builder builder = new Builder
    {
        OperationId = request.OperationId,
        CallBack = CallBack,
        Queue = _queue
    };

    _taskFactory.StartNew(builder.BuildReport, cancelSource.Token);
}

J'espère que cela aidera tous ceux qui rencontrent ce problème !

1voto

tcarvin Points 6537

VB6 (COM) doit être exécuté à partir d'un thread STA. Votre code WCF appelle probablement le composant VB6 sur un ou plusieurs threads MTA. Je parie que votre application de test (non WCF), celle qui a fonctionné, était une application de bureau. Vous devrez vous assurer que le composant VB6 n'est pas appelé depuis des threads .NET arbitraires.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X