I’ve intended to publish this article long time ago, and never found the time to finish all I wanted to wanted to be in it. I’ll publish it now as is, hoping it may still help someone get started…
------------------------
I've been recently asked what's the correct order in which MSSCCI functions should be called for integrating an IDE application like Visual Studio with a MSSCCI (Microsoft Source Code Control) provider.
While looking for references, I realized that the MSSCCI documentation in MSDN is a good reference for the API functions, but it doesn’t do a good job in describing MSSCCI concepts and how to start implementing a MSSCCI-compliant source control provider. It looks like it's time to write such page...
What is a MSSCCI dll?
Basically, a MSSCCI source control provider is a 32-bit Native dynamic link library, exporting a couple of predefined functions with names beginning with “Scc” (e.g. SccAdd, SccCheckout, etc). A MSSCCI provider doesn't need to implement all these functions, but there is a subset of the functions that must be implemented by any provider (more on that later) to make the provider be usable by an Integrated Development Environment (IDE) like Visual Studio.
An IDE loads the MSSCCI dll by calling LoadLibrary or LoadLibraryEx. After that it usually looks up and binds the MSSCCI function by name by calling GetProcAddress, and will start calling the Scc functions directly. When the IDE is done using the MSSCCI provider it can unload the dll by calling FreeLibrary.
There are no Unicode versions of the functions, therefore Unicode-ANSI conversion may need to be performed as necessary by both IDE and the source control provider.
Because the MSSCCI providers are 32 bit dlls, they cannot be loaded directly in 64 bit processes. To the best of my knowledge, there are no plans of creating a 64-bit version of the interface for loading a MSSCCI provider in a 64-bit process. However this limitation can be circumvented by creating a 32-bit COM object that loads and wraps the MSSCCI dll, further exposing COM interfaces functions similar to MSSCCI functions; the COM wrapper object can be co-created in 64-bit processes.
MSSCCI Connections
To perform source control operations with a MSSCCI provider, an IDE needs to open a connection; this is done with the SccOpenProject function. A connection is simply an association (1:1 mapping) between a folder in the source control storage database (project name in MSSCCI terms) and a folder on local disk (local project path). An example (for SourceSafe) might be:
C:\My Solutions\Wincheat <----> "$/Solutions/Wincheat", CDFAAAA
When the connection is open, the provider can also perform authentication of the user for accessing the source control storage, etc.
Note there are no restriction on the folders mapping. Multiple connections/mappings can be opened from the same local folder pointing to different folders in the scc database; multiple connections can be opened from various local folders pointing to the same project in the scc database; and mapped projects can overlap.
After opening a source control connection, further source control operations are possible using that connection.Within a connection, files are identified only based on their local path; the path must be under the cone defined by the connection local root. The path in storage is automatically deduced based on the connection root folders. Files outside this connection root should not be controllable through this connection.
E.g. The C:\My Solutions\Wincheat\WinCheat.sln can be controlled through the connection above and is associated with $/Solutions/Wincheat/Wincheat.sln. The file C:\My Solutions\OtherSolution\OtherSolution.sln can’t be controlled using this connection, even though in the scc database there may be an matching item at $/Solutions/OtherSolution/OtherSolution.sln.
In the following example, there are 3 connections open
C:\My Solutions\Solution1 <----> "$/Solution1", BCDAAAA
D:\Project1 <----> "$/Solution1/Project1", KRFAAAA
C:\My Solutions\Solution1\SubProject <----> "$/Solution1/Project1", KRFAAAA
Depending on which connection is used, the same file in storage $/Solution1/Project1/File.txt can be referenced either as C:\My Solutions\Solution1\Project1\File.txt, D:\Project1\File.txt or C:\My Solutions\Solution1\SubProject\File.txt
A MSSCCI provider implementer should not make assumptions (based on enlistment or other similar mapping information specific to that provider) that a file in the storage is associated with only one file on local disk - at any time a new MSSCCI connection can be opened from any folder on local disk and the same file in storage can be accessed using that connection.
When a connection is no longer needed, the IDE should close it using the SccCloseProject function.
The server path used for opening a connection is specific to each source control provider. An IDE application has no idea of the format (and should not make any assumptions on the format). You may be asking yourself where does the projName string used in SccOpenProject comes from. Usually, during an Open from source control operation the IDE will call SccGetProjPath which allows the user to select a folder in the source control database. The string identifying the selected project is returned to the IDE, and used immediately in new connections, or is persisted in solution/project files and used on subsequent open from disk of that project/solution. Also, the IDE may attempt to retrieve server path strings from mssccprj.scc file if the source control provider creates and maintains such files.
A typical fragment showing opening a connection could look like this:
SccOpenProject(context1, project1, localpath1) open in context1 a connection
project1<---->localpath1SccCheckin/SccGet/etc invoke source control functions using the connection open by using file paths under localpath1 SccCloseProject(context1) close the connection open in context1
MSSCCI Contexts
You may have noticed that most of the MSSCCI functions have an argument LPVOID pvContext.
A context is obtained by calling SccInitialize and can be disposed of by calling SccUninitialize.
A context allows identifying the connection that should be used with a specific operation like Get, Checkin, etc, because at one point in time, at most one connection can be opened within a context.
A context is simply a data structure specific to each source control provider; it usually contains information like flags specifying whether a connection is opened or not within that context, information about the connection open, etc.
The typical order of operations with a source control database is:
SccInitialize() receives context SccOpenProject(context, project, localpath) open in this context a connection
project<---->localpathSccCheckin(context)/SccGet(context)/etc perform any source control operations using the context and the project<---->localpath connection. The file paths needs to be SccCloseProject(context) close the connection open in this context SccUninitialize(context) dispose of context
If an IDE needs to open a different connection to the database:
a) it can reuse an existing context for the new connection, but first it will have to close the currently open connection within that context. Typical order of operations is:
SccInitialize() receives context1 SccOpenProject(context1, project1, localpath1) open in context1 connection
project1<---->localpath1SccCloseProject(context1) close the connection open in context1 SccOpenProject(context1, project2, localpath2) open in context1 new connection
project2<---->localpath2SccCloseProject(context1) close the new connection open in context1 SccUninitialize(context1) dispose of context1
b) it can create a new context and open the new connection with that context. For this to work, a provider must support the SCC_CAP_REENTRANT capability to declare it is reentrant, thread-safe and that it supports opening multiple connections. An order of operations may be:
SccInitialize() receives context1 SccOpenProject(context1, project1, localpath1) open in context1 connection
project1<---->localpath1SccInitialize() receives context2 SccOpenProject(context2, project2, localpath2) open in context1 new connection
project2<---->localpath2SccCloseProject(context1) close the new connection open in context1 SccCloseProject(context2) close the new connection open in context2 SccUninitialize(context2) dispose of context2 SccUninitialize(context1) dispose of context1
Note that operations on multiple contexts can be interspersed any way you like as long as the order of the operation within a context is the correct one.
Versions, capabilities and extended capabilities
When calling into a MSSCCI provider, an IDE needs to know which source control functionality is supported by the MSSCCI provider (which functions are implemented). There are a couple of functions that return this information.
SccGetVersion is one of the first functions an IDE will call. This returns the version of the MSSCCI provider (currently there can be providers implementing versions 1.0, 1.1, 1.2 or 1.3 of the MSSCCI spec). Certain functions are defined only by higher number versions MSSCCI spec (were introduced later). Thus if a provider reports supporting version 1.1 of the spec, an IDE won’t even bother binding and calling a function like SccWillCreateSccFile introduced in version 1.2 of the spec.
When a MSSCCI provider declares implementing a specific version of a spec, it is not required to implement all the functions defined by that spec version. For instance, a MSSCCI 1.2 provider may implement SccCreateSubProject, but may not support features like batching (SccBeginBatch) or viewing file history (SccHistory). An IDE will not call a function if the MSSCCI provider does not declare supporting it.
To define the functionality supported, a provider should return the lpSccCaps capabilities flags during the call to SccInitialize(). E.g. Set and return the bit SCC_CAP_HISTORY in the lpSccCaps if your provider supports history operations.
For MSSCCI 1.3 and later providers, additional capabilities flags can be returned by implementing the SccGetExtendedCapabilities() function.
Beside flags specifying which functions are implemented, capabilities flags may specify other information about the provider, e.g. whether comments are supported on certain operations.
Note that an IDE may only use providers implementing specific functionality. E.g. VisualStudio 2002 and later can only use source control providers supporting multiple connections (reentrant providers, with version 1.1 or greater and defining SCC_CAP_REENTRANT flag).
If the provider does not define a capability flag introduced by a later MSSCCI spec version than the version implemented by the provider, the IDE assumes that functionality is not available in the provider, and may fallback to other scc operations. E.g. If a MSSCCI 1.1 provider is used with VS2003 and later, the lack of the SCC_CAP_MULTICHECKOUT capability will make VS attempt other ways of determining whether the provider supports or not multiple checkouts. Or, if a MSSCCI 1.2 provider is used with VS2005 and later to control websites, recursive enumeration of files in the source control database using SccPopulateDirList is not possible and VS will try alternative (and slower) ways of achieving the same result, using SccPopulateList and recursive SccGet operations.