We have a .NET assembly (Aspose.Words actually) and we want clients to use it from COM clients without much hassle.
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.
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.
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:
Anything from the above sounds stupid?
How to get .IDL from a .NET assembly using a command line.
Thanks.
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();
}
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.
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.
来源:https://stackoverflow.com/questions/2825734/how-to-get-idl-from-a-net-assembly-or-how-to-to-convert-tlb-to-idl-in-a-comma