Procédure pas à pas : utilisation du runtime d'accès concurrentiel routage dans une application COM
Ce document montre comment utiliser le runtime d’accès concurrentiel dans une application qui utilise le modèle d’objet de composant (COM).
Prérequis
Lisez les documents suivants avant de commencer cette procédure pas à pas :
Pour plus d’informations sur COM, consultez Component Object Model (COM).
Gestion de la durée de vie de la bibliothèque COM
Bien que l’utilisation de COM avec le runtime d’accès concurrentiel respecte les mêmes principes que tout autre mécanisme d’accès concurrentiel, les instructions suivantes peuvent vous aider à utiliser ces bibliothèques ensemble.
Un thread doit appeler CoInitializeEx avant d’utiliser la bibliothèque COM.
Un thread peut appeler
CoInitializeEx
plusieurs fois tant qu’il fournit les mêmes arguments à chaque appel.Pour chaque appel à
CoInitializeEx
, un thread doit également appeler CoUninitialize. En d’autres termes, les appels àCoInitializeEx
etCoUninitialize
doivent être équilibrés.Pour passer d’un appartement de thread à un autre, un thread doit libérer complètement la bibliothèque COM avant d’appeler
CoInitializeEx
avec la nouvelle spécification de threading.
D’autres principes COM s’appliquent lorsque vous utilisez COM avec le runtime d’accès concurrentiel. Par exemple, une application qui crée un objet dans un appartement à thread unique (STA) et marshale cet objet vers un autre appartement doit également fournir une boucle de message pour traiter les messages entrants. Rappelez-vous également que le marshaling d’objets entre appartements peut diminuer les performances.
Utilisation de COM avec la bibliothèque de modèles parallèles
Lorsque vous utilisez COM avec un composant dans la bibliothèque de modèles parallèles (PPL), par exemple, un groupe de tâches ou un algorithme parallèle, appelez CoInitializeEx
avant d’utiliser la bibliothèque COM pendant chaque tâche ou itération, puis appelez CoUninitialize
avant la fin de chaque tâche ou itération. L’exemple suivant montre comment gérer la durée de vie de la bibliothèque COM avec un objet concurrency ::structured_task_group .
structured_task_group tasks;
// Create and run a task.
auto task = make_task([] {
// Initialize the COM library on the current thread.
CoInitializeEx(NULL, COINIT_MULTITHREADED);
// TODO: Perform task here.
// Free the COM library.
CoUninitialize();
});
tasks.run(task);
// TODO: Run additional tasks here.
// Wait for the tasks to finish.
tasks.wait();
Vous devez vous assurer que la bibliothèque COM est correctement libérée lorsqu’une tâche ou un algorithme parallèle est annulé ou lorsque le corps de la tâche lève une exception. Pour garantir que la tâche appelle CoUninitialize
avant sa sortie, utilisez un try-finally
bloc ou le modèle RAII (Resource Acquisition Is Initialization ). L’exemple suivant utilise un try-finally
bloc pour libérer la bibliothèque COM une fois la tâche terminée ou annulée, ou lorsqu’une exception est levée.
structured_task_group tasks;
// Create and run a task.
auto task = make_task([] {
bool coinit = false;
__try {
// Initialize the COM library on the current thread.
CoInitializeEx(NULL, COINIT_MULTITHREADED);
coinit = true;
// TODO: Perform task here.
}
__finally {
// Free the COM library.
if (coinit)
CoUninitialize();
}
});
tasks.run(task);
// TODO: Run additional tasks here.
// Wait for the tasks to finish.
tasks.wait();
L’exemple suivant utilise le modèle RAII pour définir la CCoInitializer
classe, qui gère la durée de vie de la bibliothèque COM dans une étendue donnée.
// An exception-safe wrapper class that manages the lifetime
// of the COM library in a given scope.
class CCoInitializer
{
public:
explicit CCoInitializer(DWORD dwCoInit = COINIT_APARTMENTTHREADED)
: _coinitialized(false)
{
// Initialize the COM library on the current thread.
HRESULT hr = CoInitializeEx(NULL, dwCoInit);
if (SUCCEEDED(hr))
_coinitialized = true;
}
~CCoInitializer()
{
// Free the COM library.
if (_coinitialized)
CoUninitialize();
}
private:
// Flags whether COM was properly initialized.
bool _coinitialized;
// Hide copy constructor and assignment operator.
CCoInitializer(const CCoInitializer&);
CCoInitializer& operator=(const CCoInitializer&);
};
Vous pouvez utiliser la CCoInitializer
classe pour libérer automatiquement la bibliothèque COM lorsque la tâche se termine, comme suit.
structured_task_group tasks;
// Create and run a task.
auto task = make_task([] {
// Enable COM for the lifetime of the task.
CCoInitializer coinit(COINIT_MULTITHREADED);
// TODO: Perform task here.
// The CCoInitializer object frees the COM library
// when the task exits.
});
tasks.run(task);
// TODO: Run additional tasks here.
// Wait for the tasks to finish.
tasks.wait();
Pour plus d’informations sur l’annulation dans le runtime d’accès concurrentiel, consultez Annulation dans la bibliothèque PPL.
Utilisation de COM avec des agents asynchrones
Lorsque vous utilisez COM avec des agents asynchrones, appelez CoInitializeEx
avant d’utiliser la bibliothèque COM dans la méthode concurrency ::agent ::run pour votre agent. Appelez CoUninitialize
ensuite avant que la run
méthode ne retourne. N’utilisez pas de routines de gestion COM dans le constructeur ou le destructeur de votre agent, et ne remplacez pas les méthodes concurrency ::agent ::start ou concurrency ::agent ::d one , car ces méthodes sont appelées à partir d’un thread différent de la run
méthode.
L’exemple suivant montre une classe d’agent de base, nommée CCoAgent
, qui gère la bibliothèque COM dans la run
méthode.
class CCoAgent : public agent
{
protected:
void run()
{
// Initialize the COM library on the current thread.
CoInitializeEx(NULL, COINIT_MULTITHREADED);
// TODO: Perform work here.
// Free the COM library.
CoUninitialize();
// Set the agent to the finished state.
done();
}
};
Un exemple complet est fourni plus loin dans cette procédure pas à pas.
Utilisation de COM avec des tâches légères
Le planificateur de tâches de document décrit le rôle des tâches légères dans le runtime d’accès concurrentiel. Vous pouvez utiliser COM avec une tâche légère comme vous le feriez avec n’importe quelle routine de thread que vous passez à la CreateThread
fonction dans l’API Windows. Cela est illustré par l'exemple suivant.
// A basic lightweight task that you schedule directly from a
// Scheduler or ScheduleGroup object.
void ThreadProc(void* data)
{
// Initialize the COM library on the current thread.
CoInitializeEx(NULL, COINIT_MULTITHREADED);
// TODO: Perform work here.
// Free the COM library.
CoUninitialize();
}
Exemple d’application COMPATIBLE COM
Cette section présente une application com complète qui utilise l’interface IScriptControl
pour exécuter un script qui calcule le nième numéro Fibonacci. Cet exemple appelle d’abord le script à partir du thread principal, puis utilise le PPL et les agents pour appeler le script simultanément.
Considérez la fonction d’assistance suivante, RunScriptProcedure
qui appelle une procédure dans un IScriptControl
objet.
// Calls a procedure in an IScriptControl object.
template<size_t ArgCount>
_variant_t RunScriptProcedure(IScriptControlPtr pScriptControl,
_bstr_t& procedureName, array<_variant_t, ArgCount>& arguments)
{
// Create a 1-dimensional, 0-based safe array.
SAFEARRAYBOUND rgsabound[] = { ArgCount, 0 };
CComSafeArray<VARIANT> sa(rgsabound, 1U);
// Copy the arguments to the safe array.
LONG lIndex = 0;
for_each(begin(arguments), end(arguments), [&](_variant_t& arg) {
HRESULT hr = sa.SetAt(lIndex, arg);
if (FAILED(hr))
throw hr;
++lIndex;
});
// Call the procedure in the script.
return pScriptControl->Run(procedureName, &sa.m_psa);
}
La wmain
fonction crée un objet, ajoute du IScriptControl
code de script à celui-ci qui calcule le nième numéro Fibonacci, puis appelle la RunScriptProcedure
fonction pour exécuter ce script.
int wmain()
{
HRESULT hr;
// Enable COM on this thread for the lifetime of the program.
CCoInitializer coinit(COINIT_MULTITHREADED);
// Create the script control.
IScriptControlPtr pScriptControl(__uuidof(ScriptControl));
// Set script control properties.
pScriptControl->Language = "JScript";
pScriptControl->AllowUI = TRUE;
// Add script code that computes the nth Fibonacci number.
hr = pScriptControl->AddCode(
"function fib(n) { if (n<2) return n; else return fib(n-1) + fib(n-2); }" );
if (FAILED(hr))
return hr;
// Test the script control by computing the 15th Fibonacci number.
wcout << endl << L"Main Thread:" << endl;
LONG lValue = 15;
array<_variant_t, 1> args = { _variant_t(lValue) };
_variant_t result = RunScriptProcedure(
pScriptControl,
_bstr_t("fib"),
args);
// Print the result.
wcout << L"fib(" << lValue << L") = " << result.lVal << endl;
return S_OK;
}
Appel du script à partir de PPL
La fonction suivante utilise ParallelFibonacci
l’algorithme concurrency ::p arallel_for pour appeler le script en parallèle. Cette fonction utilise la CCoInitializer
classe pour gérer la durée de vie de la bibliothèque COM pendant chaque itération de la tâche.
// Computes multiple Fibonacci numbers in parallel by using
// the parallel_for algorithm.
HRESULT ParallelFibonacci(IScriptControlPtr pScriptControl)
{
try {
parallel_for(10L, 20L, [&pScriptControl](LONG lIndex)
{
// Enable COM for the lifetime of the task.
CCoInitializer coinit(COINIT_MULTITHREADED);
// Call the helper function to run the script procedure.
array<_variant_t, 1> args = { _variant_t(lIndex) };
_variant_t result = RunScriptProcedure(
pScriptControl,
_bstr_t("fib"),
args);
// Print the result.
wstringstream ss;
ss << L"fib(" << lIndex << L") = " << result.lVal << endl;
wcout << ss.str();
});
}
catch (HRESULT hr) {
return hr;
}
return S_OK;
}
Pour utiliser la fonction avec l’exemple ParallelFibonacci
, ajoutez le code suivant avant que la wmain
fonction ne retourne.
// Use the parallel_for algorithm to compute multiple
// Fibonacci numbers in parallel.
wcout << endl << L"Parallel Fibonacci:" << endl;
if (FAILED(hr = ParallelFibonacci(pScriptControl)))
return hr;
Appel du script à partir d’un agent
L’exemple suivant montre la FibonacciScriptAgent
classe, qui appelle une procédure de script pour calculer le nième numéro Fibonacci. La classe utilise la FibonacciScriptAgent
transmission de messages pour recevoir, du programme principal, des valeurs d’entrée à la fonction de script. La run
méthode gère la durée de vie de la bibliothèque COM tout au long de la tâche.
// A basic agent that calls a script procedure to compute the
// nth Fibonacci number.
class FibonacciScriptAgent : public agent
{
public:
FibonacciScriptAgent(IScriptControlPtr pScriptControl, ISource<LONG>& source)
: _pScriptControl(pScriptControl)
, _source(source) { }
public:
// Retrieves the result code.
HRESULT GetHRESULT()
{
return receive(_result);
}
protected:
void run()
{
// Initialize the COM library on the current thread.
CoInitializeEx(NULL, COINIT_MULTITHREADED);
// Read values from the message buffer until
// we receive the sentinel value.
LONG lValue;
while ((lValue = receive(_source)) != Sentinel)
{
try {
// Call the helper function to run the script procedure.
array<_variant_t, 1> args = { _variant_t(lValue) };
_variant_t result = RunScriptProcedure(
_pScriptControl,
_bstr_t("fib"),
args);
// Print the result.
wstringstream ss;
ss << L"fib(" << lValue << L") = " << result.lVal << endl;
wcout << ss.str();
}
catch (HRESULT hr) {
send(_result, hr);
break;
}
}
// Set the result code (does nothing if a value is already set).
send(_result, S_OK);
// Free the COM library.
CoUninitialize();
// Set the agent to the finished state.
done();
}
public:
// Signals the agent to terminate.
static const LONG Sentinel = 0L;
private:
// The IScriptControl object that contains the script procedure.
IScriptControlPtr _pScriptControl;
// Message buffer from which to read arguments to the
// script procedure.
ISource<LONG>& _source;
// The result code for the overall operation.
single_assignment<HRESULT> _result;
};
La fonction suivante, AgentFibonacci
crée plusieurs FibonacciScriptAgent
objets et utilise le passage de messages pour envoyer plusieurs valeurs d’entrée à ces objets.
// Computes multiple Fibonacci numbers in parallel by using
// asynchronous agents.
HRESULT AgentFibonacci(IScriptControlPtr pScriptControl)
{
// Message buffer to hold arguments to the script procedure.
unbounded_buffer<LONG> values;
// Create several agents.
array<agent*, 3> agents =
{
new FibonacciScriptAgent(pScriptControl, values),
new FibonacciScriptAgent(pScriptControl, values),
new FibonacciScriptAgent(pScriptControl, values),
};
// Start each agent.
for_each(begin(agents), end(agents), [](agent* a) {
a->start();
});
// Send a few values to the agents.
send(values, 30L);
send(values, 22L);
send(values, 10L);
send(values, 12L);
// Send a sentinel value to each agent.
for_each(begin(agents), end(agents), [&values](agent*) {
send(values, FibonacciScriptAgent::Sentinel);
});
// Wait for all agents to finish.
agent::wait_for_all(3, &agents[0]);
// Determine the result code.
HRESULT hr = S_OK;
for_each(begin(agents), end(agents), [&hr](agent* a) {
HRESULT hrTemp;
if (FAILED(hrTemp =
reinterpret_cast<FibonacciScriptAgent*>(a)->GetHRESULT()))
{
hr = hrTemp;
}
});
// Clean up.
for_each(begin(agents), end(agents), [](agent* a) {
delete a;
});
return hr;
}
Pour utiliser la fonction avec l’exemple AgentFibonacci
, ajoutez le code suivant avant que la wmain
fonction ne retourne.
// Use asynchronous agents to compute multiple
// Fibonacci numbers in parallel.
wcout << endl << L"Agent Fibonacci:" << endl;
if (FAILED(hr = AgentFibonacci(pScriptControl)))
return hr;
Exemple complet
Le code suivant montre l’exemple complet, qui utilise des algorithmes parallèles et des agents asynchrones pour appeler une procédure de script qui calcule des nombres Fibonacci.
// parallel-scripts.cpp
// compile with: /EHsc
#include <agents.h>
#include <ppl.h>
#include <array>
#include <sstream>
#include <iostream>
#include <atlsafe.h>
// TODO: Change this path if necessary.
#import "C:\windows\system32\msscript.ocx"
using namespace concurrency;
using namespace MSScriptControl;
using namespace std;
// An exception-safe wrapper class that manages the lifetime
// of the COM library in a given scope.
class CCoInitializer
{
public:
explicit CCoInitializer(DWORD dwCoInit = COINIT_APARTMENTTHREADED)
: _coinitialized(false)
{
// Initialize the COM library on the current thread.
HRESULT hr = CoInitializeEx(NULL, dwCoInit);
if (FAILED(hr))
throw hr;
_coinitialized = true;
}
~CCoInitializer()
{
// Free the COM library.
if (_coinitialized)
CoUninitialize();
}
private:
// Flags whether COM was properly initialized.
bool _coinitialized;
// Hide copy constructor and assignment operator.
CCoInitializer(const CCoInitializer&);
CCoInitializer& operator=(const CCoInitializer&);
};
// Calls a procedure in an IScriptControl object.
template<size_t ArgCount>
_variant_t RunScriptProcedure(IScriptControlPtr pScriptControl,
_bstr_t& procedureName, array<_variant_t, ArgCount>& arguments)
{
// Create a 1-dimensional, 0-based safe array.
SAFEARRAYBOUND rgsabound[] = { ArgCount, 0 };
CComSafeArray<VARIANT> sa(rgsabound, 1U);
// Copy the arguments to the safe array.
LONG lIndex = 0;
for_each(begin(arguments), end(arguments), [&](_variant_t& arg) {
HRESULT hr = sa.SetAt(lIndex, arg);
if (FAILED(hr))
throw hr;
++lIndex;
});
// Call the procedure in the script.
return pScriptControl->Run(procedureName, &sa.m_psa);
}
// Computes multiple Fibonacci numbers in parallel by using
// the parallel_for algorithm.
HRESULT ParallelFibonacci(IScriptControlPtr pScriptControl)
{
try {
parallel_for(10L, 20L, [&pScriptControl](LONG lIndex)
{
// Enable COM for the lifetime of the task.
CCoInitializer coinit(COINIT_MULTITHREADED);
// Call the helper function to run the script procedure.
array<_variant_t, 1> args = { _variant_t(lIndex) };
_variant_t result = RunScriptProcedure(
pScriptControl,
_bstr_t("fib"),
args);
// Print the result.
wstringstream ss;
ss << L"fib(" << lIndex << L") = " << result.lVal << endl;
wcout << ss.str();
});
}
catch (HRESULT hr) {
return hr;
}
return S_OK;
}
// A basic agent that calls a script procedure to compute the
// nth Fibonacci number.
class FibonacciScriptAgent : public agent
{
public:
FibonacciScriptAgent(IScriptControlPtr pScriptControl, ISource<LONG>& source)
: _pScriptControl(pScriptControl)
, _source(source) { }
public:
// Retrieves the result code.
HRESULT GetHRESULT()
{
return receive(_result);
}
protected:
void run()
{
// Initialize the COM library on the current thread.
CoInitializeEx(NULL, COINIT_MULTITHREADED);
// Read values from the message buffer until
// we receive the sentinel value.
LONG lValue;
while ((lValue = receive(_source)) != Sentinel)
{
try {
// Call the helper function to run the script procedure.
array<_variant_t, 1> args = { _variant_t(lValue) };
_variant_t result = RunScriptProcedure(
_pScriptControl,
_bstr_t("fib"),
args);
// Print the result.
wstringstream ss;
ss << L"fib(" << lValue << L") = " << result.lVal << endl;
wcout << ss.str();
}
catch (HRESULT hr) {
send(_result, hr);
break;
}
}
// Set the result code (does nothing if a value is already set).
send(_result, S_OK);
// Free the COM library.
CoUninitialize();
// Set the agent to the finished state.
done();
}
public:
// Signals the agent to terminate.
static const LONG Sentinel = 0L;
private:
// The IScriptControl object that contains the script procedure.
IScriptControlPtr _pScriptControl;
// Message buffer from which to read arguments to the
// script procedure.
ISource<LONG>& _source;
// The result code for the overall operation.
single_assignment<HRESULT> _result;
};
// Computes multiple Fibonacci numbers in parallel by using
// asynchronous agents.
HRESULT AgentFibonacci(IScriptControlPtr pScriptControl)
{
// Message buffer to hold arguments to the script procedure.
unbounded_buffer<LONG> values;
// Create several agents.
array<agent*, 3> agents =
{
new FibonacciScriptAgent(pScriptControl, values),
new FibonacciScriptAgent(pScriptControl, values),
new FibonacciScriptAgent(pScriptControl, values),
};
// Start each agent.
for_each(begin(agents), end(agents), [](agent* a) {
a->start();
});
// Send a few values to the agents.
send(values, 30L);
send(values, 22L);
send(values, 10L);
send(values, 12L);
// Send a sentinel value to each agent.
for_each(begin(agents), end(agents), [&values](agent*) {
send(values, FibonacciScriptAgent::Sentinel);
});
// Wait for all agents to finish.
agent::wait_for_all(3, &agents[0]);
// Determine the result code.
HRESULT hr = S_OK;
for_each(begin(agents), end(agents), [&hr](agent* a) {
HRESULT hrTemp;
if (FAILED(hrTemp =
reinterpret_cast<FibonacciScriptAgent*>(a)->GetHRESULT()))
{
hr = hrTemp;
}
});
// Clean up.
for_each(begin(agents), end(agents), [](agent* a) {
delete a;
});
return hr;
}
int wmain()
{
HRESULT hr;
// Enable COM on this thread for the lifetime of the program.
CCoInitializer coinit(COINIT_MULTITHREADED);
// Create the script control.
IScriptControlPtr pScriptControl(__uuidof(ScriptControl));
// Set script control properties.
pScriptControl->Language = "JScript";
pScriptControl->AllowUI = TRUE;
// Add script code that computes the nth Fibonacci number.
hr = pScriptControl->AddCode(
"function fib(n) { if (n<2) return n; else return fib(n-1) + fib(n-2); }" );
if (FAILED(hr))
return hr;
// Test the script control by computing the 15th Fibonacci number.
wcout << L"Main Thread:" << endl;
long n = 15;
array<_variant_t, 1> args = { _variant_t(n) };
_variant_t result = RunScriptProcedure(
pScriptControl,
_bstr_t("fib"),
args);
// Print the result.
wcout << L"fib(" << n << L") = " << result.lVal << endl;
// Use the parallel_for algorithm to compute multiple
// Fibonacci numbers in parallel.
wcout << endl << L"Parallel Fibonacci:" << endl;
if (FAILED(hr = ParallelFibonacci(pScriptControl)))
return hr;
// Use asynchronous agents to compute multiple
// Fibonacci numbers in parallel.
wcout << endl << L"Agent Fibonacci:" << endl;
if (FAILED(hr = AgentFibonacci(pScriptControl)))
return hr;
return S_OK;
}
L’exemple produit l’exemple de sortie suivant.
Main Thread:
fib(15) = 610
Parallel Fibonacci:
fib(15) = 610
fib(10) = 55
fib(16) = 987
fib(18) = 2584
fib(11) = 89
fib(17) = 1597
fib(19) = 4181
fib(12) = 144
fib(13) = 233
fib(14) = 377
Agent Fibonacci:
fib(30) = 832040
fib(22) = 17711
fib(10) = 55
fib(12) = 144
Compilation du code
Copiez l’exemple de code et collez-le dans un projet Visual Studio, ou collez-le dans un fichier nommé parallel-scripts.cpp
, puis exécutez la commande suivante dans une fenêtre d’invite de commandes Visual Studio.
cl.exe /EHsc parallel-scripts.cpp /link ole32.lib
Voir aussi
Procédures pas à pas relatives au runtime d’accès concurrentiel
Parallélisme des tâches
Algorithmes parallèles
Agents asynchrones
Gestion des exceptions
Annulation dans la bibliothèque de modèles parallèles
Planificateur de tâches