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

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.

Related

Howto tell PowerBuilder to pass options to a JVM when starting?

What I want to do?
I want to create and consume java objects in PowerBuilder and call methods on it. This should happen with less overhead possible.
I do not want to consume java webservices!
So I've a working sample in which I can create a java object, call a method on this object and output the result from the called method.
Everything is working as expected. I'm using Java 1.8.0_31.
But now I want to attach my java IDE (IntelliJ) to the running JVM (started by PowerBuilder) to debug the java code which gets called by PowerBuilder.
And now my question.
How do I tell PowerBuilder to add special options when starting the JVM?
In special I want to add the following option(s) in some way:
-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005
The JVM is created like following:
LONG ll_result
inv_java = CREATE JavaVM
ll_result = inv_java.CreateJavaVM("C:\Development\tms java\pbJavaTest", FALSE)
CHOOSE CASE ll_result
CASE 1
CASE 0
CASE -1
MessageBox ( "", "jvm.dll was not found in the classpath.")
CASE -2
MessageBox ( "", "pbejbclient90.jar file was not found." )
CASE ELSE
MessageBox ( "", "Unknown result (" + String (ll_result ) +")" )
END CHOOSE
In the PowerBuilder help I found something about overriding the static registry classpath. There is something written about custom properties which sounds like what I'm looking for.
But there's no example on how to add JVM options to override default behavior.
Does anyone have a clue on how to tell PowerBuilder to use my options?
Or does anyone have any advice which could guide me in the right direction?
Update 1
I found an old post which solved my initial issue.
If someone else want to know how it works take a look at this post:
http://nntp-archive.sybase.com/nntp-archive/action/article/%3C46262213.6742.1681692777#sybase.com%3E
Hi, you need to set some windows registry entries.
Under HKEY_LOCAL_MACHINE\SOFTWARE\Sybase\Powerbuilder\9.0\Java, there
are two folders: PBIDEConfig and PBRTConfig. The first one is used when
you run your application from within the IDE, and the latter is used
when you run your compiled application. Those two folders can have
PBJVMconfig and PBJVMprops folders within them.
PBJVMconfig is for JVM configuration options such as -Xms. You have to
specify incremental key values starting from "0" by one, and one special
key "Count" to tell Powerbuilder how many options exists to enumerate.
PBJVMprops is for all -D options. You do not need to specify -D for
PBJVMProps, just the name of the property and its value, and as many
properties as you wish.
Let me give some examples:
Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE\SOFTWARE\Sybase\PowerBuilder\9.0\Java\PBIDEConfig\PBJVMprops]
"java.security.auth.login.config"="auth.conf"
"user.language"="en"
[HKEY_LOCAL_MACHINE\SOFTWARE\Sybase\PowerBuilder\9.0\Java\PBRTConfig\PBJVMconfig]
"0"="-client"
"1"="-Xms128m"
"2"="-Xmx512m"
"Count"="3"
[HKEY_LOCAL_MACHINE\SOFTWARE\Sybase\PowerBuilder\9.0\Java\PBRTConfig\PBJVMprops]
"java.security.auth.login.config"="auth.conf"
"user.language"="en"
Regards,
Gokhan Demir
But now there's another issue...
PB isn't able to create EJB Proxies for my sample class which is really simple with java 1.8.0_31. They were created with the default version, which is 1.6.0_24.
public class Simple
{
public Simple()
{
}
public static String getValue()
{
return "blubber";
}
public int getInt32Value()
{
return 123456;
}
public double getDoubleVaue()
{
return 123.123;
}
public static void main(String[] args)
{
System.out.println(Simple.getValue());
}
}
The error is the following. :D
---------- Deploy: Deploy of project p_genapp_ejbclientproxy (15:35:18)
Retrieving PowerBuilder Proxies from EJB...
Generation Errors: Error: class not found: (
Deployment Error: No files returned for package/component 'Simple'. Error code: Unknown. Proxy was not created.
Done.
---------- Finished Deploy of project p_genapp_ejbclientproxy (15:35:19)
So the whole way isn't a option because we do not want to change the JAVA settings in PB back and forth just to generate new EJB Proxies for changed JAVA objects in the future...
So one option to test will be creating COM wrappers for JAVA classes to use them in PB...

Using Kentico API from LINQPad is throwing an exception

I am trying to call Kentico API from LINQPad, but getting the following exception:
[AbstractProvider.GetProvider]: The object type 'cms.document' is missing the provider type configuration
My code is:
void Main()
{
var pages = DocumentHelper.GetDocuments("CMS.MenuItem").Path("/", PathTypeEnum.Children);
pages.Dump();
}
Note: I tested the code from Visual Studio, it works, but not from LINQPad.
The problem is that during the initial discovery Kentico looks only at the following paths:
AppDomain.CurrentDomain.BaseDirectory
AppDomain.CurrentDomain.RelativeSearchPath
Which in case of LINQPad are C:\Program Files (x86)\LINQPad4\ and null. Therefore the providers do not get resolved.
I've tried running the code in a new AppDomain but it doesn't seem to work in LINQPad. I suggest submitting this to Kentico as an idea or an issue.
A workaround to this would be copying the LINQPad executable to a location of Kentico DLLs - e.g. C:\inetpub\wwwroot\Kentico82\Lib. That works just fine.
Update (thx to Joe Albahari):
If you wrap your code in this:
var appDomain = Util.CreateAppDomain ("AD", null, new AppDomainSetup
{
PrivateBinPath = #"C:\inetpub\wwwroot\Kentico82\CMS\bin",
});
appDomain.DoCallBack(() => { /* your code */ });
you'll be able to execute it. However, you can't Dump() it to the output window. But you can write it to a text file for example. If you experience the following error:
FileNotFoundException: Could not load file or assembly 'LINQPad, Version=1.0.0.0, Culture=neutral, PublicKeyToken=21353812cd2a2db5' or one of its dependencies. The system cannot find the file specified.
Go to Edit -> Preferences -> Advanced -> Run each query in its own process and turn it off.

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);
}

Specify path of a file in a Class Library, a method in that class library called from console application

I have a ClassLibrary Project which is my business layer - Demo.Business
For this class library,
I have folder in the class library as below
TRT
|
TRT.cs
TRTDetails.cs
TRTFiles(Folder)
|
**TRTFile.txt**
In TRT.cs class i have a method
public void UpdateDetails()
{
var typeSeq = from val in TRTDetails.Read**(#"TRTFile.txt")**
}
Now i have added reference of this class library "Demo.Business.dll" to my console application - "DemoProcess.exe".
In the above Console Application I am calling the method "UpdateDetails()" as follows:
public void CallMethod()
{
UpdateDetails();
}
How can I specify the path of the file "TRTFile.txt" in the method "UpdateDetails()" in class library?
I tried using System.IO.Path.GetDirectoryName
(System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase)
Which always gives the path of executing application.
i.e
C:\\Projects\\Demo.Process\\bin\\Debug
How can i get the path as
C:\\Projects\\Demo.Business\\TRT\\TRTFiles............
There are two ways to do this:
I.
This can be done using relative path. If your's dll is also situated in Debug folder, the following path = #"..\..\..\Demo.Business\TRT\TRTFiles\" will do the work.
First ..\ will get us to C:\\Projects\\Demo.Process\\bin\\.
Second ..\ will get us to C:\\Projects\\Demo.Process\\.
Third ..\ will get us to C:\\Projects\\.
II.
Or by using classes used for writing visual studio extensions (Extensibility, EnvDTE namespaces, etc.), they provide functionality to get all information about your project and it's content. But it's complicated.