Friday, July 31, 2009

DllRegisterServer w/o regsvr32

I have a need to register some COM DLLs from a C# program. Every example I found on the web used regsvr32 to perform the registration. This didn't make much sense to me as all regsvr32 does is call DllRegisterServer() exported in the DLL. So I worked out how to do the same from C# using P/Inovke and System.Runtime.InteropServices.

And here it is for you to use. This is a small sample but it compiles and registers the DLLs that are listed on the command line. Be sure to compile with the /unsafe switch. Also, I have more error checking and reporting in my application which I removed from this sample. Enjoy!

   1:  using System;
   2:  using System.Runtime.InteropServices;
   3:   
   4:  public class RegisterDll
   5:  {
   6:      unsafe internal delegate UInt32 DllRegisterServer();
   7:      
   8:      public static void Main(string[] args)
   9:      {
  10:          foreach (string file in args)
  11:          {
  12:              Console.WriteLine("Registering {0}", file);
  13:              RegisterCOMDll(file);
  14:          }
  15:   
  16:          Console.WriteLine("Done!");
  17:      }
  18:   
  19:      private static void RegisterCOMDll(string file)
  20:      {
  21:          IntPtr library = LoadLibraryEx(file, IntPtr.Zero, 0);
  22:          if (library == IntPtr.Zero)
  23:          {
  24:              string msg = "Unable to load '" + file + "' error is " + Marshal.GetLastWin32Error().ToString();
  25:              throw new ApplicationException(msg);
  26:          }
  27:   
  28:          IntPtr proc = GetProcAddress(library, "DllRegisterServer");
  29:          if (proc == IntPtr.Zero)
  30:          {
  31:              int err = Marshal.GetLastWin32Error();
  32:              string msg = "Unable to load 'DllRegisterServer' from '" + file + "' error is " + err.ToString();
  33:              throw new ApplicationException(msg);
  34:          }
  35:   
  36:          DllRegisterServer drs = (DllRegisterServer)Marshal.GetDelegateForFunctionPointer(proc, typeof(DllRegisterServer));
  37:          UInt32 result = drs();
  38:          if (result != 0)
  39:          {
  40:              string msg = "Error " + result.ToString() + " returned from DllRegisterServer in " + file;
  41:              throw new ApplicationException(msg);
  42:          }
  43:   
  44:          FreeLibrary(library);
  45:      }
  46:   
  47:      [Flags]
  48:      public enum LoadLibraryFlags : uint
  49:      {
  50:          DONT_RESOLVE_DLL_REFERENCES = 0x00000001,
  51:          LOAD_IGNORE_CODE_AUTHZ_LEVEL = 0x00000010,
  52:          LOAD_LIBRARY_AS_DATAFILE = 0x00000002,
  53:          LOAD_WITH_ALTERED_SEARCH_PATH = 0x00000008,
  54:      }
  55:   
  56:      [DllImport("kernel32.dll", EntryPoint = "FreeLibrary", SetLastError = true)]
  57:      public static extern bool FreeLibrary(IntPtr hModule);
  58:   
  59:      [DllImport("kernel32.dll", EntryPoint = "LoadLibraryExW", CharSet = CharSet.Unicode, SetLastError = true)]
  60:      public static extern IntPtr LoadLibraryEx(
  61:          [MarshalAs(UnmanagedType.LPWStr)] string lpFileName,
  62:          IntPtr hFile,
  63:          [MarshalAs(UnmanagedType.U4)] LoadLibraryFlags dwFlags);
  64:   
  65:      [DllImport("kernel32.dll", CharSet = CharSet.Ansi, EntryPoint = "GetProcAddress", ExactSpelling = true, SetLastError = true)]
  66:      public static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName);
  67:  }

13 comments:

  1. I get the error:
    Unable to load 'c:\wkhtmltoxcom.dll' error is 126
    Even though there is a file called wkhtmltoxcom.dll on C: in the root

    ReplyDelete
  2. Do you know if there is any other way of calling a com dll from dotNet than having to call regsvr32 first.
    I've been looking at the DllImport attribute, can you use that with dll's that are not registred with regsvr32.

    The reason I ask is because I'm trying to run a COM dll from an Asp.Net website, that's hosted on a shared server, where I don't have permission to do a regsvr32. I need an xcopy deploy method for the com dll. Is that at all possible?

    ReplyDelete
  3. You can't call any COM object without configuring the interface in the registry. regsvr32 is just a shell that calls DllRegisterServer() and DllUnregisterServer() in the DLL for you.

    If you are creating the COM object in code you control and passing it to another module you could do all the low-level work yourself to construct the instance. Or you could rework your code to use a regular DLL interface and not a COM object.

    Error 126 is "module not found" error. Is C:\ in your path? Are all the dependencies your DLL needs also in the path?

    ReplyDelete
  4. I apologise for my ignorance. But how do I check if the dll file is in my "path".
    I originally påt it in the /Bin folder of my ASP.Net website, that gave the same error. As far as I have gathered. As long as I call from a code behind .cs file, that code gets put into a dll that's placed in the Bin folder right. Is it in my path then?
    I've tried with RegisterCOMDll(HttpContext.Current.Server.MapPath("wkhtmltopdf/") + "" + "wkhtmltoxcom.dll")
    and
    RegisterCOMDll("wkhtmltoxcom.dll")

    Both give the same error.
    I've put your code in App_Code and made RegisterCOMDll public and I call it directly.

    ReplyDelete
  5. wkhtmltoxcom.dll has one dependency on wkhtmltox0.dll that I've also placed in the Bin folder.

    ReplyDelete
  6. I don't work with ASP.NET and don't know what it uses for the executable path. You can look up the documentation for LoadLibrary() to see where it searches to find DLLs to load.

    ReplyDelete
  7. I added this to your class
    [DllImport("kernel32.dll", EntryPoint = "SetDllDirectory", CharSet = CharSet.Unicode, SetLastError = true)]
    public static extern IntPtr SetDllDirectory(
    [MarshalAs(UnmanagedType.LPTStr)] string lpPathName);

    and called it with the directory my dll i located in.
    Now I get the error Error 2147942405 returned from DllRegisterServer in wkhtmltoxcom.dll

    Meaning I don't have sufficient rights to register in COM.

    Ahh well, It was worth a try.

    Thank you so much for your help. I guess i can modify the way you call DllRegisterServer into a way I can call a method in my dll, without the dll beeing registered with com, right?

    ReplyDelete
  8. Thanks for the tip about SetDllDirectory().

    Yes, you should be able to use P/Invoke wrappers around LoadLibrary(), GetProcAddress() and FreeLibrary() to call functions inside DLLs.

    ReplyDelete
  9. This comment has been removed by the author.

    ReplyDelete
  10. Accidentally deleted the post I wrote earlier.
    I have a similar problem for components written in .Net and compiled for COM interop.
    Is there any alternative for the DllRegisterServer() function for these .Net created components?
    I do not want to use regasm.exe

    ReplyDelete
  11. I believe you can use System.Runtime.InteropServices.RegistrationServices, but I have only read the documentation. I haven't used it.

    ReplyDelete
  12. Good post, thanks for sharing. I did it, but I got an error. There was a problem with the system files. I replaced the dll file http://fix4dll.com/mfc100u_dll. If there are such an error, suggest to change the dll files at once and everything will go smoothly..

    ReplyDelete