Приложение-служба WCF - использование вызова объекта C ++ приводит к зависанию DLL-файла Visual Basic 6.0 - PullRequest
4 голосов
/ 14 ноября 2011

В настоящее время мы переходим в систему для использования WCF и столкнулись с проблемой, которую мы не можем выяснить.Установка состоит из C # DLL-файла, который оборачивает C ++ и Visual Basic 6.0 DLL-файл.DLL-файл C # имеет обертки для обоих из них и создает экземпляры обоих объектов.Объект C ++ инициализируется (получает данные из файлов) и затем передается в объект Visual Basic 6.0, который запускает отчет, используя данные в объекте C ++.Все это происходит как приложение-служба WCF, и по большей части оно прекрасно работает, но когда код Visual Basic 6.0 вызывает метод в объекте C ++, все зависает.

Я протестировал, используя толькопростое приложение, которое вызывает тот же файл DLL C # (за пределами WCF) и работает без нареканий.Итак, что-то происходит с WCF и этим C ++ DLL-файлом, но мы не можем понять, что именно.Я изменил DLL-файл Visual Basic 6.0, чтобы использовать Запустить без присмотра и Хранить в памяти (чтобы иметь возможность использовать его в многопоточном режиме), но, похоже, это не имеет значения.

Кто-нибудь имел опыт с этим, или есть какие-либо мысли о том, почему это будет зависать?Я думаю, что служба WCF каким-то образом блокирует файл DLL, и поэтому, когда файл DLL Visual Basic 6.0 использует его, он не может получить к нему доступ, что приводит к его взаимоблокировке.

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

Информационный объект содержит информацию о том, что должен сгенерировать объект Summary.Он используется только в методе Initialize.

Объект Visual Basic 6.0 загружается через интерфейс:

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

Так что теперь объект PageInformation объекта Visual Basic 6.0 имеет объект сводки.

Далее мы вызываем метод для создания отчета:

_pageInfo.BuildReport();

Это происходит внутри DLL-файла Visual Basic 6.0, и в тот момент, когда код пытается использовать объект сводки, он зависает

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

Если я использую этот же вызов в C #, он просто отлично отбрасывает данные.

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

Опять же, когда я использую эту оболочку вне WCF, она проходит через кодхорошо.Только когда он работает в WCF, он зависает.

Кажется, что это проблема в MTA, и я не уверен, можно ли настроить приложение службы WCF, работающее на IIS, для работы в STA.Это возможно?

РЕШЕНО: Я нашел свой ответ в этом вопросе переполнения стека:

Как сделать службу WCF STA (однопоточной)

Что привело меня к статье XXX .

По сути, мне пришлось создать поток, настроенный на STA, и запуститьAPI (мой файл C # DLL) в нем.Поскольку все это выполняется с помощью TaskFactory (поэтому я могу отменить вызовы и выполнить несколько запросов), это было немного сложно.Теперь у меня все еще есть возможность запускать несколько отчетов одновременно в MTA, но каждый отчет выполняется в STA.Кроме того, я не теряю свою функциональность отмены от WCF.

Вот код (мне нужно еще кое-что очистить):

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();
    }
}

И моя служба вызывает это через:

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

Надеюсь, это поможет всем, кто сталкивается с этой проблемой!

1 Ответ

1 голос
/ 15 ноября 2011

VB6 (COM) должен быть запущен из потока STA.Ваш код WCF, вероятно, вызывает компонент VB6 в одном или нескольких потоках MTA.Бьюсь об заклад, ваше тестовое (не WCF) приложение, которое работало, было настольнымВам необходимо убедиться, что компонент VB6 не вызывается из произвольных потоков .NET.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...