I want to implement a Microsoft CryptographicServiceProvider library and currently I thinking about the best way how to deal with context handle which I create.
My question is specific to this case but the design approach can be used in other situations.
I come from a managed code background and I am not 100% shure about multithread pointer handling in C/C++.
In general there are two functions which are responsible for handle creation and destruction (CryptAcquireContext, CryptReleaseContext), and all subsequent CSP functions uses the handle which is return by the creator function.
I didn't found any concrete information or specification from Microsoft which gives a design approach or rules how to do it. But I did research with other CSP providers created by Microsoft to find out the design rules, which are:
- The functions must be thread safe
- The context handle will not be shared between threads
- If a context handle is not valid return with an error
Other MS CSP Provider will return a valid pointer as handle, or NULL if not.
I don't think that the calling application will pass complete garbage but it could happen that it passes a handle which has been already released and my library should return with an error.
This brought me to three ideas how to implement that:
Just allocate memory of my context struct with malloc or new and return the raw pointer as handle.
I can expect that the applications which call my library will pass a valid handle. But if not my library will run into an undefined behaviour. So I need a better solution.
Add the pointer which I create to a list (std::list, std::map). So I can iterate the list to check if the pointer exists. The access to the list is guarded with a mutex.
This should be safe and a regular API usage shouldn't be a performance issue. But in a Terminal Server scenario it could be. In this case the Windows process lsass.exe creates for every user who wants to login a CSP context in a separate thread and makes around 10 API calls per context.
The design goal is that my library should be able to handle 300 clients parallel. I don't know how many threads a created by Windows in this case.
So if possible I would prefer a lockless implementation.
I allocate a basic struct which holds a check value and the pointer of the actual data. Use the pointer of this struct as context handle.
typedef struct CSPHandle { int Type; // (eg. magic number CSPContext=0xA1B2C3D4) CSPContextPtr pCSPContext; };
So I could read the first byte of the passed pointer and check if the data equals my defined type. And I have the full control about actual data pointer, which is set to NULL if the context is released. Is this a good or bad idea?
What are your thoughts about this case? Should I go with one of these approaches or is there a other solution?
Thanks