Friday, October 23, 2009

A MSSCCI Primer (How Microsoft Source Code Control API works)

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
SccCheckin/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
SccCheckin(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
SccCloseProject(context1) close the connection open in context1
SccOpenProject(context1, project2, localpath2) open in context1 new connection
SccCloseProject(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
SccInitialize() receives context2
SccOpenProject(context2, project2, localpath2) open in context1 new connection
SccCloseProject(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.


Kiran said...

Hi Alin,

Thanks for writing this page. It was indeed very helpful.

Also, what is not clearly explained in MSDN is the role of Auxiliary project path. It would be good, if you could extend this article explaining the significance of this parameter.


Alin Constantin said...

@Kiran: AuxPath has only meaning for a specific source control provider. It depends from provider to provider as to what it's used for, and they can store there any necessary information that helps identifying a folder in the scc store so it can be found/open by SccOpenProject.
E.g. for SourceSafe databases, the auxpath contains the path to the VSS database (either a network share or a local folder where srcsafe.ini is). Other providers don't need extra settings and use empty strings for AuxPath. The string is not the same for all users opening the same projct (e.g. some users may access a VSS database as \\server\path, while other users may access the same database as C:\SourceSafe\database, etc).

Ankit Thakar said...

How to query the state of the capability flags?

Alin Constantin said...

@Ankit: It depends which flags you want to query. You can see here the full set of MSSCCI capability flags.

The supported SCC_CAP_xxxxxx flags are returned when you call SccInitialize(), in the DWORD pointed by lpSccCaps argument.
The supported SCC_EXCAP_xxxxxx flags can be checked if they are supported by calling SccGetExtendedCapabilities() with the flag of interest.

Geeta S said...

Hi Alin,

How to get the user id (user name ) of the checked out file (not checked by me(current user))?
By using SccQueryInfo() one can get if the file is checked out bu other user. By using SccProperties() one can see the user name in the dialog displayed by provider (provider specific dialog box). But If I want to retrieve the user name using scc api, what is the way to get it ? is there any callback method?


Alin Constantin said...

@Geeta: It's been 12 years since I used MSSCCI so I can't be 100% sure, but as far as I remember there is no way to get the user names who checked out a file via MSSCCI