Showing posts with label com. Show all posts
Showing posts with label com. Show all posts

Wednesday, December 6, 2017

How to get installed applicatio path for executbale in COM

Leave a Comment

I am trying to get installed location of all application using COM. I am able to get the display name of each application. But I am not able to get installed path of each application.

MY Code:

CComPtr<IShellItem> spPrinters;         CoInitialize(nullptr);         HRESULT hresult = ::SHCreateItemFromParsingName(L"::{26EE0668-A00A-44D7-9371-BEB064C98683}\\8\\"             L"::{7B81BE6A-CE2B-4676-A29E-EB907A5126C5}", nullptr, IID_PPV_ARGS(&spPrinters));         CComPtr<IEnumShellItems> spEnum;         spPrinters->BindToHandler(nullptr, BHID_EnumItems, IID_PPV_ARGS(&spEnum));         for (CComPtr<IShellItem> spProgram; spEnum->Next(1, &spProgram, nullptr) == S_OK; spProgram.Release())         {             CComHeapPtr<wchar_t> spszName;             spProgram->GetDisplayName(SIGDN_NORMALDISPLAY, &spszName);             CString cDisplayName = spszName; } 

Any idea how to get installed path from IEnumShellItems.

Thanks in adv.

1 Answers

Answers 1

Here is a piece of code that will dump this out. The child's IPropertyStore does not return these, I don't know why, so we have to use the old IShellFolder2::GetDetailsEx method with a special column id (which is the same as a PROPERTYKEY).

CComPtr<IShellItem> cpl; CComPtr<IShellFolder2> folder; CComPtr<IEnumShellItems> enumerator; PROPERTYKEY pkLocation; SHCreateItemFromParsingName(L"::{26EE0668-A00A-44D7-9371-BEB064C98683}\\8\\::{7B81BE6A-CE2B-4676-A29E-EB907A5126C5}", nullptr, IID_PPV_ARGS(&cpl));  // bind to IShellFolder cpl->BindToHandler(NULL, BHID_SFObject, IID_PPV_ARGS(&folder));  // bind to IEnumShellItems cpl->BindToHandler(NULL, BHID_EnumItems, IID_PPV_ARGS(&enumerator));  // get this property key's value PSGetPropertyKeyFromName(L"System.Software.InstallLocation", &pkLocation);  for (CComPtr<IShellItem> child; enumerator->Next(1, &child, nullptr) == S_OK; child.Release()) {     // get child's display name     CComHeapPtr<wchar_t> name;     child->GetDisplayName(SIGDN_NORMALDISPLAY, &name);     wprintf(L"%s\n", name);      // get child's PIDL     CComHeapPtr<ITEMIDLIST> pidl;     SHGetIDListFromObject(child, &pidl);      // the PIDL is absolute, we need the relative one (the last itemId in the list)     // get it's install location     CComVariant v;     if (SUCCEEDED(folder->GetDetailsEx(ILFindLastID(pidl), &pkLocation, &v)))     {         // it's a VT_BSTR         wprintf(L" %s\n", v.bstrVal);     } } 

Note it's using an undocumented System.Software.InstallLocation PROPERTYKEY. To find it I just dumped all columns with a code like this for each child:

    int iCol = 0;     do     {         SHCOLUMNID colId;         if (FAILED(folder->MapColumnToSCID(iCol, &colId)))             break; // last column          CComHeapPtr<wchar_t> name;         PSGetNameFromPropertyKey(colId, &name);          CComVariant v;         if (SUCCEEDED(folder->GetDetailsEx(ILFindLastID(pidl), &colId, &v)))         {             if (v.vt == VT_BSTR)             {                 wprintf(L" %s: %s\n", name, v.bstrVal);             }             else             {                 wprintf(L" %s vt: %i\n", name, v.vt);             }         }          iCol++;     } while (true); } 

PS: I've not added much error checking, but you should.

Read More

Tuesday, April 25, 2017

COM asynchronous call doesn't respect the message filter

Leave a Comment

I have an STA COM object that implements a custom interface. My custom interface has a custom proxy stub that was built from the code generated by the MIDL-compiler. I would like to be able to asynchronously make calls to the interface from other apartments. I'm finding that the synchronous interface calls respect the OLE message filter on the calling thread, but the asynchronous interface calls do not. This means that COM asynchronous calls cannot be used in a fire-and-forget manner if the calling apartment has a message filter that suggests retrying the call later.

Is this expected? Is there any way around this other than not using a message filter, not using fire-and-forget operations, or having a separate homegrown component just to manage fire-and-forget operations?

For the code below, MessageFilter is a simple, in-module implementation of IMessageFilter that routes calls to lambdas. If I do not use message filters, both the synchronous and asynchronous calls work fine. If I use the message filters shown below, the synchronous call works (after the main STA message filter stops returning SERVERCALL_RETRYLATER) but the asynchronous call immediately fails and never retries.

The main STA has a message filter that defers for some period of time.

// establish deferral time chrono::time_point<chrono::system_clock> defer_until = ...;  // create message filter auto message_filter = new MessageFilter; message_filter->AddRef(); message_filter->handle_incoming_call     = [defer_until](DWORD, HTASK, DWORD, LPINTERFACEINFO)       {           return chrono::high_resolution_clock::now() >= defer_until               ? SERVERCALL_ISHANDLED               : SERVERCALL_RETRYLATER;       };  // register message filter CoRegisterMessageFilter(message_filter, nullptr); 

Another STA sets up its own message filter to tell COM to retry.

// create message filter auto message_filter = new MessageFilter; message_filter->AddRef(); message_filter->retry_rejected_call     = [](HTASK, DWORD, DWORD)       {           return 0; // retry immediately       };  // register message filter CoRegisterMessageFilter(message_filter, nullptr); 

In that secondary STA, I get a proxy for the object interface from the main STA.

// get global interface table IGlobalInterfaceTablePtr global_interface_table; global_interface_table.CreateInstance(CLSID_StdGlobalInterfaceTable);  // get interface reference IMyInterfacePtr object_interface; global_interface_table->GetInterfaceFromGlobal(cookie, __uuidof(IMyInterface), reinterpret_cast<LPVOID*>(&object_interface))); 

This works:

// execute synchronously HRESULT hr = object_interface->SomeMethod();  /* final result, after the deferral period: hr == S_OK */ 

This does not work:

// get call factory ICallFactoryPtr call_factory; object_interface->QueryInterface(&call_factory);  // create async call AsyncIMyInterfacePtr async_call; call_factory->CreateCall(__uuidof(AsyncIMyInterface), nullptr, __uuidof(AsyncIMyInterface), reinterpret_cast<LPUNKNOWN*>(&async_call)));  // begin executing asynchronously async_call->Begin_SomeMethod();  // end executing asynchronously HRESULT hr = async_call->Finish_SomeMethod();  /* final result, immediate: hr == RPC_E_SERVERCALL_RETRYLATER */ 

0 Answers

Read More

Saturday, October 1, 2016

Exposing .NET events to COM?

1 comment

I've been trying to expose and fire an event to a VBA client. So far on the VBA client side, the event is exposed and I see the method event handling method added to my module class however the VBA event handling method does not fire. For some reason, when debugging the event is null. Modifying my code with synchronously did not help either.

For the record, I've checked other SO questions but they didn't help.

Any good answers will be appreciated.

[ComVisible(true)] [Guid("56C41646-10CB-4188-979D-23F70E0FFDF5")] [ClassInterface(ClassInterfaceType.None)] [ComSourceInterfaces(typeof(IWebEvents))] [ProgId("MyAssembly.MyClass")] public class MyClass : ServicedComponent, IMyClass {     public string _address { get; private set; }     public string _filename { get; private set; }      [DispId(4)]     public void DownloadFileAsync(string address, string filename)     {         _address = address;         _filename = filename;         System.Net.WebClient wc = new System.Net.WebClient();         Task.Factory.StartNew(() => wc.DownloadFile(_address, _filename))             .ContinueWith((t) =>         {             if (null != this.OnDownloadCompleted)                 OnDownloadCompleted();         });     }     public event OnDownloadCompletedEventHandler OnDownloadCompleted; }  [ComVisible(false)] public delegate void OnDownloadCompletedEventHandler();  [ComVisible(true)] [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)] public interface IWebEvents {     [DispId(1)]     void OnDownloadCompleted(); } 

This is a good gig for you all bounty hunters, 200 rep points

2 Answers

Answers 1

The key concept in .net code is to define event(s) as method(s) on a separate interface and connect it to the class via [ComSourceInterfacesAttribute]. In the example this is done with this code [ComSourceInterfaces(typeof(IEvents))] where IEvents interface defines the event(s) which should be handled on COM client.

Note to event naming: Event names defined in c# class and interface method names defined on interface must be the same. In this example IEvents::OnDownloadCompleted corresponds with DemoEvents::OnDownloadCompleted.

Then a second interface is defined which represents the public API of the class itself, here it is called IDemoEvents. On this interface methods are defined which are called on COM client.

C# code (builds to COMVisibleEvents.dll)

using System; using System.EnterpriseServices; using System.IO; using System.Net; using System.Runtime.InteropServices; using System.Threading.Tasks;  namespace COMVisibleEvents {     [ComVisible(true)]     [Guid("8403C952-E751-4DE1-BD91-F35DEE19206E")]     [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]     public interface IEvents     {         [DispId(1)]         void OnDownloadCompleted();     }      [ComVisible(true)]     [Guid("2BF7DA6B-DDB3-42A5-BD65-92EE93ABB473")]     [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]     public interface IDemoEvents     {         [DispId(1)]         Task DownloadFileAsync(string address, string filename);     }      [ComVisible(true)]     [Guid("56C41646-10CB-4188-979D-23F70E0FFDF5")]     [ClassInterface(ClassInterfaceType.None)]     [ComSourceInterfaces(typeof(IEvents))]     [ProgId("COMVisibleEvents.DemoEvents")]     public class DemoEvents         : ServicedComponent, IDemoEvents     {         public delegate void OnDownloadCompletedDelegate();         private event OnDownloadCompletedDelegate OnDownloadCompleted;         public string _address { get; private set; }         public string _filename { get; private set; }         private readonly string _downloadToDirectory =              Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);          public async Task DownloadFileAsync(string address, string filename)         {             try             {                 using (WebClient webClient = new WebClient())                 {                     webClient.Credentials = new NetworkCredential(                         "user", "psw", "domain");                     string file = Path.Combine(_downloadToDirectory, filename);                     await webClient.DownloadFileTaskAsync(new Uri(address), file)                         .ContinueWith(t =>                         {                             if (OnDownloadCompleted != null)                             {                                 OnDownloadCompleted();                             }                         }, TaskScheduler.FromCurrentSynchronizationContext());                 }             }             catch (Exception ex)             {                 // Log exception here ...             }         }     } } 

Note to file download: To download the file the method WebClient.DownloadFileTaskAsync is used. It downloads the specified resource to a local file as an asynchronous operation using a task object. Task object typically executes asynchronously on a thread pool thread rather than synchronously on the main application thread. Therefore it is necessary to call ContinueWith on main thread because otherwise it won't be possible to execute the OnDownloadCompleted event. Thats why ContinueWith(continuationAction, TaskScheduler.FromCurrentSynchronizationContext) is used.

regasm

C:\Windows\Microsoft.NET\Framework\v4.0.30319>regasm C:\Temp\COMVisibleEvents\bin\Debug\COMVisibleEvents.dll /tlb: C:\Temp\COMVisibleEvents\bin\Debug\COMVisibleEvents.tlb 

VBA client reference to *.tlb file

Add reference to *tlb which was generated by regasm. Here the name of this tlb file is COMVisibleEvents.

enter image description here

Here Excel User Form was used as VBA client. After the button was clicked, the method DownloadFileAsync was executed and when this method completes the event was caught in handler m_eventSource_OnDownloadCompleted. In this example you can download the source codes of the c# project COMVisibleEvents.dll from my dropbox.

VBA client code (MS Excel 2007)

Option Explicit  Private WithEvents m_eventSource As DemoEvents  Private Sub DownloadFileAsyncButton_Click()     m_eventSource.DownloadFileAsync "https://www.dropbox.com/s/0q3dskxopelymac/COMVisibleEvents.zip?dl=0", "COMVisibleEvents.zip" End Sub  Private Sub m_eventSource_OnDownloadCompleted()     MsgBox "Download completed..." End Sub  Private Sub UserForm_Initialize()     Set m_eventSource = New COMVisibleEvents.DemoEvents End Sub 

Result

enter image description here

Answers 2

The fact that your event is null may indicate that you declared your class object on the VBA client side without an WithEvents attribute. In order to receive events, you need to specify the class instance as follows:

Private WithEvents obj As MyAssembly.MyClass 
Read More

Wednesday, April 20, 2016

Pass C# object to COM by value

Leave a Comment

I'm trying to use a COM interface from C# that exposes the following interface as generated by tlbimp:

[Guid("7DDCEDF4-3B78-460E-BB34-C7496FD3CD56")] [TypeLibType(TypeLibTypeFlags.FDual | TypeLibTypeFlags.FNonExtensible | TypeLibTypeFlags.FDispatchable)] public interface IFred  {     [DispId(1)]     IBarney Pall { get; set; } }  [Guid("E390230E-EE9C-4819-BC19-08DAB808AEA9")] [TypeLibType(TypeLibTypeFlags.FDual | TypeLibTypeFlags.FNonExtensible | TypeLibTypeFlags.FDispatchable)] public interface IBarney {     [DispId(1)]     double Wilma { get; set; } } 

The generated wrapper assembly does not contain an implementation for the IBarney interface. I've created a C# structure that implements the IBarney interface like this:

[Guid("2C61BA37-7047-43DB-84B1-83B4268CF77B")] [ComVisible(true)] public struct Barney : IBarney {     public double Wilma { get; set; } } 

Which "works", the question now is, will a Barney instance be marshalled by value or by reference? This is important due to the network overhead involved. Ideally executing something like:

fredInstance.Pall = new Barney { Wilma = 0.37 }; 

will result in a single network round trip. How can I verify this, or how can I tell COM interop my Barney structure should always be marshalled by value?

Update:

Given the comment from Hans Passant. What would be the 'proper' way to design this? What would be the IDL needed to allow for a simple structure to be usable as a value for a 'property' of a COM interface? Looking at the IDL from which the interfaces are generated that I'm currently using adding a coclass declaration with a default IBarney interface would be enough, right?

1 Answers

Answers 1

A couple comments... Don't know if this will be an answer or not...

1) I would think that you should make your interfaces [ComVisible(true)]

2) Why do you define Barney as a struct? It should be a class. Unlike C++ where the only difference between a class and a struct is the default of private vs public members, C# structs and classes are fundamentally different.

Read More

Tuesday, March 8, 2016

Showing error when password protected OpenXml Word document get resaved as a password protected binary Word in office 2010

Leave a Comment

In microsoft word i have created openxml.doc(*.docx) file given credentials 'abc' as Readpassword and 'xyz' as WritePassword.

Now i have to convert openxml.doc to binary.doc(WdSaveFormat=0) the document is created sucessfully as Binary.doc using below code

 // Convert OpenXml.doc into binary.doc     Convert(@"C:\Test\OpenXml.doc", @"C:\Test\binary.doc", WdSaveFormat.wdFormatDocument);   // Convert a Word .docx to Word 2003 .doc         public static void Convert(string input, string output, WdSaveFormat format)         {             // Create an instance of Word.exe             Word._Application oWord = new Word.Application();              // Make this instance of word invisible (Can still see it in the taskmgr).             oWord.Visible = false;              // Interop requires objects.             object oMissing = System.Reflection.Missing.Value;             object isVisible = true;             object readOnly = false;             object oInput = input;             object oOutput = output;             object oFormat = format;             object oNewPassword = "xyz";             object oOldPassword = "abc";             object test = null;              try             {                 // Load a document into our instance of word.exe                 // suppose password "abc"                 Word._Document oDoc = oWord.Documents.Open(ref oInput, ref oMissing, ref readOnly,                                                             ref oMissing, oOldPassword, ref oMissing,                                                             ref oMissing, oNewPassword, ref oMissing,                                                             ref oMissing, ref oMissing, ref isVisible,                                                             ref oMissing, ref oMissing, ref oMissing,                                                             ref oMissing);                  // Make this document the active document.                 oDoc.Activate();                  // Save this document in Word 2003 format.                 oDoc.SaveAs(ref oOutput, ref oFormat, ref oMissing,                             ref oOldPassword, ref oMissing,                             oNewPassword, ref oMissing, ref oMissing,                             ref oMissing, ref oMissing, ref oMissing,                             ref oMissing, ref oMissing, ref oMissing,                             ref oMissing, ref oMissing);                 Console.WriteLine(test);                 // Always close Word.exe.                 oWord.Quit(ref oMissing, ref oMissing, ref oMissing);             }             catch (Exception)             {                 throw;             }         } 

But when try to open document manually or from code it accepts Readpassword('abc') as shown below

enter image description here

but when tries to give WritePassword('xyz') it doesnt accept and shown password incorrect error.Please check below screenshots

enter image description here

enter image description here

1 Answers

Answers 1

With the code you provided I am also not able to correctly set the read/write passwords. It seems that Word is not able to change the save format and retain the read/save passwords at the same time (this might be a bug or simple an unsupported scenario).

However, there is a very simple workaround: Just save the document temporarily without password and then set the password again:

public static void Convert(string input, string output, Word.WdSaveFormat format) {     // Create an instance of Word.exe>     var oWord = new Word.Application();      // open the protected document     var oDoc = oWord.Documents.Open(input, PasswordDocument: "abc", WritePasswordDocument: "xyz");      // save the document without password first     oDoc.SaveAs(FileName: output, Password: "", WritePassword: "");      // close and reopen     oDoc.Close();     oDoc = oWord.Documents.Open(output);      // set the password     oDoc.SaveAs(FileName: output, FileFormat: format, Password: "abc", WritePassword: "xyz");      oWord.Quit(); } 
Read More