Why is COM ignoring my DllSurrogate entry? - com

I'm aware of this question, but I've followed the steps listed there and I'm still stumped.
I've got a class, registered as follows (this is an RGS file):
HKCR
{
NoRemove CLSID
{
ForceRemove {5CB1D770-BF72-4F3D-B4DA-85E0542126F4} = s 'ExamplePlugin Class'
{
val AppID = s '%APPID%'
InprocServer32 = s '%MODULE%'
{
val ThreadingModel = s 'Free'
}
}
}
}
I've got an AppID, registered as follows:
HKCR
{
NoRemove AppID
{
'%APPID%' = s 'ExamplePlugin'
{
val DllSurrogate = s ''
}
'ExamplePlugin.DLL'
{
val AppID = s '%APPID%'
}
}
}
I'm passing CLSCTX_ALL to CComPtr<IPlugin>::CoCreateInstance.
In short, as far as I can tell, I've followed the checklist:
I have an AppID value specified under my CLSID. I have a corresponding AppID key.
I've included CLSCTX_LOCAL_SERVER in the activation call. My CLSID key does not have any LocalServer keys.
My CLSID key contains an InprocServer32 key.
I assume that when the checklist says "the proxy/stub DLL specified in InprocServer32 exists", it means "the implementing DLL". It does exist. My proxy/stub DLL is correctly registered elsewhere.
I have a DllSurrogate value under my AppID key.
If I look at my class in OLE/COM object viewer, it appears to be correct (the Implementation tab has "Use Surrogate Process" checked).
It's still not working: my DLL is loading into the same process as my host EXE.
A clue: If I run Process Monitor, I can't see it looking for the CLSID\{...}\AppID value. If I pass CLSCTX_LOCAL_SERVER to CoCreateInstance, I get "class not registered" returned.
I'm on Windows 2008 x64, but I've tried with my code compiled for both x86 and x64 with the same result.
What am I missing?

You have to spefify CLSCTX_LOCAL_SERVER to CoCreateInstance() to enforce out-proc activation. That's peculiarity of DCOM - if your component is registered as an in-proc COM server and you specify a CLSCTX_ mask including any value for in-proc activation the component is activated in-proc - DCOM is not used.
Note that COM+ provides almost the same functionality but if you create a "server application" and add your component there and then specify CLSCTX_ALL the component will be instantiated in COM+ surrogate - out-proc activation will be selected automatically.

It turns out that the documentation is misleading. It's not enough merely to set CLSCTX_LOCAL_SERVER. You also have to remove the CLSCTX_INPROC values from the call to CoCreateInstance. If you don't, COM will always use the in-proc stuff, and will never query for DllSurrogate.

Related

Registering .net assembly for COM succeeds with regasm but fails using RegistrationServices.RegisterAssembly

This is one of the strangest issue I have encountered.
There is a .net assembly, which is exposed to COM.
If you register it with regasm /codebase my.dll - it is sucessfully registered, and can be used.
However, if you register it from code using RegistrationServices.RegisterAssembly() :
[...]
RegistrationServices regSvcs = new RegistrationServices();
Assembly assembly = Assembly.LoadFrom(path);
// must call this before overriding registry hives to prevent binding failures on exported types during RegisterAssembly
assembly.GetExportedTypes();
using (RegistryHarvester registryHarvester = new RegistryHarvester(true))
{
// ******** this throws *********
regSvcs.RegisterAssembly(assembly, AssemblyRegistrationFlags.SetCodeBase);
}
Then it throws exception:
Could not load file or assembly 'Infragistics2.Win.UltraWinTree.v9.2, Version=9.2.20092.2083,
Culture=neutral, PublicKeyToken=7dd5c3163f2cd0cb' or one of its dependencies.
Provider type not defined. (Exception from HRESULT: 0x80090017)
This error has very little resource on the net, and looks like related to some security(?) cryptography(?) feature.
After long-long hours, I figured out what causes this (but don't know why):
If there is a public class with a public constructor in the assembly with a parameter UltraTree (from the referenced assembly 'Infragistics2.Win.UltraWinTree.v9.2'), then you cannot register from code, but with regasm only.
When I changed the have a public function Init(UltraTree tree), then it works, I can register from code. So:
// regasm: OK / RegistrationServices.RegisterAssembly(): exception
public class Foo
{
public Foo(UltraWinTree tree) { .. }
}
Foo foo = new Foo(_tree);
-------------- vs --------------
// regasm: OK / RegistrationServices.RegisterAssembly(): OK
public class Foo
{
public Foo() {}
public void Init(UltraWinTree tree) { .. }
}
Foo foo = new Foo();
foo.Init(_tree);
So I could workaround by passing UltraWinTree in a new Init() function instead of constructor, but this is not nice, and I want to know the reason, what the heck is going on?
Anyone has any idea? Thanks.
PS:
Okay, but why we want to register from code? As we use Wix to create installer, which uses heat.exe to harvest registry entries (which are added during asm registration), so heat.exe does assembly registration from code.
I've been dealing with this for years so this is the only answer you need to read:
Heat calls regasm /regfile. So does InstallShield when you tell it to. If you read this page:
https://learn.microsoft.com/en-us/dotnet/framework/tools/regasm-exe-assembly-registration-tool
There's a very important caveat in the remarks section.
You can use the /regfile option to generate a .reg file that contains
the registry entries instead of making the changes directly to the
registry. You can update the registry on a computer by importing the
.reg file with the Registry Editor tool (Regedit.exe). The .reg file
does not contain any registry updates that can be made by user-defined
register functions. The /regfile option only emits registry entries
for managed classes. This option does not emit entries for TypeLibIDs
or InterfaceIDs.
So what to do? Use Heat to generate most of the metadata. Then on a clean machine, (snapshot VM best) us a registry snapshot and compare tool such as InCntrl3 or InstallWatch Pro and sniff out what additional meta regasm writes to the registry. Finally massage that into your Wxs code.
Then on a cleam machine test the install. The result should work and not require any custom actions in the install.

Why does ATL COM registration defaults to HKCR

When creating an COM object through ATL, the default .rgs file always registers the object to HKCR:
HKCR
{
...
}
If this object is being written to the registry for the first time, writing to HKCR is equivalent to writing to HKLM, which would make the object globally visible to all users.
This seems like a rather poor idea from the stand point of registry maintenance since the HKCR/HKLM will quickly blow up with any COM objects installed by any user on the machine and thus slowing down access for all the users. In addition, any deployment module including COM objects will have to request for admin elevation on Windows with UAC, which would adversely affect deployability. So why is ATL/COM designed this way?
This article suggests that in order to register a COM object under HKCU (http://blogs.msdn.com/b/jaredpar/archive/2005/05/29/423000.aspx), DllRegisterServer must be updated. Why wouldn't it work if we simply changed HKCR to HKCU in the .rgs file? Would ATL simply ignore it? If that's the case, why does it insist on using HKCR?
Registration through DllRegisterServer is expected to provide system wide registration and COM class availability by design.
To supersede this registration behavior and register per-user, at some point another registration method was introduced: DllInstall function. Current ATL implements this out of the box offering per-user registration with "user" command line switch provided to the DllInstall function.
Below is code you have on new project generated from template (using wizard):
// DllInstall - Adds/Removes entries to the system registry per user per machine.
STDAPI DllInstall(BOOL bInstall, _In_opt_ LPCWSTR pszCmdLine)
{
HRESULT hr = E_FAIL;
static const wchar_t szUserSwitch[] = L"user";
if (pszCmdLine != NULL)
{
if (_wcsnicmp(pszCmdLine, szUserSwitch, _countof(szUserSwitch)) == 0)
{
ATL::AtlSetPerUserRegistration(true);
}
}
if (bInstall)
{
hr = DllRegisterServer();
if (FAILED(hr))
{
DllUnregisterServer();
}
}
else
{
hr = DllUnregisterServer();
}
return hr;
}
To register per-user you can use "regsvr32 /i:user MyAtlProject.dll". You are free to choose the registration you want, there are no "poor" and "good" methods - you just have options to choose from.
It is now possible to call AtlSetPerUserRegistration to specify whether the registration is done to HKEY_LOCAL_MACHINE or HKEY_CURRENT_USER without having to modify .rgs files.

How to load a dll in an AppDomain with different NET version

I have some third party assembly which was build using Net 2.0. Although I can of course reference that assembly in net 4, running the app results in all kinds of strange errors.
So I though loading it in a separate application domain will fix the problem but it does not. I assume it is still executed using the Net 4 runtime. Is there any way to force execution of an application domain in a different net version ?
I use CreateInstanceFromAndUnwrap and than call the proxy.
Thanks for any help
Joe
You can use the supportedRuntime configuration element to set the CLR version of an AppDomain. If you do not want to add this to your app.config, you can add a second .config file that will be used during the construction of the new AppDomain. Have a look at AppDomainSetup.ConfigurationFile for more info.
Create AppDomain. And Create DomainManager:MarshalByRef object in new domain.
DomainManager Load Assembly To created(new) domain.
AppDomainSetup ads = new AppDomainSetup();
AppDomain managerAD = AppDomain.CreateDomain("AD1", null, ads);
Assembly asm = managerAD.Load(typeof(DomainManager).Assembly.FullName);
string typeName = typeof(DomainManager).FullName;
DomainManager manager = (DomainManager)managerAD.CreateInstanceAndUnwrap(asm.GetName().Name,
typeName);
public class DomainManager : MarshalByRefObject
{
public void GetAppDomain(string assemblyFileName)
{
Assembly asm2 = Assembly.LoadFrom(assemblyFileName);
Type neededType = asm2.GetType(<paste type>);
object instance = Activator.CreateInstance(procType);
}

Can I reference a DataContract and its proxy version from same class

I'm dipping my foot into WCF and am trying to make a simple test project that can both consume the service as a service and also directly instantiate it's classes and work with them.
I had an earlier working version where data passed was just primitive types. However, when I attempted to convert to using data contracts, I'm getting conflicts in whether it's referencing the proxy-declared version of the contract or the version from the project itself.
Question: Is it possible to do this and if so, how would I resolve the data contract references?
private void Test()
{
MyService fssDirect = new MyService(); // direct instantiation
MyServiceClient fssService = new MyServiceClient(); // Service proxy
ClientCredentialsContract Client = new ClientCredentialsContract();
ResponseDataContract Result = new ResponseDataContract();
if (CallAsService)
{
Result = fssService.Create(Client, Request);
}
else
{
Result = fssDirect.Create(Client, Request);
}
}
In the above, any reference to the RequestDataContract and ClientCredentialsContract types indicates
Warning: The type 'MyContracts.RequestDataContract' in 'C:...\Test\MyServiceProxy.cs' conflicts with the imported type 'MyContracts.RequestDataContract' in 'C:...\MyService\bin\Debug\Contracts.dll'. Using the type defined in 'C:...\Test\MyServiceProxy.cs'.
(Names changed and paths shortened to protect the innocent)
Thanks,
John
When you create the proxy for your service, try referencing the assembly which contains the data contracts (if using "Add Service Reference", go to the advanced options, select "reuse types in referenced assemblies"; if using svcutil, use the /r argument). This way the tool won't generate the data contracts and you won't have the conflicts.

WCF Service Reference Will not compile

I currently have a simple WCF service with the following operation:
[OperationContract]
int InsertApplication(Application application);
The Application Parameter is defined as follows:
[DataContract]
public class Application
{
int test = 0;
[DataMember]
public int Test
{
get { return test; }
set { test = value; }
}
}
This service is being consumed within a Windows service with a namespace SpringstoneColoAgent. I added a service reference with no problems called OfficeInternalService. The code that calls the service method is as follows:
Application application = new Application();//= ConvertToApp(app);
application.Test = 1;
int oracleID = client.InsertApplication(application);
However, visual studio is telling me that 'application' is an invalid parameter. On further research I try to build anyway. I get a bunch of errors pointing to the Reference.cs file. Looking at this file I determine all the errors revolve around code that uses the following:
SpringstoneColoAgent.OfficeInternalService._
Where anything it is trying to reference under the service reference name is incorrect. So for example this code is giving an error:
[System.ServiceModel.OperationContractAttribute(Action="http://tempuri.org/IOfficeInternalService/InsertApplication", ReplyAction="http://tempuri.org/IOfficeInternalService/InsertApplicationResponse")]
int InsertApplication(SpringstoneColoAgent.OfficeInternalService.Application application);
If I do not fully qualify the namespace and remove SpringstoneColoAgent.OfficeInternalService. so that the code looks like this:
[System.ServiceModel.OperationContractAttribute(Action="http://tempuri.org/IOfficeInternalService/InsertApplication", ReplyAction="http://tempuri.org/IOfficeInternalService/InsertApplicationResponse")]
int InsertApplication(Application application);
This will fix the error. I repeated this everywhere I could find the error and everything compiled fine. The downside is that everytime I make a change to the WCF service and need to update the service reference it loses these changes and I have to go back and change them.
I'm guessing I am missing something here and was curious if anyone had any direction or has run into a similar situation.
Thanks for any advice!
The Application class is a known .NET type: http://msdn.microsoft.com/en-us/library/system.windows.forms.application.aspx. Try to work with a different class name to avoid name clashes.
Also try to avoid the int private member in the datacontract. Because it is not a datamember, it is not exposed in the WSDL and a generated proxy class on the client side does not know this private member. This can also cause problems.