Take the 2-minute tour ×

I have a legacy VB 6 application that uses WriteProfileString which is provided only for compatibility with 16-bit versions of Windows.

I'm migrating it into a functional equivalent (that means an exact clone) .NET application. This requires me generate an Interop assembly from the TLB file of the WriteProfileString COM object using TlbImp.exe. This is a requirement and P/Invoke cannot be used.

When I do so TlbImp.exe generates an empty assembly.

WriteProfileString has the following IDL:

// typelib filename: WriteProfileString.tlb

  helpstring("WritePrivateProfileStringW API Type Library")
library igrWriteProfileStringW

      helpstring("KERNEL API Calls")
    module KernelAPI {
        [entry("WritePrivateProfileStringW"), helpstring("Sets the value of a .ini file setting.")]
        long _stdcall WritePrivateProfileStringW(
                        [in] BSTR lpApplicationName, 
                        [in] BSTR lpKeyName, 
                        [in] BSTR lpString, 
                        [in] BSTR lpFileName);

Fortunately Microsoft has open sourced the next version of TlbImp.exe named TlbImp2.exe.
I was able to debug through the code and found out that both TlbImp.exe and TlbImp2.exe do not import methods from modules. I had to hack through the code in order to make TlbImp2.exe to export the module's static methods.

I had to change the ConvModule.cs file:

public override void OnCreate()
    // Avoid duplicate creation
    if (m_type != null)

    // Create constant fields for the module
    ConvCommon.CreateConstantFields(m_info, RefNonAliasedTypeInfo, m_typeBuilder, ConvType.Module);
    ConvCommon.CreateMethodsForModule(m_info, RefNonAliasedTypeInfo, m_typeBuilder, ConvType.Module);

    m_type = m_typeBuilder.CreateType();

And add the following method to ConvCommon.cs:

public static void CreateMethodsForModule(ConverterInfo info, TypeInfo type, TypeBuilder typeBuilder, ConvType convType)
    using (TypeAttr attr = type.GetTypeAttr())
        int cVars = attr.cFuncs;
        for (int n = 0; n < cVars; ++n)
            using (var func = type.GetFuncDesc(n))
                string methodName = type.GetDocumentation(func.memid);

                CreateMethod(new InterfaceInfo(info, typeBuilder, true, type, attr, false), new InterfaceMemberInfo(info, type, n, methodName, methodName, InterfaceMemberType.Method, TypeLibTypes.Interop.INVOKEKIND.INVOKE_FUNC, func.memid, func, null), CreateMethodMode.InterfaceMethodMode);

So now it exports the methods correctly but I'm still wondering why it didn't export these methods in the first place.

This is the code of the exported assembly:

using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

namespace igrWriteProfileStringW
  public static class KernelAPI
    [MethodImpl(MethodImplOptions.PreserveSig | MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
    public abstract int WritePrivateProfileStringW([MarshalAs(UnmanagedType.BStr), In] string lpApplicationName, [MarshalAs(UnmanagedType.BStr), In] string lpKeyName, [MarshalAs(UnmanagedType.BStr), In] string lpString, [MarshalAs(UnmanagedType.BStr), In] string lpFileName);

It generates a static class with an abstract member. That doesn't make any sense. Of course I can change the code to export it differently but why does TlbImp2.exe do so in the first place?

I'm assuming it exports it that way because it exports the module's constants. Am I right? What modifiers should I apply to the WriteProfileString method in order to ensure it can be used with interop?

share|improve this question

2 Answers 2

It is easier to use the native api directly instead of jumping through all COM hoops..

from pinovke.net:

[return: MarshalAs(UnmanagedType.Bool)]
static extern bool WritePrivateProfileString(string lpAppName,
   string lpKeyName, string lpString, string lpFileName);
share|improve this answer
I am aware of p/invoke but I must use COM Interop. –  the_drow Aug 11 '13 at 11:38
Oh dear, I guess the WriteProfileString is then one of the easier challenges... –  rene Aug 11 '13 at 11:42
Yes it is :) This is a business requirement with a good reason which I can't disclose. –  the_drow Aug 11 '13 at 11:47

Type libraries support a super-set of declarations that Tlbimp.exe supports. The ability to declare exports from a DLL is not used very often, type libraries are first and foremost vehicles to export COM declarations.

There's little you can do with Tlbimp to solve this problem. Nor should you try to solve it since the alternative is so simple. Just write a pinvoke declaration.

Do consider the next step, Read/WritePrivateProfileString() have really awful runtime behavior due to the heavy legacy appcompat built into Windows. Reading a single INI parameter costs about 50 milliseconds. Read 20 of them and you've blown your startup time with doing nothing very useful. And it is very lossy, it is entirely agnostic of text file encoding. You cannot typically use the built-in .NET support for Settings in a library, but an XML file is a fine substitute.

share|improve this answer
I am fully aware of those considerations but I must use COM Interop. –  the_drow Aug 11 '13 at 11:36
I have no clue why you think that's necessary. If you still need to provide a type library for VB6 code with that module declaration (why?) then simply use the original one. Or recreate it by compiling the IDL with midl.exe –  Hans Passant Aug 11 '13 at 12:16
You'll have to trust me on this one. I need a .NET Interop assembly. –  the_drow Aug 11 '13 at 12:54
This makes no sense at all. An interop assembly is required to let .NET code use a COM server. You stated in your question that you wanted the exact opposite and replace a COM server with .NET code. You'd only ever use Tlbexp, not Tlbimp. And since it is supposed to be an "exact clone", the existing type library must already be a perfect match with the one you'd generate with Tlbexp. You cannot get the answer you are looking for when your question makes no sense and we are supposed to trust you that it doesn't make sense. You'll have to trust us that you are doing it wrong. –  Hans Passant Aug 11 '13 at 13:01

Your Answer


