So I'm trying to do manually what AxImp does (I'm doing it dynamically).
My product is a wide released, sanctioned "add-on" to a third party product. They have an OCX, which I add to my form with a COM reference...however, if the client has (or installs) an updated version of their product my product can no longer load the OCX.
Therefore I'm trying to load their OCX dynamically. I've got everything working except that I need the GUID of one of the interfaces in one of their OCXs. I know what the type name is, and the OCX >is< registered on the system. How can I get an object's GUID just from the type name?
Note, Assembly.LoadFrom() doesn't work because the OCX isn't .NET it's COM.
Since your comment let us know that the GUID is found in the OCX but not registered under HKEY_CLASSES_ROOT, we'll have to read it from the type library:
Call LoadTypeLib or LoadTypeLibEx, passing the path to the .OCX file
Then use the FindName method of the returned object.
Then GetTypeAttr followed by PtrToStructure to get a TYPEATTR structure with the GUID.
Not sure why you can't simply add a COM reference to the DLL to your project.
Visual Studio will automatically add a .NET wrapper to any COM object that is referenced this way.
I had to write something like that to migrate code from COM to C#
class GetGuid
{
[DllImport("oleaut32.dll", CharSet = CharSet.Unicode, ExactSpelling = true)]
static extern int LoadTypeLib(string fileName, out ITypeLib typeLib);
public string SearchRegistry(string dllPath /* or ocx */)
{
var result = string.Empty;
ITypeLib tl;
if (LoadTypeLib(dllPath, out tl) == 0)
{
ITypeInfo tf;
tl.GetTypeInfo(0, out tf);
var ip = IntPtr.Zero;
tl.GetLibAttr(out ip);
if (ip != IntPtr.Zero)
{
var ta = (System.Runtime.InteropServices.ComTypes.TYPELIBATTR)Marshal.PtrToStructure(ip, typeof(System.Runtime.InteropServices.ComTypes.TYPELIBATTR));
result = ta.guid.ToString();
}
}
return result;
}
}
Related
In most cases, VB6 DLLs and OCXs must be registered before use. In Visual Basic Project (VBP) files the objects referenced are listed as follows:
Object={70031B70-1070-0D70-AC0E-B049A0701010}#1.0#0; Component.ocx
Reference=*\G{000CD090-0D00-4F07-0707-80040E010704}#1.0#0#..\References\GeneralLibrary.dll#General Purpose Libraries
When these files are updated, I need to change all of the references in my VB6 project. Since VBP files are just text files, these references can be updated using text parsing. The caveat is, I need to know the GUIDs of each component and version ahead of time.
After some searching I found that the oleaut32.dll library in Windows can be used to gather information from a DLL. This can be leveraged through C# using ITypeLib and ITypeInfo.
I was able to scour a method for using C# to read the DLL file in question to obtain GUID and version information. However, the documentation available for the ITypeLib and ITypeInfo doesn't have any examples. I also wasn't able to find any examples online.
Sample Code:
class Program
{
[DllImport("oleaut32.dll", PreserveSig=false)]
public static extern ITypeLib LoadTypeLib([In, MarshalAs(UnmanagedType.LPWStr)] string typelib);
static void Main(string[] args)
{
ITypeLib dllLibrary;
ITypeInfo information;
dllLibrary = LoadTypeLib("C:\\common_files\\Common.dll");
dllLibrary.GetTypeInfo(0, out information);
Console.ReadLine();
}
}
Question
How do I use ITypeLib and ITypeInfo to get the GUID and version information of a DLL or OCX?
Caveat: Because of the way our system is deployed and the fact that there are a number of old DLL artifacts in the registry with the same name, I would like to avoid registering the DLLs and then reading what was registered from the registry.
I was also unable to find the DLL used for getting version information as stated in this question.
The following code will load a tlb in C# - i've not tried a DLL or OCX with an embedded tlb, but it may work for those. If not, you'll need to extract the tlb from the embedded resources (yay! more PInvoke) and then run on the tlb directly. This is a full command line app - you can copy and paste this into a new source file and compile and test on the command line.
using System;
using System.Runtime.InteropServices;
using System.Diagnostics;
public class TypeLibTest
{
[DllImport("oleaut32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, PreserveSig = false)]
private static extern IntPtr LoadTypeLib(string fileName, out System.Runtime.InteropServices.ComTypes.ITypeLib typeLib);
public static void Main(string[] args)
{
System.Runtime.InteropServices.ComTypes.ITypeLib typeLib = null;
IntPtr ptr = IntPtr.Zero;
string file = arg[0];
try
{
LoadTypeLib(file, out typeLib);
typeLib.GetLibAttr(out ptr);
var typeAttr = (System.Runtime.InteropServices.ComTypes.TYPELIBATTR) Marshal.PtrToStructure(ptr, typeof(System.Runtime.InteropServices.ComTypes.TYPELIBATTR));
Console.WriteLine("{0}.{1}", typeAttr.wMajorVerNum, typeAttr.wMinorVerNum);
}
catch (COMException ex)
{
Console.WriteLine("Error: " + ex.Message);
}
finally
{
if (typeLib != null && ptr != IntPtr.Zero)
{ typeLib.ReleaseTLibAttr(ptr); }
}
}
}
After reading Larry Osterman's response on the very same issue I am trying to solve at the moment, I thought I had found the answer to my question.
For the record, the question was : how can I from .Net (non-WinRT) list the types in a WinRT assembly ( mine are .dll files apparently, not .Winmd)
I therefore used the following code snippet :
//note, this wrapper function returns the metadata file name and token
// it immediately releases the importer pointer
static Tuple<string, UInt32> ResolveTypeName(string typename)
{
string path;
object importer = null;
UInt32 token;
try
{
var hr = RoGetMetaDataFile(typename, IntPtr.Zero, out path, out importer, out token);
//TODO: check HR for error
return Tuple.Create(path, token);
}
finally
{
Marshal.ReleaseComObject(importer);
}
}
[DllImport("WinTypes.dll")]
static extern UInt32 RoGetMetaDataFile(
[MarshalAs(UnmanagedType.HString)] string name,
IntPtr metaDataDispenser,
[MarshalAs(UnmanagedType.HString)] out string metaDataFilePath,
[MarshalAs(UnmanagedType.Interface)] out object metaDataImport,
out UInt32 typeDefToken);
( found on https://gist.github.com/2920743)
Unfortunately, I get a non-zero HResult.
I referred to the documentation and found this :
HR_RESULT_FROM_WIN32(ERROR_NO_PACKAGE) The function was called from a
process that is not in a Windows Store app.
Does that mean it is not possible to list the types from .Net (non-WinRT) at all ?
RoGetMetaDataFile is used to load a metadata file from within an app package. It locates the metadata file in which the named type is defined, loads that metadata file, and returns an IMetaDataImport interface pointer that represents that metadata file.
From ordinary .NET code you can call RuntimeEnvironment.GetRuntimeInterfaceAsIntPtr (or GetRuntimeInterfaceAsObject) to get the current runtime's IMetaDataDispenser interface pointer, which can be used to load arbitrary modules for inspection.
From native code, you can call ICLRMetaHost::GetRuntime to load a runtime, then from that object call ICLRRuntimeInfo::GetInterface to get its IMetaDataDispenser interface pointer.
RoGetMetaDataFile can be used from outside the app package, however it will only resolve system windows runtime types.
In order to resolve app specific types, you need to be running with "package identity" - in other words, in the context of a running application.
so I've been told I might not have access to the registry or programs with which usually load their IFilters onto the system, so I have to include the IFilter dlls in the application and load them directly from there. I'm currently using CodeProject's C# IFilter classes, but their are still a few things that are over my head when it comes to the filterPersistClass, persistentHandlerClass and COM and as such I am a bit lost on how I could get this to work.
I've done all the mundane stuff like, get the dlls, setup a resource file with "Extension, DLL Path" and that, but just can't seem to get a grasp on how to now load the IFilter DLL. It's maybe that I should just start from scratch, but thought I would ask for some help first.
EDIT (Partial Solution)
Well I figured out how to load query.dll using the code below in the FilterReader constructor in FilterReader.cs, though I'm having problems now loading the PDFFilter.dll file and am getting the following error:
Unable to find an entry point named 'LoadIFilter' in DLL 'C:\Program Files\Adobe\Adobe PDF iFilter 9 for 64-bit platforms\bin\PDFFilter.dll'
The problem I think I am now stuck at is that PDFFilter.dll uses STA and C# applications are MTA.
[DllImport("query.dll", SetLastError = true, CharSet = CharSet.Unicode)]
static extern int LoadIFilter(string pwcsPath, [MarshalAs(UnmanagedType.IUnknown)] ref object pUnkOuter, ref IFilter ppIUnk);
// --------------------------- constructor ----------------------------------
var isFilter = false;
object iUnknown = null;
LoadIFilter(fileName, ref iUnknown, ref _filter);
var persistFile = (_filter as IPersistFile);
if (persistFile != null)
{
persistFile.Load(fileName, 0);
IFILTER_FLAGS flags;
IFILTER_INIT iflags =
IFILTER_INIT.CANON_HYPHENS |
IFILTER_INIT.CANON_PARAGRAPHS |
IFILTER_INIT.CANON_SPACES |
IFILTER_INIT.APPLY_INDEX_ATTRIBUTES |
IFILTER_INIT.HARD_LINE_BREAKS |
IFILTER_INIT.FILTER_OWNED_VALUE_OK;
if (_filter.Init(iflags, 0, IntPtr.Zero, out flags) == IFilterReturnCode.S_OK)
isFilter = true;
}
if (_filter != null && isFilter) return;
if (_filter != null) Marshal.ReleaseComObject(_filter);
There is nothing magical about IFilter objects. They are housed in standard COM dlls. In the end, all you need the clsid of the class which knows how to process pdf files.
The LoadIFilter function in query.dll is just a convenient helper function. Everything it does you can do yourself.
There is a standard way, in the registry, in which a file extension (e.g. .pdf) is resolved to a clsid (e.g. {E8978DA6-047F-4E3D-9C78-CDBE46041603})
Note: You could also just skip to the end, and know that the clsid of Adobe's IFilter implementation is {E8978DA6-047F-4E3D-9C78-CDBE46041603}. But that's not guaranteed, so you need to crawl the registry.
The algorithm to resolve an .ext to the clsid of an object that implements IFilter is:
GetIFilterClassIDForFileExtension(String extension)
arguments:
extension (String) e.g. ".pdf"
returns:
clsid (Guid) e.g.
//Get the Persistent Handler for this extension
//e.g.
// HKLM\Software\Classes\.pdf\PersistentHandler\(Default)
//returns
// "{F6594A6D-D57F-4EFD-B2C3-DCD9779E382E}"
persistentHandlerGuid = HKLM\Software\Classes\.pdf\PersistentHandler\(Default)
//Get the clsid associated with this persistent handler
//e.g.
// HKLM\Software\Classes\CLSID\{F6594A6D-D57F-4EFD-B2C3-DCD9779E382E}\PersistentAddinsRegistered\{89BCB740-6119-101A-BCB7-00DD010655AF}
//where the final guid is the interface identifier (IID) of IFilter
clsid = HKLM\persistentHandlerGuid\PersistentAddinsRegistered\{89BCB740-6119-101A-BCB7-00DD010655AF}
//e.g. returns "{E8978DA6-047F-4E3D-9C78-CDBE46041603}", the clsid of Adobe's PDF IFilter
return clsid
Once you have the clsid of the appropriate object, you create it with:
Guid clsid = GetIFilterClassForFileExtension(".pdf")
IFilter filter = CreateComObject(clsid);
You now have the entire guts of the LoadIFilter function from query.dll:
IFilter LoadIFilter(String filename)
{
String extension = ExtractFileExt(filename); //e.g. "foo.pdf" --> ".pdf"
Guid clsid = GetIFilterClassForFileExtension(extension);
return CreateComObject(clsid) as IFilter;
}
Now, all that still requires the registry, because you still have to be able to resolve an extension into a clsid. If you already know the classid, then you don't need the registry:
IFilter adobeIFilterForPdfs = CreateComObject("{E8978DA6-047F-4E3D-9C78-CDBE46041603}")
And you're good to go.
The important point is that the function you're trying to call, LoadIFilter is not inside Adobe's dll (or any other IFilter dll provided by any other company, to crawl any other file types). The LoadIFilter function is exported by query.dll, and is simply a helper function for the above steps i described.
All IFilter dlls are COM dlls. The documented way to load a COM dll is through the CoCreateInstance function:
IUnknown CreateComObject(Guid ClassID)
{
IUnknown unk;
HRESULT hr = CoCreateInstance(ClassID, null, CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER, IUnknown, ref unk);
if (Failed(hr))
throw new Exception("Could not create instance: "+hr);
return unk;
}
I'll leave it to you to find the correct way to create a COM object from C# managed code. I've forgotten.
Note: Any code released into public domain. No attribution required.
This question already has answers here:
Closed 11 years ago.
Possible Duplicate:
Specify the search path for DllImport in .NET
I have an unmanaged DLL. For clearance, its a C++ dll that I want to use in my c# code.
The problem is that its a windows app and user can install it in any directory of their choice. So I can't reference it with a static path and I couldn't find a way to give a relative path.
Here is the code I tried :
[DllImport("mydll.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern bool SetDllDirectory(string lpPathName);
public void SetDllDirectory(string lpPathName)
{
try
{
bool r = SetDllDirectory(lpPathName);
}
catch (Exception ex)
{
throw ex;
}
}
public void SetDirectoryPath()
{
try
{
DirectoryInfo directory = new DirectoryInfo(System.IO.Path.GetDirectoryName(System.Windows.Forms.Application.ExecutablePath));
if (directory.Exists)
SetDllDirectory(directory.ToString() + "\\mydll.dll");
}
catch (Exception ex)
{
throw ex;
}
}
And below is the error I am receiving.
Unable to find an entry point named 'SetDllDirectory' in DLL 'mydll.dll'.
This question could be a replica of
"C++ unmanaged DLL in c#"
"Relative Path to DLL in Platform Invoke Statement"
Sorry for that but I didn't find any solution in these references.
If you are trying to pinvoke into an unmanaged library that is in a non-standard location you need to add this location to the dll search path that windows uses to look for dll files.
If your users can tell the program where the c/c++ library file is, you can load it manually when they choose it.
public class UnsafeNativeMethods {
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool SetDllDirectory(string lpPathName);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern int GetDllDirectory(int bufsize, StringBuilder buf);
[DllImport("kernel32", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern IntPtr LoadLibrary(string librayName);
[DllImport("mylibrary")]
public static extern void InitMyLibrary();
}
You can call the above in some form of preload routine like so:
public void LoadDllFile( string dllfolder, string libname ) {
var currentpath = new StringBuilder(255);
UnsafeNativeMethods.GetDllDirectory( currentpath.Length, currentpath );
// use new path
UnsafeNativeMethods.SetDllDirectory( dllfolder );
UnsafeNativeMethods.LoadLibrary( libname );
// restore old path
UnsafeNativeMethods.SetDllDirectory( currentpath );
}
You might then call it like so:
LoadDllFile( "c:\whatever", "mylibrary.dll" );
This error message:
Unable to find an entry point named 'SetDllDirectory' in DLL 'mydll.dll'.
says nothing about not being able to find the DLL. It says that your P/Invoke definition is incorrect. There is no function named SetDllDirectory in your DLL (mydll.dll). And why should there be? The only functions that are exported from DLLs are those that you explicitly code and export. Since you didn't write code for a SetDllDirectory and indicate that it should be exported from mydll.dll, it doesn't exist in the DLL.
Beyond that, this question is unanswerable unless you update your question with real code. You need to post, at minimum, the signature for the function you're trying to call from the unmanaged DLL, along with the P/Invoke code that you've tried on the C# (managed) side.
The problem is that its a windows app and user can install it in any directory of their choice. So I can't reference it with a static path and I couldn't find a way to give a relative path.
This is not a real problem. Relative paths work just fine, and they work in exactly the way that your question indicates that you've already tried. You just include the name of the DLL in the P/Invoke definition. The default DLL search order takes care of everything else. If the managed EXE is in the same directory as the unmanaged DLL that you want to P/Invoke, everything will work seamlessly.
It doesn't matter where the user chooses to install the app, so long as the DLL and the EXE are located in the same folder. And that's the job of your installer.
And so long as you use relative paths, you absolutely do not need to use the SetDllDirectory function from the Win32 API (which is actually defined in kernel32.dll).
I'm trying to use TrustCommerce in my website. I installed dll files from the website, but I can't use it' I'm supposed to be able to use myObject.PushParam(string) and it's not working. Is there something I'm missing here? I found an article about it in PHP:
public static void TrustCommerce()
{
//TCLinkNET.TClinkClass.
object myObject = Orders.COMCreateObject("TCLINKCOMLib.TClinkClass");
}
public static object COMCreateObject(string sProgID)
{
// We get the type using just the ProgID
Type oType = Type.GetTypeFromProgID(sProgID);
if (oType != null)
{
return Activator.CreateInstance(oType);
}
return null;
}
add COM reference to your project, then an introp assembly will be generated for the COM. the way you are using requires reflection to invoke COM methods. 3.5 or less, do not forget to distribute the interop assembly with your application installer.