You can host the CLR from native code, so theoretically, you
could start an appdomain from a type 1 CA and load and run an assembly from the
hosted appdomain. But that's only the start; you'd still have to marshal between
the two layers to give the managed code access to thenative-code MSI API. It wouldn't be a weekend job.
First let's look at the client side code:
#using <mscorlib.dll>
#using <system.dll>
#include "StdAfx.h"
using namespace System;
using namespace System::Windows::Forms;
using namespace System::Reflection;
extern "C" UINT __stdcall Test ( MSIHANDLE hMSI )
{
int _hMSI = hMSI;
array< int ^>^ args = { _hMSI };
Type^ typeCA = Assembly::LoadFile("D:\\ClassLibrary1.dll" )->GetType("ClassLibrary1.TestClass");
Object^ objectCA = System::Activator::CreateInstance(typeCA);
typeCA->InvokeMember("TestMethod",BindingFlags::InvokeMethod, Type::DefaultBinder,objectCA,args);
return ERROR_SUCCESS;
}
Now let's look at the part of our C# code that will PInvoke to make our MSI API calls:
public class Session : System.MarshalByRefObject
{
private IntPtr
_handle;
[DllImport("msi.dll", CharSet =
CharSet.Unicode)]
static extern int MsiGetProperty(IntPtr handle, string
szName,
[Out] StringBuilder szValueBuf, ref int pchValueBuf);[DllImport("msi.dll", CharSet = CharSet.Unicode)]
static extern int
MsiSetProperty(IntPtr handle, string szName, string szValue);public string GetProperty(string propertyName)
{
StringBuilder
propertyValue = new StringBuilder(1024);
int size = 1024;
MsiGetProperty(
_handle, propertyName, propertyValue, ref size);
return
propertyValue.ToString();
}public void SetProperty(string propertyName, string
propertyValue)
{
MsiSetProperty(_handle, propertyName,
propertyValue);
}public void SetHandle( long handle )
{
_handle = new
IntPtr(handle);
}
}
Basically it's a class that wraps up MsiGetProperty() and MsiSetProperty() and exposes them as string GetProperty( string ) and void SetProperty( string, string ). Just instantiate the class, call the SetHandle() method passing it the MSI handle passed by the C++ code and your rolling. Let's see it in action:
using System;
using System.Runtime.InteropServices;
using
System.Text;
using System.Windows.Forms;
namespace
ClassLibrary1
{
public class TestClass
{
public void TestMethod(int
handle)
{
Session session = new Session();
session.SetHandle( handle
);
MessageBox.Show(session.GetProperty("ProductName"));
session.SetProperty("ProductName",
"Test");
}
}
To implement it, we merely wire the C++ code up as a Type 1 CA and store both DLL's in the binary table. The C++ could query the table and extract the record to a temp path for the reflection but I didn't need to code that since InstallShield has a built in pattern for extracting and cleaning up support files.
Speaking of InstallShield, unfortunatly the server side class would not work with CoCreateObjectDotNet(). They must be starting the CLR/AppDomain up out of process. InstallShield 12.5 maybe? Hint, hint.
Also this is obviously a prototype. I will be working on creating a complete implementation where the entire API is exposed and the client function is data driven so that it's reusable.
2 comments:
Can we able to debug the C# code and C++ code (server and client) in VS.Net 2005 while installing?
Hi Chris,
That is what I was exactly looking for when i stumbled upon your blog and found an answer to my solution.
I did follow the steps as you metnioned... except that am using one MsiInterop class which I downloaded from CodeProject rather than creating my own. However, I always get an "Invalid Handle Error" when the .NET library tries to access the MSI database using the MSIHandle passed to it. I have verified that the value of Msi Handle in C# is same as in C++.
Do you have any idea what could go wrong? would be glad if you could help me with this!
Post a Comment