API client.get fails through COM
Moderator: SourceGear
API client.get fails through COM
Thanks to SourceGear's user forums, I've been able to successfully implement a number of methods through the use of the API provided.
I have executed the library I've created numerous times in VS 2003. However, when I try implementing my library in other environments (namely, VB 6 and FoxPro 7) I'm running into a problem.
Using regasm.exe (.NET 1.1), I created a .tlb on the machine with VB6 and FoxPro. In the environments, I can see the methods exposed through COM.
So, here's the issue: After setting up an object to my class contained in the .dll, I make a call to <object>.VaultGet(string vaultFolder, string workingDirectory), which in turn creates a clientInstance and sends the call to client.Get(...). This works perfectly, everything is retrieved to the working directory specified. When I run this call again, however, it fails (in VB6 and FoxPro). The attached screenshot is what I see in FoxPro (VB6's error is the same).
If I attempt to run my app again, this time with a different working folder, it works - one time.
In other words, for example, if I call my method: VaultGet("$/Applications/project", "C:\directoryOne"), it works fine. If I rerun the app with the same call, it fails with the attached error. If I change the path name in the foxpro or vb program, i.e., VaultGet("$/Applications/project, "C:\directoryTwo"), and then run it, it works great again.
I've removed the GUID's in the registry pointing to the my .dll and .tlb, recompiled the .tlb, restarted the environments, restarted my machine, etc. But once this working directory is used once, it is marked - never to be used again.
Any thoughts?
Thanks,
Mark
I have executed the library I've created numerous times in VS 2003. However, when I try implementing my library in other environments (namely, VB 6 and FoxPro 7) I'm running into a problem.
Using regasm.exe (.NET 1.1), I created a .tlb on the machine with VB6 and FoxPro. In the environments, I can see the methods exposed through COM.
So, here's the issue: After setting up an object to my class contained in the .dll, I make a call to <object>.VaultGet(string vaultFolder, string workingDirectory), which in turn creates a clientInstance and sends the call to client.Get(...). This works perfectly, everything is retrieved to the working directory specified. When I run this call again, however, it fails (in VB6 and FoxPro). The attached screenshot is what I see in FoxPro (VB6's error is the same).
If I attempt to run my app again, this time with a different working folder, it works - one time.
In other words, for example, if I call my method: VaultGet("$/Applications/project", "C:\directoryOne"), it works fine. If I rerun the app with the same call, it fails with the attached error. If I change the path name in the foxpro or vb program, i.e., VaultGet("$/Applications/project, "C:\directoryTwo"), and then run it, it works great again.
I've removed the GUID's in the registry pointing to the my .dll and .tlb, recompiled the .tlb, restarted the environments, restarted my machine, etc. But once this working directory is used once, it is marked - never to be used again.
Any thoughts?
Thanks,
Mark
- Attachments
-
- Error.jpg (28.33 KiB) Viewed 16168 times
I'm not familiar with FoxPro (I've never used it), so I don't know how much help I can be.
The exception you posted seems to reveal that the .NET framework can't find VaultClientOperationsLib, which is strange since previous calls obviously did find it (since most of the download code, and all of the working folder code, is in that library). I think the error about the working folder being incompatable is a red herring: that's kind of a catch-all string used for exceptions at that low-level (reading/writing working folder state information). I expected that any error at that level would be because of the data, and not something like this (I see I was wrong ), but fortunately the real exception string is included.
Maybe .NET has problems deserializing the data when running in FoxPro? Can you use those working folders from another Vault client after they've been filled by a Get?
The exception you posted seems to reveal that the .NET framework can't find VaultClientOperationsLib, which is strange since previous calls obviously did find it (since most of the download code, and all of the working folder code, is in that library). I think the error about the working folder being incompatable is a red herring: that's kind of a catch-all string used for exceptions at that low-level (reading/writing working folder state information). I expected that any error at that level would be because of the data, and not something like this (I see I was wrong ), but fortunately the real exception string is included.
Maybe .NET has problems deserializing the data when running in FoxPro? Can you use those working folders from another Vault client after they've been filled by a Get?
Shaw Terwilliger
SourceGear LLC
`echo sterwill5sourcegear6com | tr 56 @.`
SourceGear LLC
`echo sterwill5sourcegear6com | tr 56 @.`
I've continued to work on this problem over the past week. I thought I might be on to something when I noticed that the vaultClientOperationsLib.dll version in the VB error was set to 2.0.1.xxxx (I was using the 2.0.2.xxxx vaultClientOperationsLib.dll from within my library).
(By the way, the screenshot from the foxpro error is from a different machine running an older version of vault - just wanted to point that out to avoid confusion).
Even though it appears that the vaultClientOperationsLib isn't registered in the GAC, I thought I'd go ahead and install the latest version of the vault client (2.0.2).
Now I reran my simple getlatest call in my VB app using exactly the same local directory in which I received the "Cannot find vaultClientOperationsLib.dll version 2.0.1.xxxx" the first time, and I got the same error - with the same dll version (cannot find...2.0.1.xxxx). I suspected dll hell, but before I did some registry cleaning, I tried running the VB getlatest call using a different local directory. It worked great. So I ran it again to see if I would run into the same problem stated above. I did.
This time, however, it stated that it couldn't find version 2.0.2.xxxx of the vaultClientOperationsLib.dll. So, it turned out that it wasn't a versioning issue.
The wierd thing is, if it was my client VB app causing problems, I would think I should see this error come up the first time I execute the code.
So it appears I'm back a square one....What is holding onto the local directory name that gets passed to get api call, and why does the vault api puke when it sees this directory name more than once?
One last clue I can provide...When I run the call from VB a second time (when I receive the .dll error) the directory structure from vault is created on my local machine before it pukes. I'm not sure how the vault api handles the get call, if it creates the vault directory structure on the local machine, then populates the structure with the actual data from the server, but it's dying right after the structure is created.
Mark
(By the way, the screenshot from the foxpro error is from a different machine running an older version of vault - just wanted to point that out to avoid confusion).
Even though it appears that the vaultClientOperationsLib isn't registered in the GAC, I thought I'd go ahead and install the latest version of the vault client (2.0.2).
Now I reran my simple getlatest call in my VB app using exactly the same local directory in which I received the "Cannot find vaultClientOperationsLib.dll version 2.0.1.xxxx" the first time, and I got the same error - with the same dll version (cannot find...2.0.1.xxxx). I suspected dll hell, but before I did some registry cleaning, I tried running the VB getlatest call using a different local directory. It worked great. So I ran it again to see if I would run into the same problem stated above. I did.
This time, however, it stated that it couldn't find version 2.0.2.xxxx of the vaultClientOperationsLib.dll. So, it turned out that it wasn't a versioning issue.
The wierd thing is, if it was my client VB app causing problems, I would think I should see this error come up the first time I execute the code.
So it appears I'm back a square one....What is holding onto the local directory name that gets passed to get api call, and why does the vault api puke when it sees this directory name more than once?
One last clue I can provide...When I run the call from VB a second time (when I receive the .dll error) the directory structure from vault is created on my local machine before it pukes. I'm not sure how the vault api handles the get call, if it creates the vault directory structure on the local machine, then populates the structure with the actual data from the server, but it's dying right after the structure is created.
Mark
Yes, the local directory structure is created first, then the file data is merged into it. But it's the same function that does both of these things in sequence, and that function is inside VaultClientOperationsLib. So the assembly is obviously found once (so the call can begin and the directories are created), but then some problem happens before the data is written that causes the CLR to claim it disappeared.
Lots of code executes between the time the disk tree is created and the time the first file is placed in a working folder. Here are the big things that happen inside Get():
Lots of code executes between the time the disk tree is created and the time the first file is placed in a working folder. Here are the big things that happen inside Get():
- Directories on disk are created. No network activity required, since the current tree cache data is used.
- A thread is spawned so we can update disk items while we continue to download. Perhaps this is causing your problems. You can disable the use of the thread (tasks will just be serialized and overall execution time will increase) by setting your ClientInstance object's UseUpdateThread variable to false before calling any of the Get functions.
- The server is contacted with requests to download lots of files. The server responds with "success, go ahead" or "stop, no permission" or something like that.
- We download a file from the stream, put it in a temp folder, and pass it to the update thread. We keep doing this while there are files to download and the update thread can keep up (while there are fewer than 200 temp files still around).
- In the update thread, we take this file, open up the working folder object, and tell it to manage a new version. It opens the temp file (which is a delta), opens the source baseline file, does some delta magic, and creates a target. The target is merged into your working folder according to your preferences (auto merge, overwrite, don't overwrite). The working folder state information is saved to disk either inside _sgvault\state in the working folder, or down in your %APPDATA% folder. The temp file is deleted.
- When all the files are done, we tell the server we're done downloading and stop the update thread.
Shaw Terwilliger
SourceGear LLC
`echo sterwill5sourcegear6com | tr 56 @.`
SourceGear LLC
`echo sterwill5sourcegear6com | tr 56 @.`
Shaw,
Thank you!
If I delete the temp directory (C:\Documents and Settings\<user>\Application Data\SourceGear\Vault_1\Client\...) I can get it to run once more. (Stopping the thread had no effect.)
I bet you can guess what my next question is going to be... Is there a way, through the API, to stop vault from generating the temp directory? More specifically, is there a way to stop vault from generating the offending file in temp, whatever that may be?
Thanks,
Mark
Thank you!
If I delete the temp directory (C:\Documents and Settings\<user>\Application Data\SourceGear\Vault_1\Client\...) I can get it to run once more. (Stopping the thread had no effect.)
I bet you can guess what my next question is going to be... Is there a way, through the API, to stop vault from generating the temp directory? More specifically, is there a way to stop vault from generating the offending file in temp, whatever that may be?
Thanks,
Mark
I've added some additional code in my client application to remove the temp directory between get calls. Specifically, it appears that the offending file is "state" in the temp directory.
This works fine for simple get calls, but when you start mixing gets and checkouts, it becomes less obvious. Namely, you can't simply delete the temp directory if you want to eventally check the file(s) back in (commit), as the temp directory holds information that commit needs in order to "know" where the checked out file(s) live on your machine.
I know sourcegear is working toward supporting the API. With that said, I feel that futher testing/refining of the API (especially w/ COM Interop) would be greatly appreciated. We have numerous projects getting switched from source safe to vault, and successful linking to the API is of great importance.
Thanks,
Mark
This works fine for simple get calls, but when you start mixing gets and checkouts, it becomes less obvious. Namely, you can't simply delete the temp directory if you want to eventally check the file(s) back in (commit), as the temp directory holds information that commit needs in order to "know" where the checked out file(s) live on your machine.
I know sourcegear is working toward supporting the API. With that said, I feel that futher testing/refining of the API (especially w/ COM Interop) would be greatly appreciated. We have numerous projects getting switched from source safe to vault, and successful linking to the API is of great importance.
Thanks,
Mark
I wasn't clear enough about exactly what the temp folder is, and how it's different from the working folder hidden data folder.
When I said downloaded files are placed in a temp folder, I meant they're stored in %TEMP% (which will usually be somewhere like C:\Documents and Settings\<username>\Local Settings\Temp). When the delta engine is invoked, it takes this temp file and creates a target file in the working folder's hidden folder (inside %APPDATA%, something like C:\Documents and Settings\<username>\Application Data\SourceGear\Vault_1\Client\<GUID>\<username>\_sgvault\).
The files in the second location are permanent. You shouldn't delete those files if you ever want to use your working folder again, since those files contain information about the versions, dates, and baselines of your working folder files.
If Vault is cleaning up the temporary files (in %TEMP), it's probably some other step of the process that's causing the problems.
When I said downloaded files are placed in a temp folder, I meant they're stored in %TEMP% (which will usually be somewhere like C:\Documents and Settings\<username>\Local Settings\Temp). When the delta engine is invoked, it takes this temp file and creates a target file in the working folder's hidden folder (inside %APPDATA%, something like C:\Documents and Settings\<username>\Application Data\SourceGear\Vault_1\Client\<GUID>\<username>\_sgvault\).
The files in the second location are permanent. You shouldn't delete those files if you ever want to use your working folder again, since those files contain information about the versions, dates, and baselines of your working folder files.
If Vault is cleaning up the temporary files (in %TEMP), it's probably some other step of the process that's causing the problems.
Shaw Terwilliger
SourceGear LLC
`echo sterwill5sourcegear6com | tr 56 @.`
SourceGear LLC
`echo sterwill5sourcegear6com | tr 56 @.`
Shaw,
Thank you for the further explaination. I will look into this tomorrow. Just to be clear, the %APPDATA% folder shouldn't be touched? I have been deleting the state file in %APPDATA% to allow me to run multiple gets to the same local directory (without receiving the aforementioned error).
You're saying I should remove the state file in the working directory after each get?
Let me know, I'll give it a try and report back.
Mark
Thank you for the further explaination. I will look into this tomorrow. Just to be clear, the %APPDATA% folder shouldn't be touched? I have been deleting the state file in %APPDATA% to allow me to run multiple gets to the same local directory (without receiving the aforementioned error).
You're saying I should remove the state file in the working directory after each get?
Let me know, I'll give it a try and report back.
Mark
Don't delete anything from %APPDATA% if you want Vault to work correctly. Don't delete the state file. Use a non-working folder (GetToNonWorkingFolder) if you want to reuse a working folder for multiple gets with no state saved between them. You won't be able to commit any changes from a non-working folder (this is by design).
Shaw Terwilliger
SourceGear LLC
`echo sterwill5sourcegear6com | tr 56 @.`
SourceGear LLC
`echo sterwill5sourcegear6com | tr 56 @.`
I understand the approach you're taking, This works fine to do a get multiple times. Thanks for pointing that out.
The issue of checking out a file or folder still remains...
I'm trying my best to skate around the fact that you need to call the get method after calling the checkout method. From my client app, I've tried calling the API method checkout, then calling getToNonWorkingFolder (as neither require a working folder to be set).
After running this, I can go to the GUI app and see that these files are checked out without a working folder (status is "missing"). If I now set the working folder to be the folder I did a getToNonWorkingFolder to, the status changes to "Unknown". Any attempt from the GUI to check these files back in now fails - I have to do a "undo checkout".
Is there a workaround for this so I check these files back in?
Further, how are you (i.e., sourcegear) able to integrate Vault's source control with VB6 and FoxPro? You have to be exposing methods to COM Interop so these non-.NET MS apps can see them, right?
Thanks,
Mark
The issue of checking out a file or folder still remains...
I'm trying my best to skate around the fact that you need to call the get method after calling the checkout method. From my client app, I've tried calling the API method checkout, then calling getToNonWorkingFolder (as neither require a working folder to be set).
After running this, I can go to the GUI app and see that these files are checked out without a working folder (status is "missing"). If I now set the working folder to be the folder I did a getToNonWorkingFolder to, the status changes to "Unknown". Any attempt from the GUI to check these files back in now fails - I have to do a "undo checkout".
Is there a workaround for this so I check these files back in?
Further, how are you (i.e., sourcegear) able to integrate Vault's source control with VB6 and FoxPro? You have to be exposing methods to COM Interop so these non-.NET MS apps can see them, right?
Thanks,
Mark
CheckOut, then GetToNonWorkingFolder will always result in the scenario you describe. Non-working folders have no state information saved by Vault, so the versions of the files there aren't known. Therefore, they can't be checked back in (since we can't create a delta to send to the server between an unknown version and a known version). Get() is the only function that will store that state information.
Integration with Visual Studio is done without using COM. MSSCI, the Microsoft Source Control Interface, simply makes calls to functions exported from an unmanaged DLL, which we have written. Our implementation hosts an instance of the .NET CLR, which then loads our managed Vault code.
Did you try setting the ClientInstance's UseUpdateThread property to false before calling Get? I think we may be able to figure out what's causing the CLR (wherever it's hosted) to forget where to find VaultClientOperationsLib.dll. Maybe try installing it in the GAC (which you have have already done to make COM work)?
Integration with Visual Studio is done without using COM. MSSCI, the Microsoft Source Control Interface, simply makes calls to functions exported from an unmanaged DLL, which we have written. Our implementation hosts an instance of the .NET CLR, which then loads our managed Vault code.
Did you try setting the ClientInstance's UseUpdateThread property to false before calling Get? I think we may be able to figure out what's causing the CLR (wherever it's hosted) to forget where to find VaultClientOperationsLib.dll. Maybe try installing it in the GAC (which you have have already done to make COM work)?
Shaw Terwilliger
SourceGear LLC
`echo sterwill5sourcegear6com | tr 56 @.`
SourceGear LLC
`echo sterwill5sourcegear6com | tr 56 @.`
Shaw,
I attempted setting the UseUpdateThread property to false (as you instructed above) to no avail. I received the same error.
Unfortunately, we are unable to install my .dll in the GAC, as the .dlls that Vault provides aren't strongly named (a strongly named .dll can't reference non-strongly named .dlls (vault's), therefore, we can't add my .dll to the GAC - they have to be strongly named to be added).
The good news is I've been able to track down the issue (I think). If I place my .dll, along with Vault's .dlls, in the same directory as the executable, it works. Apparently, my .dll will look in predifined locations for Vault's .dlls (like in the current directory of the running app, etc.). If it can't find them in the various paths, it throws up its hands.
There are properties/methods from within .NET (in the AppDomain Class) that can tell the assembly where to look for referenced private assemblies (i.e., non-GAC assemblies) - so you can expand the list of paths to search.
The one thing that's still odd, I wonder why it can execute once? We may never know...
Thanks,
Mark
I attempted setting the UseUpdateThread property to false (as you instructed above) to no avail. I received the same error.
Unfortunately, we are unable to install my .dll in the GAC, as the .dlls that Vault provides aren't strongly named (a strongly named .dll can't reference non-strongly named .dlls (vault's), therefore, we can't add my .dll to the GAC - they have to be strongly named to be added).
The good news is I've been able to track down the issue (I think). If I place my .dll, along with Vault's .dlls, in the same directory as the executable, it works. Apparently, my .dll will look in predifined locations for Vault's .dlls (like in the current directory of the running app, etc.). If it can't find them in the various paths, it throws up its hands.
There are properties/methods from within .NET (in the AppDomain Class) that can tell the assembly where to look for referenced private assemblies (i.e., non-GAC assemblies) - so you can expand the list of paths to search.
The one thing that's still odd, I wonder why it can execute once? We may never know...
Thanks,
Mark