Bonjour !

J'ai un service qui expose une classe COM à des clients.
La classe COM lit des données pour les fournir à ses clients.
Les données sont en lecture seule et sont mises à jour de temps en temps par d'autres processus, en bloc.
Je souhaite changer les données à la volée sans arrêter le service.

Supposons que les données soient gérées par une classe DataLinker:
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
class DataLinker
{
public:
	DataLinker(std::_tstring);
	bool Open();
	...
};
Voici la classe DataLocker qui maintient les données en disponibilité, tant qu'au
moins un client les utilise ou tant qu'il n'y a pas de nouvelles données disponibles,
grâce à un compteur de référence:
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
class DataLocker
{
public:
	DataLocker() = default;
 
private:
	DataLocker(DataLocker const &) = delete;
	DataLocker & operator=(DataLocker const &) = delete;
 
	DataLinker *f_ptr_DataLinker = nullptr;
	std::_tstring f_datapath;
	LONG f_refcnt = 0l;
 
public:
	DataLinker *GetDataLinker()
		{ return f_ptr_DataLinker; }
 
	std::_tstring const & GetDataPath() const
		{ return f_datapath; }
 
	bool IsEmpty() const
		{ return f_ptr_DataLinker == nullptr; }
 
	void AddRef()
	{
		InterlockedIncrement(&f_refcnt);
	}
 
	void Release()
	{
		if (InterlockedDecrement(&f_refcnt) == 0)
			Unload();
	}
 
	bool Load(std::_tstring const & datapath)
	{
		ATLASSUME(f_ptr_DataLinker == nullptr && f_refcnt == 0l);
		DataLinker *ptr_DataLinker;
		try
		{
			ptr_DataLinker = new DataLinker(datapath);
			if (ptr_DataLinker == nullptr || !ptr_DataLinker->Open())
			{
				delete ptr_DataLinker;
				return false;
			}
			f_ptr_DataLinker = ptr_DataLinker;
			f_refcnt = 1l;//Increment(&f_refcnt);
			f_datapath = datapath;
			return true;
		}
		catch (std::exception & e)
		{
#ifdef _DEBUG
			OutputDebugStringA(e.what());
#endif
			delete ptr_DataLinker;
		}
		return false;
	}
 
private:
	void Unload()
	{
		ATLASSUME(f_ptr_DataLinker != nullptr && f_refcnt == 0l);
		delete f_ptr_DataLinker;
		f_ptr_DataLinker = nullptr;
		//f_refcnt = 0l;
		f_datapath.clear();
	}
};
Voici la classe singleton DataSwitcher qui gère la disponibilité et le changement de données (un pooler avec deux DataLocker pour passer de l'un à l'autre):
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
class DataSwitcher
{
private:
	DataLocker f_pls1;
	DataLocker f_pls2;
	DataLocker *f_curpls = nullptr;
 
	DataSwitcher()
		{ CheckParams(); }
 
public:
	void CheckParams();
 
	static DataSwitcher & Get();
 
	DataLocker *Data()
		{ return f_curpls; }
};
 
void DataSwitcher::CheckParams()
{
	//check if a DataLocker is available
	DataLocker *next_pls = nullptr;
	DataLocker *curr_pls = nullptr;
	if (f_pls1.IsEmpty())
	{
		next_pls = &f_pls1;
		curr_pls = &f_pls2;
	}
	else if (f_pls2.IsEmpty())
	{
		next_pls = &f_pls2;
		curr_pls = &f_pls1;
	}
	if (next_pls)
	{
		unsigned somevalue = read_somevalue_somewhere();
		std::_tstring datapath(_T("D:\\DATA_VERSION_") + std::to_tstring(somevalue) + _T("\\"););
		if (datapath != curr_pls->GetDataPath())
		{
			//load...
			if (next_pls->Load(datapath))
			{
				//... and switch
				f_curpls = next_pls;
				//release current
				if (!curr_pls->IsEmpty())
					curr_pls->Release();
			}
		}
	}
}
 
DataSwitcher & DataSwitcher::Get()
{
	static DataSwitcher ds;
	return ds;
}
Et pour finir voici le code à ajouter dans la classe COM, ou en début et fin d'utilisation de n'importe quel client:
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
	DataLocker *f_curpls = nullptr;
 
	...
 
	HRESULT FinalConstruct()
	{
		f_curpls = DataSingleton::Get().Data();
		if (f_curpls == nullptr)
			return E_FAIL;
		f_curpls->AddRef();
		return S_OK;
	}
 
	void FinalRelease()
	{
		if (f_curpls)
			f_curpls->Release();
	}
La méthode DataSwitcher::CheckParams() est appélée une fois lors de la construction unique de DataSwitcher dans sa méthode static Get(), et puis de temps en temps dans un background thread qui appelle CheckParams().

J'ai trois doutes:
-le compteur de référence de DataLocker est-il safe, ne va-t-il jamais passer sous zéro, ou rester toujours strictement supérieur à zéro ?
-Est-ce que l'échange de DataLocker dans CheckParams() est safe, ainsi que son acquisition et incrémentation de référence dans le FinalConstruct() des clients ?
-En fin de programme, il reste un DataLocker disponible. Où faire le dernier Release() proprement ?

Voilà, merci, j'espère que j'ai été assez clair