Calling Managed Code From Unmanaged Code

One significant task for developers (including myself) is doing some kind of automation of Microsoft Office. So, I will concentrate on the opposing side of the problem - how to use managed assembly from MS Office or VB script. In this article, I will discuss how to call managed code from unmanaged code.

It is a good idea to check your .NET Framework SDK documentation, especially, Interoperating with Unmanaged Code. For a start, we will first check the managed side of the story. To expose the managed types, properties, fields, or events to COM, they must be public. Types can't be abstract and they must have a default constructor. It's recommended to define interface explicitly but it is not necessary. The tools provided with .NET Platform are capable of creating this for you. Let's try it.

  1. using System;  
  2. public class T {  
  3.     public string A(string s) {  
  4.         return s + " from C#";  
  5.     }  
  6. }  
Copy and paste the code in the Notepad and save it as str.cs. Open the command line and execute the following.

csc /t:library str.cs
tlbexp str.dll /out:str.tlb
regasm str.dll

 

  • Copy str.dll and str.tlb to MS Office directory. Typically, it is "C:\Program Files\Microsoft Office\Office".
  • Now, open your favorite MS Office application, i.e., Excel and open Visual Basic Editor (Alt + F11).
  • To check the early binding, add a reference to your library, browse to and select str.tlb.
  • Insert a new module into the project and paste the following code in the code editor.

 

  1. Sub Test()  
  2. Dim o As New T  
  3. MsgBox(o.A("Hello"), , "VBA test")  
  4. End Sub  
  5. Sub LBTest()  
  6. Dim o  
  7. o = CreateObject("T")  
  8. MsgBox(o.A("Hello"), , "VBA late binding test")  
  9. End Sub  

If you like to do the same from VB script, copy str.dll and str.tlb to "C:\WINNT\system32" because wscript.exe will need these to execute your script. Create a new .VBS file and paste into it the following.

  1. Dim o  
  2. Set o = CreateObject("T")  
  3. MsgBox o.A("Hello"), , "VBS test"  

When you are done with testing, don't forget to unregister your str.dll. To do that, execute from the command line "regasm str.dll /unregister". Also, deleting str.dll and str.tlb copies from "C:\Program Files\Microsoft Office\Office" and "C:\WINNT\system32" is a good idea. But some developers will be interested to examine the closer details of what is in str.tlb.

They will discover the following.

  1. // Generated .IDL file (by the OLE/COM Object Viewer)  
  2. //   
  3. // typelib filename: str.tlb  
  4. [  
  5.     uuid(AD7B6A7C - 4 F96 - 3710 - B1AC - 5170E611 BA57),  
  6.     version(1.0),  
  7.     custom(90883 F05 - 3 D28 - 11 D2 - 8 F17 - 00 A0C9A6186D, str, Version = 0.0 .0 .0, Culture = neutral, PublicKeyToken = null)  
  8. ]  
  9. library str {  
  10.     // TLib : // TLib : Common Language Runtime Library : {BED7F4EA-1A96-11D2-8F08-00A0C9A6186D}  
  11.     importlib("mscorlib.tlb");  
  12.     // TLib : OLE Automation : {00020430-0000-0000-C000-000000000046}  
  13.     importlib("stdole2.tlb");  
  14.     // Forward declare all types defined in this typelib  
  15.     interface _T;  
  16.     [  
  17.         uuid(4996 A037 - 02 BD - 3839 - A363 - A9A321885D2C),  
  18.         version(1.0),  
  19.         custom(0 F21F359 - AB84 - 41E8 - 9 A78 - 36 D110E6D2F9, T)  
  20.     ]  
  21.     coclass T {  
  22.         [  
  23.             default  
  24.         ] interface _T;  
  25.         interface _Object;  
  26.     };  
  27.     [  
  28.         odl,  
  29.         uuid(742 F958A - 4 C27 - 399 F - 89 DD - 5368588 B63B7),  
  30.         hidden,  
  31.         dual,  
  32.         oleautomation,  
  33.         custom(0 F21F359 - AB84 - 41E8 - 9 A78 - 36 D110E6D2F9, T)  
  34.     ]  
  35.     interface _T: IDispatch {};  
  36. };  

What is wrong there? Our public string A method is nowhere to be seen. Also, the familiar methods belonging to the object are hidden.

Calling Managed Code From Unmanaged Code

That means the VBA code editor won't be able to help in to show methods and properties of the class. So what could be done to improve that kind of situation? The help comes from attributes. Change your C# code into this.

  1. using System;  
  2. using System.Runtime.InteropServices;  
  3. [ClassInterface(ClassInterfaceType.AutoDual)]  
  4. public class T {  
  5.     public string A(string s) {  
  6.         return s + " from C#";  
  7.     }  
  8. }  

Repeat the above-mentioned steps to compile. Get tlb and register library, copy str.dll and str.tlb to MS Office directory "C:\Program Files\Microsoft Office\Office" and try early binding. Everything will work fine.

Calling Managed Code From Unmanaged Code

Again, we will check what things look like in OLE/COM Object Viewer.

  1. // Generated .IDL file (by the OLE/COM Object Viewer)  
  2. //   
  3. // typelib filename: str.tlb  
  4. [  
  5.     uuid(AD7B6A7C - 4 F96 - 3710 - B1AC - 5170E611 BA57),  
  6.     version(1.0),  
  7.     custom(90883 F05 - 3 D28 - 11 D2 - 8 F17 - 00 A0C9A6186D, str, Version = 0.0 .0 .0, Culture = neutral, PublicKeyToken = null)  
  8. ]  
  9. library str {  
  10.     // TLib : // TLib : Common Language Runtime Library : {BED7F4EA-1A96-11D2-8F08-00A0C9A6186D}  
  11.     importlib("mscorlib.tlb");  
  12.     // TLib : OLE Automation : {00020430-0000-0000-C000-000000000046}  
  13.     importlib("stdole2.tlb");  
  14.     // Forward declare all types defined in this typelib  
  15.     interface _T;  
  16.     [  
  17.         uuid(4996 A037 - 02 BD - 3839 - A363 - A9A321885D2C),  
  18.         version(1.0),  
  19.         custom(0 F21F359 - AB84 - 41E8 - 9 A78 - 36 D110E6D2F9, T)  
  20.     ]  
  21.     coclass T {  
  22.         [  
  23.             default  
  24.         ] interface _T;  
  25.         interface _Object;  
  26.     };  
  27.     [  
  28.         odl,  
  29.         uuid(C145FF57 - 9 C23 - 355 C - B068 - BADECB60531B),  
  30.         hidden,  
  31.         dual,  
  32.         nonextensible,  
  33.         oleautomation,  
  34.         custom(0 F21F359 - AB84 - 41E8 - 9 A78 - 36 D110E6D2F9, T)  
  35.     ]  
  36.     interface _T: IDispatch {  
  37.         [id(00000000), propget,  
  38.             custom(54 FC8F55 - 38 DE - 4703 - 9 C4E - 250351302 B1C, 1)  
  39.         ]  
  40.         HRESULT ToString([out, retval] BSTR * pRetVal);  
  41.         [id(0x60020001)]  
  42.         HRESULT Equals(  
  43.             [ in ] VARIANT obj,  
  44.             [out, retval] VARIANT_BOOL * pRetVal);  
  45.         [id(0x60020002)]  
  46.         HRESULT GetHashCode([out, retval] long * pRetVal);  
  47.         [id(0x60020003)]  
  48.         HRESULT GetType([out, retval] _Type ** pRetVal);  
  49.         [id(0x60020004)]  
  50.         HRESULT A(  
  51.             [ in ] BSTR s,  
  52.             [out, retval] BSTR * pRetVal);  
  53.     };  
  54. };  
If we compare it with the old IDL, now our class T is described with interface _T like last time but there are all methods (some of them are inherited from object).

If you like, it's possible to do things slightly differently. You can generate tlb in one go using the following command.

regasm str.dll /tlb:str.tlb
 
Or, you can generate the registry file and do registration manually using regasm str.dll /regfile:str.reg
 
That looks like the following. 

REGEDIT4
[HKEY_CLASSES_ROOT\T]
@="T"
[HKEY_CLASSES_ROOT\T\CLSID]
@="{4996A037-02BD-3839-A363-A9A321885D2C}"
[HKEY_CLASSES_ROOT\CLSID\{4996A037-02BD-3839-A363-A9A321885D2C}]
@="T"
[HKEY_CLASSES_ROOT\CLSID\{4996A037-02BD-3839-A363-A9A321885D2C}\InprocServer32]
@="C:\WINNT\System32\mscoree.dll"
"ThreadingModel"="Both"
"Class"="T"
"Assembly"="str, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
"RuntimeVersion"="v1.0.3705"
[HKEY_CLASSES_ROOT\CLSID\{4996A037-02BD-3839-A363-A9A321885D2C}\ProgId]
@="T"
[HKEY_CLASSES_ROOT\CLSID\{4996A037-02BD-3839-A363-A9A321885D2C}\Implemented Categories\{62C8FE65-4EBB-45E7-B440-6E39B2CDBF29}]