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.
Can we able to debug the C# code and C++ code (server and client) in VS.Net 2005 while installing?
ReplyDeleteHi Chris,
ReplyDeleteThat 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!
Hii,
ReplyDeleteI have issue with this as following
I have used VC++ for my setup. I had called the function during the setup, which is in my VC++ code.
The function is as followed
#pragma unmanaged
#include "stdafx.h"
#include "MSI_Logging.h"
#include "stdafx.h"
#using
#include
#include
#include
using namespace System::IO;
using namespace System::Net;
using namespace System::Net::NetworkInformation;
using namespace System::Text;
using namespace System;
using namespace System::Collections;
using namespace System::Collections::Specialized;
using namespace System::Security::Cryptography;
BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved )
{
return TRUE;
}
UINT __stdcall SampleFunction2 ( MSIHANDLE hModule )
{
MessageBox(NULL, "Hello world", "CodeProject.com", MB_OK);
array ^initVectorBytes = System::Text::Encoding::ASCII->GetBytes("@1B2c3D4e5F6g7H8");
array ^saltValueBytes = System::Text::Encoding::ASCII->GetBytes("s@1tValue");
array ^cipherTextBytes = Convert::FromBase64String("EMf/6yaltP7MXPoRo+XF6nwe3M0dzobeXY9UpSoSPTM=");
PasswordDeriveBytes^ password = gcnew PasswordDeriveBytes ("BFEBFBFF0001067A", saltValueBytes,"MD5",9);
array ^keyBytes = password->GetBytes(192 / 8);
RijndaelManaged^ symmetricKey = gcnew RijndaelManaged;
symmetricKey->Mode = CipherMode::CBC;
ICryptoTransform^ decryptor;
decryptor=symmetricKey->CreateDecryptor(keyBytes,initVectorBytes);
MemoryStream^ memoryStream = gcnew MemoryStream(cipherTextBytes);
CryptoStream^ cryptoStream = gcnew CryptoStream(memoryStream,decryptor,CryptoStreamMode::Read);
array ^plainTextBytes;
plainTextBytes = gcnew array(cipherTextBytes->Length);
int decryptedByteCount;
decryptedByteCount = cryptoStream->Read(plainTextBytes,0,plainTextBytes->Length);
memoryStream->Close();
cryptoStream->Close();
String^ plainText;
plainText = Encoding::UTF8->GetString(plainTextBytes,0,decryptedByteCount);
return ERROR_SUCCESS;
}
This is is the function which I called during my setup. I refer following to call this function
http://www.codeproject.com/KB/install/msicustomaction.aspx
When I am run this setup I got following error
"Attemp to use MSIL code from this assembly during native code initialization.
This indicates a bug in your application. It is most likely the result of calling an MSIL-compiled (/clr) function from a native constructor or from DLLMain"
What should I do?
Thanks,
Ankit
This thread is completely OBE by the launch of WiX's DTF solution. I would investigate that and forget about the mixed mode C++ stuff.
ReplyDelete