How to get IDL from a .NET assembly (or how to to convert TLB to IDL) in a command line?
Asked Answered
L

3

11

We have a .NET assembly (Aspose.Words actually) and we want clients to use it from COM clients without much hassle.

  1. So we ship a .TLB with the assembly so the client can use it from languages such as C++ or Delphi and don't bother extracting .TLB themselves.

  2. We also ship an .IDL with the assembly so the clients can look into it if they want to see the enumeration values if they are programming in ASP for example.

  3. I want .TLB and .IDL to be generated by a build script. I can generate .TLB no problems. But I cannot generate .IDL (or as an alternative convert .TLB to .IDL) in a command line. I do this manually using OLEVIEW.EXE which is not nice.

Heard about Delphi's TLIBIMP.EXE sounds like it could do that, but it does not seem to be available separately.

So the questions are:

  1. Anything from the above sounds stupid?

  2. How to get .IDL from a .NET assembly using a command line.

Thanks.

Lungi answered 13/5, 2010 at 9:38 Comment(2)
Your clients might be able to browse the TLB files with OleView.exe. It shows the (generated) IDL given a TLB file.Taxi
Possible duplicate of Getting IDL (for TLB) from a COM+ dll when it is not providedSpalato
V
5

I have written this quick and dirty hack for extracting the IDL from a TLB. It uses the same (to the best of my knowledge) COM object that OLEVIEW.EXE uses.

/*
   Extract the IDL from a tlb file and print it to stdout.

   The tlb file is analysed by a COM object of type CLSID_ITypeLibViewer and 
   the extracted IDL is shown in a window created by the same object.

   This program extracts the IDL from that window.

   Usually Microsoft's OLEViewer (a.k.a. OLE/COM Object Viewer) registers a 
   COM object of that CLSID_ITypeLibViewer (for example inside a dll called 
   IViewers.Dll), I've tested it with:
   IVIEWERS.DLL "OLE/COM Object Interface Viewer" Version 6.0.6000.16384

   This software is provided "as-is," without any express or implied warranty.
*/

#define _CRT_SECURE_NO_WARNINGS

#include <Windows.h>
#include <atlbase.h>
#include <comdef.h>

#include <iostream>
#include <string>
#include <ctime>

const char IDL_window_name[] = "ITypeLib Viewer";

const IID   IID_IInterfaceViewer = { 0xfc37e5ba, 0x4a8e, 0x11ce, { 0x87, 0x0b, 
                                     0x08, 0x00, 0x36, 0x8d, 0x23, 0x02 } };

const CLSID CLSID_ITypeLibViewer = { 0x57efbf49, 0x4a8b, 0x11ce, { 0x87, 0x0b, 
                                     0x08, 0x00, 0x36, 0x8d, 0x23, 0x02 } };

#define RICHEDIT_CONTROL_ID 0xE901 // found by Spy++
#define RICHEDIT_PARENT_ID  0xE900 // found by Spy++

#define CHECK(hr) if ( FAILED(hr) ) { std::wcerr << L"COM error: " << stringify_hr(hr) << L"\n"; return __LINE__; }

std::string myself;
std::string tlb;

// My installation of Microsoft Visual Studio 2010 has a sample folder where 
// I can find the source of OleView:
// C:\Program Files (x86)\Microsoft Visual Studio 10.0\Samples\1033\VC2010Samples.zip
//
// Unzipping the file gives me the solution for Visual Studio 2010
// C++\MFC\ole\oleview\oleview.sln
// 
// The following declaration comes from that sample.
// The sample has also the source for the CLSID_ITypeLibViewer object but 
// unfortunately the method View is not implemented (the code in my sample says
// "Implement interface viewer UI here.").
DECLARE_INTERFACE_(IInterfaceViewer, IUnknown)
{
    STDMETHOD(QueryInterface) (THIS_ REFIID riid, LPVOID FAR* ppvObj) PURE;
    STDMETHOD_(ULONG,AddRef) (THIS) PURE;
    STDMETHOD_(ULONG,Release) (THIS) PURE;

    STDMETHOD(View) (HWND hwndParent, REFIID riid, LPUNKNOWN punk) PURE;
};

std::string replace_all( const char* txt, const std::string& old_s, const std::string& new_s );
std::string adapt( const char* txt );
std::string now();
std::wstring stringify_hr( HRESULT hr );

DWORD WINAPI scrape(LPVOID)
{
   HWND hwnd = 0;   
   int guard = 100;

   while( guard-- > 0 && (hwnd = ::FindWindowA(0,IDL_window_name)) == 0 ) {
      ::Sleep(100);
   }

   if ( guard <= 0 ) {
      std::cerr << "No window found with name '" << IDL_window_name << "'\n";
      return __LINE__;
   }

   guard = 100;
   const size_t sz = 100*1024;
   char text[sz];
   do {
      HWND parent_hwnd = ::GetDlgItem(hwnd,RICHEDIT_PARENT_ID);
      if ( parent_hwnd == 0 ) {
         ::SendMessage(hwnd,WM_CLOSE,0,0);
         std::cerr << "No parent with ID " << std::hex << "0x" 
                   << RICHEDIT_PARENT_ID << " found in window with name '" 
                   << IDL_window_name << "'\n";
         return __LINE__;
      }
      HWND richedit_hwnd = ::GetDlgItem(parent_hwnd,RICHEDIT_CONTROL_ID);
      if ( richedit_hwnd == 0 ) {
         ::SendMessage(hwnd,WM_CLOSE,0,0);
         std::cerr << "No richedit with ID " << std::hex << "0x" 
                   << RICHEDIT_CONTROL_ID << " found in window with name '" 
                   << IDL_window_name << "'\n";
         return __LINE__;
      }
      ::GetWindowTextA(richedit_hwnd,text,sz);
      ::Sleep(100);
   } while ( guard-- > 0 && strlen(text) == 0 );

   if ( guard <= 0 ) {
      ::SendMessage(hwnd,WM_CLOSE,0,0);
      std::cerr << "No IDL found in window with name '" 
                << IDL_window_name << "'\n";
      return __LINE__;
   }

   std::cout << "// IDL extracted from " << tlb << " by tlb2idl (" 
             << myself << ") on " << now() << "\n" << adapt(text);

   ::SendMessage(hwnd,WM_CLOSE,0,0);
   return 0;
}

int main(int argc, char* argv[])
{
   myself = argv[0];
   if ( argc <= 1 ) {
      std::cerr << "\nPrint to stdout the IDL extracted from file.tlb\n\nUsage:\n" 
                << myself << " file.tlb\n";
      return __LINE__;
   }

   DWORD exit_code = 0;
   CHECK( ::CoInitialize(0) );
   {
   CComPtr<ITypeLib> ptl;
   tlb = argv[1];
   CHECK( ::LoadTypeLib(std::wstring(tlb.begin(),tlb.end()).c_str(), &ptl) );

   CComPtr<IInterfaceViewer> piv;
   CHECK( ::CoCreateInstance( CLSID_ITypeLibViewer, NULL, CLSCTX_SERVER, 
                              IID_IInterfaceViewer, (void**)&piv ) );

   HANDLE th = ::CreateThread( 0, 0, scrape, 0, 0, 0 );
   CHECK( piv->View(0,IID_ITypeLib,ptl) );
   ::WaitForSingleObject(th,1000);
   ::GetExitCodeThread(th,&exit_code);
   }
   ::CoUninitialize();
   return exit_code;
}

std::string replace_all( const char* txt, const std::string& old_s, const std::string& new_s )
{
   std::string str(txt);
   size_t start_pos = 0;
   while((start_pos = str.find(old_s, start_pos)) != std::string::npos) {
      str.replace(start_pos, old_s.length(), new_s);
      start_pos += new_s.length(); 
   }
   return str;
}

std::string adapt( const char* txt )
{
   std::string str( replace_all(txt,"\r\n","\n") );
   return replace_all(str.c_str(),"\r","\n");
}

std::string now()
{
   time_t szClock;
   time( &szClock );
   struct tm *newTime = localtime( &szClock );
   return asctime( newTime ); 
}

std::wstring stringify_hr( HRESULT hr )
{
   return _com_error(hr).ErrorMessage();
}
Vehement answered 22/11, 2012 at 14:14 Comment(0)
M
2

that's fine, fix only the now function to vs 2012:

std::string now()
{
   char buffer[32];
   errno_t errNum;
   _time32( &aclock );   
   _localtime32_s( &newtime, &aclock );   

   errNum = asctime_s(buffer, 32, &newtime);

   return buffer; 
}

must ensure that you have not open OLEVIEW.

Midway answered 30/11, 2012 at 10:56 Comment(1)
Just for other confused readers: this should have been an edit/comment to the other answer https://mcmap.net/q/1024783/-how-to-get-idl-from-a-net-assembly-or-how-to-to-convert-tlb-to-idl-in-a-command-lineKeppel
T
0

The type library is sufficient for any language to use your server. The IDL would only be useful to generate a type library, which you already supply, or to generate a C++ header file, which is much easier to generate with the #import directive.

Which would be one way perhaps if you really want to do this, build a small C++ program that uses #import, provide the generated .tlh file.

Tenant answered 13/5, 2010 at 12:16 Comment(1)
Thanks and I will seriously consider not supplying IDL. If you are programming say in VBScript/Perl/PHP/Python and not using an IDE with a TLB Object Browser, then .IDL allows to see how .NET exposed method signatures, for example RESULT Save_2([in] BSTR fileName); and enum values enum { HeightRule_AtLeast = 0, HeightRule_Exactly = 1, } HeightRule; But I guess there are not many developers out there doing this and if they are making their life so difficult already, they will be okay reading help how to convert TLB to IDL if needed.Lungi

© 2022 - 2024 — McMap. All rights reserved.