Version 40 of Building Tcl DLL's for Windows

Updated 2005-08-25 15:22:15

Note PT 25-Aug-2005: Some of this information is getting old and the page could do with an update. There is the sampleextension package available via CVS from sourceforge and 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!

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 http://sourceforge.net/projects/mingw for the releases.

  • 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.41 2005-08-26 06:00:23 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" from 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).


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;
 }

Category Porting