Building Tcl DLL's for Windows

Note PT 25-Aug-2005: Some of this information is getting old and the page could do with an update.

There is a sampleextension package available from http://core.tcl.tk/sampleextension . This provides an example of how to write a Tcl extension and also shows how to setup the autoconf files and makefiles for use on unix. The win/ subdirectory has a fairly useful makefile for MSVC++ as well and I would recommend modifying this makefile.vc in preference to rolling your own.

Using Microsoft Visual C++ 6

  • Create a new win32 dynamic link library project and call it something nice, like tcldemo. This is going to be our package name too. (Note: Creating an MFC DLL project may cause problems, as the MFC framework infrastructure can conflict with the declarations in example code. You'll know you've got a _declspec() problem if you get unresolved symbols like __imp_TclCreateObjCommand() -RWT)
  • Select an 'Empty DLL' on the next dialog.
  • Before you get going, make sure you have told Visual Studio about the Tcl headers and libraries. To do this, open Tools->Options->Directories and give the Tcl include directory in the includes box and the libraries in the libraries box.
  • Open up the Project settings menu item for your new project and go to the C++ tab. Add USE_TCL_STUBS to the list of defined symbols and in the Link tab prepend tclstub83.lib to the list of libraries. (Adust the numerical suffix for your version of tcl).
  • Now we can write some code. Create a new C++ source file, called tcldemo.cpp and paste in the code below.
  • Build it. Ignore the whining about MSCVRT conflicts. You now have a DLL in either Debug/Release.
  • Fire up tclsh or better tkcon from the Debug or Release subdirectory under your project and issue
  load tcldemo.dll Tcldemo
  set tcldemo_version
  package require Tcldemo
  • You should get 0.1 printed twice!

cjl adds: I just spent a frustrating afternoon fighting an error message when trying to load a .dll created in this way on machines other than the build machine. Since googling revealed only others with similar problems, but no solutions, I thought I'd record it here.

  • The error message
    Couldn't load library "whatever.dll" invalid argument
  • The probable cause : Debug builds depend on debug versions of the language runtime, which isn't available for redistribution.
  • The solution : Only ship release builds to none-development machines. You may still need to install the appropriate "Redistributable package" for your version of Visual Studio, or package the relevant files with your own. Look in the documentation for "deployment" for the alternative methods.

Using mingw32 GNU C / C++

If you don't happen to have a copy of Microsoft's compiler then you can get a copy of GNU C compiled for the win32 platform. The mingw32 project is the minimal approach which uses only the native libraries and so doesn't support unix porting much - See Building Tcl/Tk with Mingw for info about building Tcl/Tk with Msys and Mingw.

  • Create a .cpp or .cc file with the code below using your favourite editor. (eg: emacs)
  • Create a tcldemo.def file with the following lines:
 EXPORTS
 Tcldemo_Init
 Tcldemo_SafeInit
  • Compile the code:
 g++ -Wall -g -Ii:/opt/Tcl/include -c tcldemo.cpp -o tcldemo.o
 dllwrap --driver-name g++ -def tcldemo.def -o tcldemo.dll tcldemo.o -Li:/opt/Tcl/lib -ltcl83 -lm
  • Voila: you now have a tcldemo.dll. Test as above.

I've included a suitable Makefile below.

[ Q: isn't it possible to do this without a .def file? I thought the __declspec(dllexport) was supposed to make it unnecessary ]


Dana Robinson: You can use either. You can even have some functions declared as __declspec(dllexport) and other ones specified in a .def file and it'll all work out. Personally, I like to use .def files with platform-independent code. They aren't that hard to maintain and you don't have to mess up your pretty ANSI C/C++ code with a bunch of #ifdefs and MS-specific code.


The code:

 #include <windows.h>
 #include <tcl.h>

 #ifndef DECLSPEC_EXPORT
 #define DECLSPEC_EXPORT __declspec(dllexport)
 #endif // DECLSPEC_EXPORT

 BOOL APIENTRY
 DllMain(HANDLE hModule, DWORD dwReason, LPVOID lpReserved)
 {
     return TRUE;
 }

 EXTERN_C int DECLSPEC_EXPORT
 Tcldemo_Init(Tcl_Interp* interp)
 {
 #ifdef USE_TCL_STUBS
     Tcl_InitStubs(interp, "8.3", 0);
 #endif
     Tcl_Obj *version = Tcl_SetVar2Ex(interp, "tcldemo_version", NULL,
                                      Tcl_NewDoubleObj(0.1), TCL_LEAVE_ERR_MSG);
     if (version == NULL)
         return TCL_ERROR;
     int r = Tcl_PkgProvide(interp, "Tcldemo", Tcl_GetString(version));

     // Call Tcl_CreateObjCommand etc.

     return r;
 }

 EXTERN_C int DECLSPEC_EXPORT
 Tcldemo_SafeInit(Tcl_Interp* interp)
 {
     // We don't need to be specially safe so...
     return Tcldemo_Init(interp);
 }

 # -*- Makefile -*- for Tcl Demo
 #
 # @(#)$Id: 2419,v 1.51 2006-01-28 07:00:31 jcw Exp $

 CC      =g++
 DLLWRAP =dllwrap
 DLLTOOL =dlltool
 RM      =rm -f
 CFLAGS  =-Wall -Ii:/opt/tcl/include -DUSE_TCL_STUBS
 LDFLAGS =-Li:/opt/tcl/lib
 LIBS    =-ltclstub83

 DLL     =tcldemo.dll
 DEFFILE =tcldemo.def

 WRAPFLAGS =--driver-name $(CC) --def $(DEFFILE)

 CSRCS   =tcldemo.cpp
 OBJS    =$(CSRCS:.cpp=.o)

 $(DLL): $(OBJS)
        $(DLLWRAP) $(WRAPFLAGS) -o [email protected] $^ $(LDFLAGS) $(LIBS)

 clean:
        $(RM) *.o core *~

 %.o: %.cpp
        $(CC) $(CFLAGS) -c $< -o [email protected]

 .PHONY: clean

 #
 # Local variables:
 #   mode: makefile
 # End:
 #

This excellent piece of work was created and put here by Pat Thoyts. Thanks Pat!


Paul Kienzle: With recent versions of mingw you can dispense with .def files, DECLSPEC_EXPORT, #include <windows.h>, DllMain, and dllwrap, and just use:

  g++ -Wall -g -Ii:/opt/Tcl/include -DUSE_TCL_STUBS -c tcldemo.cpp -o tcldemo.o
  g++ -shared -o tcldemo.dll tcldemo.o -Li:/opt/Tcl/lib -ltclstub83

After correcting the paths in my Makefile, my tcl extension from unix built and worked fine with ActiveState's binary distribution for windows.


I was unable to produce functional DLL with Paul Kienzles advice, but found another easy way to compile shared library with MinGW for MinGW-compiled Tcl:

 gcc -I C:\Tcl\include -s -shared -o test.dll test.c C:\Tcl\bin\tcl84.dll

Apparently newer versions of gcc can extract all required names from the DLL by itself, without using libtclXX.a or libtclstubXX.a. --mjk

( g++.exe can do that, too. But then use a extern "C" block for init and wrapper functions. You can then still call your other C++ functions and create objects, use new and delete etc. and pass the result back to the wrapper function in the extern "C" block. )

I also was able to make this work with Active Tcl 8.4, but you have to remember to remove "USE_TCL_STUBS" fromn your source code or compile option --Larry Rowe


See also Building Extensions on Windows.


Question: Once you have built the extensions, where should they be installed, and what gets installed?


Hello!

When compiling Tcldemo with MS Visual Studio .NET I got some errors:

    tcldemo error LNK2019: unresolved external symbol __imp__Tcl_PkgProvide referenced in function _Tcldemo_Init
    tcldemo error LNK2019: unresolved external symbol __imp__Tcl_GetString referenced in function _Tcldemo_Init
    tcldemo error LNK2019: unresolved external symbol __imp__Tcl_NewDoubleObj referenced in function _Tcldemo_Init
    tcldemo error LNK2019: unresolved external symbol __imp__Tcl_SetVar2Ex referenced in function _Tcldemo_Init
    tcldemo fatal error LNK1120: 4 unresolved externals

How to correct these errors?

And after executing

    gcc -I C:\Tcl\include -s -shared -o tcldemo.dll tcldemo.c C:\Tcl\bin\tcl84.dll

in Cygwin I could'n load tcldemo.dll, because of this error:

    couldn't load library "tcldemo.dll": this library or a dependent library couldn't be found in library path
    when executing 
    "load tcldemo.dll Tcldemo"

Why does it happen?

Thank you. Anton.

Peter Newman 22 March 2005: I can't help with the MS Visual Studio problems. But the couldnt load library "tcldemo.dll" should be easy to fix. It just means that tcldemo.dll isn't where wish or tclsh can find it. See the library and tclvars manpages - for details of where Tcl searches for things. You'll have to EITHER; copy tcldemo.dll to one of the directorys Tcl searches for loadable stuff in, OR; add the directory tcldemo.dll is in, to the appropriate Tcl variable. See the library and tclvars manpages - and in particular, variables like; tcl_library, auto_path, env(TCL_LIBRARY) and env(TCLLIBPATH).

Erik Leunissen 7 Jan 2006 In my experience, the error message "couldn't load library XXXX: this library or a dependent library couldn't be found in library path" is not as easy to fix as suggested above. The environment variables TCL_LIBRARY and TCLLIBPATH do not relate to the [load] command, but rather to [package require]. Therefore, their setting won't affect this problem. The problem is that the message doesn't say which library couldn't be found. The web page at:

    http://flashexperiments.insh-allah.com/ApacheError126.html

gives advice about how to find out which library that is. Using the File Monitor tool mentioned in that page, I have been succesful in resolving such a problem.


In the Tcldemo example when I added:

Tcl_CreateObjCommand(interp, "fact",wrap_fact, (ClientData) NULL,

                (Tcl_CmdDeleteProc *) NULL);

I get the following error:

 --------------------Configuration: new_test - Win32 Debug--------------------
 Compiling...
 tcldemo.cpp
 C:\new_test\tcldemo.cpp(45) : error C2664: 'struct Tcl_Command_ *(struct Tcl_Interp *,const char *,int  (__cdecl *)(void *,struct Tcl_Interp *,int,struct Tcl_Obj *const * ),void *,void (__cdecl *)(void *))'  : cannot convert parameter 3 from '' to 'int (__cdecl *)(void *,struct Tcl_Interp *,int,struct Tcl_Obj *const * )'
         None of the functions with this name in scope match the target type
 Error executing cl.exe.

 new_test.dll - 1 error(s), 0 warning(s)

Appreciate any insights/help. Thanks. RGM

PT This error is the compiler informing you that your function has the wrong type. Parameter 3 is the C function that will implement your fact command and should be a Tcl_ObjCmdProc type. Looks to me like you maybe forgot to declare the prototype? Something like this should do:

 static Tcl_ObjCmdProc HelloObjCmdProc;

 int
 HelloObjCmdProc(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[])
 {
    Tcl_SetObjResult(interp, Tcl_NewStringObj("hello, world", -1));
    return TCL_OK;
 }

Link error: LNK2026: module unsafe for SAFESEH image

HaO 2017-01-30: When I linked the stubs library with a MS-VC2015 compiled C++ (actually CLR) 32 bit project, I got the link error for the release version: "error LNK2026: module unsafe for SAFESEH image". My "tclstub85.tcl" was compiled with MS-VC6.

Recompiling TCL as follows solved the issue:

  • In the start menu: Visual Studio 2015->Visual Studio Tools->Windows Desktop Command Prompts->VS2015 x86 Native Tools-Input prompt
  • Go to the win folder of the tcl source distribution: cd c:\test\tcl8.5.19\win
  • Start compilation: nmake -f Makefile.vc release OPTS=threads
  • Install the files: nmake -f makefile.vc install INSTALLDIR=c:\test\tcl8519_vc15
  • Now use C:\test\tcl8519_vc15\lib\tclstub85.lib