Bundling an ActiveX DLL

D

David Jackson

Hello,

I'm writing a C# WinForms app which will be used to modify Microsoft Office
document properties - company is going through a bit of a clean-up exercise
before moving to Office 2007.

I'm using this: http://support.microsoft.com/kb/224351 which works
perfectly.

However, it is an ActiveX DLL which, of course, requires registering.

My boss has asked if there's any way that the whole thing can be delivered
as a single stand-alone executable rather than write an installer.

I've said that I don't believe it is, but that I'd ask in here to make
certain.

So, is it possible to include an ActiveX DLL in a C# app so that the user
doesn't need to register it directly? I understand about embedding binaries
and then extracting them at runtime - is that a possible solution?

Also, I understand that a .NET assembly can be given a COM wrapper - is the
reverse possible? I.e. can a COM object be wrapped in a .NET assembly?

Target OS will be 32-bit XP Pro or 32-bit Vista Business - all will have at
least .NET 3.0 installed.

Thank you,

DJ
 
M

Mark Rae [MVP]

My boss has asked if there's any way that the whole thing can be delivered
as a single stand-alone executable rather than write an installer.

I've said that I don't believe it is, but that I'd ask in here to make
certain.

Fairly simply, in fact... :)
So, is it possible to include an ActiveX DLL in a C# app so that the user
doesn't need to register it directly? I understand about embedding
binaries and then extracting them at runtime - is that a possible
solution?

Yep - code below. However, I should point out that I don't consider this a
particularly elegant, robust or desirable solution, but nevertheless it's
easy enough to do... The only problem you will have is that as soon as your
app makes its first call to the ActiveX DLL, it will "hook" it, so you're
unlikely to be able to unregister it and delete it as your app closes which
will mean that you'll have to delete it manually... Your suggestion to use
an installer is by far the better option...

Also, note that you will need to include the InterOp wrapper which your app
will create when it compiles...
Also, I understand that a .NET assembly can be given a COM wrapper - is
the reverse possible? I.e. can a COM object be wrapped in a .NET assembly?

Not as far as I know... Having said that, the VC++ code for DSOFile.dll is
readily available for download from the Microsoft website, so you could have
a bash at turning it into a native .NET assembly - I'm not nearly skilled
enough in VC++ to even attempt that... :)


// in your app's startup ------------------------
Stream objStream = null;
BinaryReader objBinaryReader = null;
FileStream objFileStream = null;
BinaryWriter objBinaryWriter = null;
byte[] abytBytes;

try
{
// extract the
DLL --------------------------------------------------------------------------
objStream =
Assembly.GetExecutingAssembly().GetManifestResourceStream("<MyAssemblyName>.dsofile.dll");
objBinaryReader = new BinaryReader(objStream);
objFileStream = new
FileStream(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) +
"\\dsofile.dll", FileMode.Create);
objBinaryWriter = new BinaryWriter(objFileStream);
abytBytes = new byte[objStream.Length];
objStream.Read(abytBytes, 0, abytBytes.Length);
objBinaryWriter.Write(abytBytes);
objBinaryReader.Close();
objBinaryWriter.Close();

// register the
DLL ----------------------------------------------------------------
using (Process objProcess = new Process())
{
objProcess.StartInfo.UseShellExecute = false;
objProcess.StartInfo.RedirectStandardOutput = false;
objProcess.StartInfo.RedirectStandardError = false;
objProcess.StartInfo.CreateNoWindow = true;
objProcess.StartInfo.FileName = "regsvr32.exe";
objProcess.StartInfo.Arguments = "dsofile.dll /s";
objProcess.Start();
objProcess.WaitForExit();
}

// extract the type
library -----------------------------------------------------------------
objStream =
Assembly.GetExecutingAssembly().GetManifestResourceStream("<MyAssemblyName>.Interop.DSOFile.dll");
objBinaryReader = new BinaryReader(objStream);
objFileStream = new
FileStream(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) +
"\\Interop.DSOFile.dll", FileMode.Create);
objBinaryWriter = new BinaryWriter(objFileStream);
abytBytes = new byte[objStream.Length];
objStream.Read(abytBytes, 0, abytBytes.Length);
objBinaryWriter.Write(abytBytes);
objBinaryReader.Close();
objBinaryWriter.Close();
#endif
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK,
MessageBoxIcon.Error);
}
finally
{
if (objBinaryWriter != null)
{
objBinaryWriter.Close();
objBinaryWriter = null;
}
if (objFileStream != null)
{
objFileStream.Close();
objFileStream.Dispose();
}
if (objBinaryReader != null)
{
objBinaryReader.Close();
objBinaryReader = null;
}
if (objStream != null)
{
objStream.Close();
objStream.Dispose();
}
}


// in your app's exit routine -------------

try
{
using (Process objProcess = new Process())
{
objProcess.StartInfo.UseShellExecute = true;
objProcess.StartInfo.RedirectStandardOutput = false;
objProcess.StartInfo.RedirectStandardError = false;
objProcess.StartInfo.CreateNoWindow = true;
objProcess.StartInfo.FileName = "regsvr32.exe";
objProcess.StartInfo.Arguments = "dsofile.dll /u /s";
objProcess.Start();
objProcess.WaitForExit();
}
File.Delete(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)
+ "\\dsofile.dll");
File.Delete(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)
+ "\\Interop.DSOFile.dll");
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK,
MessageBoxIcon.Error);
}
 
N

Nicholas Paldino [.NET/C# MVP]

The problem with this (not Marc's solution, but doing this in general)
is that if you have more than one instance of the app running on the
machine, it could create problems with the registering/unregistering part.

It's better to really just register the component on the machine.


--
- Nicholas Paldino [.NET/C# MVP]
- (e-mail address removed)

Mark Rae said:
My boss has asked if there's any way that the whole thing can be
delivered as a single stand-alone executable rather than write an
installer.

I've said that I don't believe it is, but that I'd ask in here to make
certain.

Fairly simply, in fact... :)
So, is it possible to include an ActiveX DLL in a C# app so that the user
doesn't need to register it directly? I understand about embedding
binaries and then extracting them at runtime - is that a possible
solution?

Yep - code below. However, I should point out that I don't consider this a
particularly elegant, robust or desirable solution, but nevertheless it's
easy enough to do... The only problem you will have is that as soon as
your app makes its first call to the ActiveX DLL, it will "hook" it, so
you're unlikely to be able to unregister it and delete it as your app
closes which will mean that you'll have to delete it manually... Your
suggestion to use an installer is by far the better option...

Also, note that you will need to include the InterOp wrapper which your
app will create when it compiles...
Also, I understand that a .NET assembly can be given a COM wrapper - is
the reverse possible? I.e. can a COM object be wrapped in a .NET
assembly?

Not as far as I know... Having said that, the VC++ code for DSOFile.dll is
readily available for download from the Microsoft website, so you could
have a bash at turning it into a native .NET assembly - I'm not nearly
skilled enough in VC++ to even attempt that... :)


// in your app's startup ------------------------
Stream objStream = null;
BinaryReader objBinaryReader = null;
FileStream objFileStream = null;
BinaryWriter objBinaryWriter = null;
byte[] abytBytes;

try
{
// extract the
DLL --------------------------------------------------------------------------
objStream =
Assembly.GetExecutingAssembly().GetManifestResourceStream("<MyAssemblyName>.dsofile.dll");
objBinaryReader = new BinaryReader(objStream);
objFileStream = new
FileStream(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)
+ "\\dsofile.dll", FileMode.Create);
objBinaryWriter = new BinaryWriter(objFileStream);
abytBytes = new byte[objStream.Length];
objStream.Read(abytBytes, 0, abytBytes.Length);
objBinaryWriter.Write(abytBytes);
objBinaryReader.Close();
objBinaryWriter.Close();

// register the
DLL ----------------------------------------------------------------
using (Process objProcess = new Process())
{
objProcess.StartInfo.UseShellExecute = false;
objProcess.StartInfo.RedirectStandardOutput = false;
objProcess.StartInfo.RedirectStandardError = false;
objProcess.StartInfo.CreateNoWindow = true;
objProcess.StartInfo.FileName = "regsvr32.exe";
objProcess.StartInfo.Arguments = "dsofile.dll /s";
objProcess.Start();
objProcess.WaitForExit();
}

// extract the type
library -----------------------------------------------------------------
objStream =
Assembly.GetExecutingAssembly().GetManifestResourceStream("<MyAssemblyName>.Interop.DSOFile.dll");
objBinaryReader = new BinaryReader(objStream);
objFileStream = new
FileStream(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)
+ "\\Interop.DSOFile.dll", FileMode.Create);
objBinaryWriter = new BinaryWriter(objFileStream);
abytBytes = new byte[objStream.Length];
objStream.Read(abytBytes, 0, abytBytes.Length);
objBinaryWriter.Write(abytBytes);
objBinaryReader.Close();
objBinaryWriter.Close();
#endif
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK,
MessageBoxIcon.Error);
}
finally
{
if (objBinaryWriter != null)
{
objBinaryWriter.Close();
objBinaryWriter = null;
}
if (objFileStream != null)
{
objFileStream.Close();
objFileStream.Dispose();
}
if (objBinaryReader != null)
{
objBinaryReader.Close();
objBinaryReader = null;
}
if (objStream != null)
{
objStream.Close();
objStream.Dispose();
}
}


// in your app's exit routine -------------

try
{
using (Process objProcess = new Process())
{
objProcess.StartInfo.UseShellExecute = true;
objProcess.StartInfo.RedirectStandardOutput = false;
objProcess.StartInfo.RedirectStandardError = false;
objProcess.StartInfo.CreateNoWindow = true;
objProcess.StartInfo.FileName = "regsvr32.exe";
objProcess.StartInfo.Arguments = "dsofile.dll /u /s";
objProcess.Start();
objProcess.WaitForExit();
}

File.Delete(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)
+ "\\dsofile.dll");

File.Delete(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)
+ "\\Interop.DSOFile.dll");
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK,
MessageBoxIcon.Error);
}
 
M

Mark Rae [MVP]

The problem with this (not Mark's solution, but doing this in general) is
that if you have more than one instance of the app running on the machine,
it could create problems with the registering/unregistering part.

And it needs to be run with elevated privileges on Vista, etc... In fact,
it's pretty messy altogether, which is why I hesitated from posting it in
the first place...
It's better to really just register the component on the machine.

Indeed.

AAMOI, do you think it would be possible to convert the raw VC++ code into a
native .NET assembly...?

I would imagine that Microsoft would already have done so if it were...
 
D

David Jackson

The problem with this (not Marc's solution, but doing this in general) is
that if you have more than one instance of the app running on the machine,
it could create problems with the registering/unregistering part.

It's better to really just register the component on the machine.

Thanks to you (and Mark) for your help with this
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top