Saturday, July 14, 2007

Calling Dot Net Components from PowerBuilder via COM wrappers - Redux

Back in August of 2006, I wrote an entry about calling .Net components from PowerBuilder using COM wrappers (i.e., CCW). Since I was basing it on a registry entry approach still, the technique demonstrated there required the component to be added to the GAC, which required (in addition to having it compiled as a COM-visible assembly) that we needed to create a strong name and sign the assembly.



Well, you don't always have access to the GAC or the registry of the machine that you need to deploy your application to. Well, fortunately we have some options. Beginning with Windows XP and Windows Server 2003, the Microsoft operating systems allow the use of manifest files rather than registry entries for loading COM components. It not only works for regular COM components, but for .Net components that have been exposed as COM-visible. The only difference is how the manifest file is structured.

If you want further information on the details, I'd recommend the following resources:

How to: Configure .NET-Based Components for Registration-Free Activation

Registration-Free Activation of .NET-Based Components: A Walkthrough

Simple COM server registration

In particular, I'd recommend the following tool, which generates one of the required manifest files for you:

Genman32 – A tool to generate Sxs manifest for managed assembly for Registration Free COM/.Net Interop

We actually need two manifest files. We'll only deploy one because the other one is going to be compiled into our .Net assembly.

Component Manifest

The Component Manifest is the first of the manifest files, and is the one that will get compiled into the .Net assembly. Actually Windows Server 2003 allows you to deploy it as a separate file alongside the assembly. However, since Windows XP requires it to be part of the assembly, and because it's just a better practice, we'll compile it into the assembly.

You can use the Genman32 utility referenced above to automatically create the file for you, or you can simply create it by hand. I'll assume the later in order to indicate what the different portions of the file do. A manifest is a text file in XML format, so you start off by creating an empty text file with the same name as the assembly except it ends in .manifest rather than .dll. The first two lines of the file are the standard xml file declaration and the standard declaration for a manifest file. That is followed by the assemblyIdentity that identified this assembly. It has at least two values, the name of the assembly and its version, as specified in the project settings.
DotNetSMTP.manifest file

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity
name="DotNetSMTP"
version="0.0.0.0"
/>
<clrClass
clsid="{4599956A-2686-3D5F-8F14-9E7F45832B6C}"
name="DotNetSMTP.DotNetSMTP"
progid="DotNetSMTP.DotNetSMTP"
threadingModel="Both"
runtimeVersion="v2.0.50727">
</clrClass>
<file name="DotNetSMTP.dll">
</file>
</assembly>

The next section of the file is where we associate the CLSID, PROGID and ThreadingModel information that we would normally provide through registry entires. CLSID must match the GUID value that was used in the assembly. Because we're working within the clrClass tag, we know that this is a managed .Net based COM component. You can use the Registration Free Activation approach with unmanaged code as well, but there is a different tag that the information is provided in for those components.

Note that as with all XML, the tag names are case sensitive. One minor mistake in either of the manifest files will result in an error message at runtime, often "This application has failed to start because the application configuration is incorrect. Reinstalling the application may fix this problem." Fortunately, more information about the specific problem is recorded in the System event log under a "SideBySide" source. For example, if I entered the assemblyIdentity tag as assemblyidentity (all lower case), the following appears in the event log:
Syntax error in manifest or policy file "dotnetinteropsmtp.exe.Manifest" on line 7. The element assemblyidentity appears as a child of element urn:schemas-microsoft-com:asm.v1^assembly which is not supported by this version of Windows.

Now that we have a manifest file for the assembly, we need to compile it into the assembly. To do that, we need to first create a resource file that references the manifest file. So create another empty text file, except that this one has a .rc extension. You then add one single line to that file in the form of "RT_MANIFEST 1 " followed by the name of the manifest file:
DotNetSMTP.rc file

RT_MANIFEST 1 DotNetSMTP.manifest

You can add the resource file to your VS project as an existing file, but that step is not required.

Now we need to compile the resource file. We do that with the .Net Resource Compiler (rc.ext), using the following at a command prompt:
rc DotNetSMTP.rc

The result is a file called DotNetSMTP.res which we need to pass to the .Net C# compiler using the /win32res option to instruct it to add it to the assembly. Unfortunately, there's no place in the VS IDE to specify this, so what we do instead is create a one line batch file that does the compile for us. Simply create a build.cmd file with the following command in it:
csc /t:library /out:<directory>\DotNetSMTP.dll /win32res:DotNetSMTP.res DotNetSMTP.cs

The <directory> is not literal. You would put the directory that you want the compiled assembly stored in that place (e.g., bin/Debug).

Note that the Genman32 is not only capable of generating the manifest file for you, it can also embed it directly in the assembly for you as well.

Application Manifest

Now that we have all the COM information that we need to access the component, we still need to tell the application to look in the assembly for it. To do that we use an Application Manifest file. Once again it's a text file in XML format, and begins with the same two initial lines.
Application Manifest file

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity type = "win32"
name = "dotnetinteropsmtp"
version = "1.0.0.1"
processorArchitecture="x86"
/>
<dependency>
<dependentAssembly>
<assemblyIdentity
name="DotNetSMTP"
version="0.0.0.0"
/>
</dependentAssembly>
</dependency>
</assembly>

The assemblyIdentity section now identifies our application. Note that if you want to be able to test this from the PowerBuilder IDE, you would need to create such a manifest file for it (or modify the already existing one). That is followed by a dependency section, and within that a dependentAssembly section. Note the information within the dependentAssembly section must match the information in the Component Manifest file, or the application will not be able to locate the component.

That's essentially it. At this point you can place the DotNetSMTP.dll file and the Application Manifest file in the same directory as the application itself. Absolutely no changes are needed to the PowerBuilder code (or to the VS code for that matter, except for removing the signing information from the project options). When PowerBuilder attempts to create the COM component (the CCW), the Windows operating system will note that there is a dependent assembly defined in the application manifest and attempt to determine if that assembly is the one needed to instantiate the component. Since it is, it then uses the information in the manifest file embedded in the assembly to do the activation of the component.

No comments: