Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a way to call go code from Microsoft Visual Studio C++?

Is there a way to call go code from Microsoft Visual Studio C++?

Please Do Not close my question arbitrarily.

This link How to build a library with cgo using visual studio compiler? does not answer my question. It doesn't give a workable solution.

It seems cgo can only be compiled with mingw gcc at this time. https://github.com/golang/go/issues/49080#issuecomment-947865866

So, it seems this problem is unsolvable at this time.

Because mingw c header files are not completely the same as Windows SDK, there will always be some code that won't work.

This is like a "to be" or "not to be" problem.

If you want to call go code from c++, use mingw gcc to compile all your code.

you can't compile cgo with mingw and then use it in Visual Stdudio C++.(Maybe it will work, but there will always be some code that won't work)

All I can found is compile cgo with mingw gcc like this link https://gist.github.com/geraldstanje/4624ac47eb3dec5b8bbe12d4714ed330

But this method has a problem.

If you link the .a library with normal visual c++ code, it faied because .a library contains symbol that visual c++ doesn't know

clang -w *.cpp v2pn.a
LINK : warning LNK4217: symbol '__acrt_iob_func' defined in 'libucrt.lib(_file.obj)' is imported by 'v2pn.a(000005.o)' in function '_cgo_preinit_init'
LINK : warning LNK4286: symbol '__acrt_iob_func' defined in 'libucrt.lib(_file.obj)' is imported by 'v2pn.a(000006.o)'
LINK : warning LNK4217: symbol '_errno' defined in 'libucrt.lib(errno.obj)' is imported by 'v2pn.a(000005.o)' in function '_cgo_beginthread'
v2pn.a(000005.o) : error LNK2019: unresolved external symbol __imp__beginthread referenced in function _cgo_beginthread
v2pn.a(000005.o) : error LNK2019: unresolved external symbol __mingw_vfprintf referenced in function fprintf
v2pn.a(000006.o) : error LNK2001: unresolved external symbol __mingw_vfprintf
a.exe : fatal error LNK1120: 2 unresolved externals
clang: error: linker command failed with exit code 1120 (use -v to see invocation)

If you link the .a library with visual c++ code with mingw gcc,

Well, there will be much more problems. Because the mingw headers are not completely synchronous with windows SDK headers, there will be a lot errors.

Like this one

gcc.exe *.cpp v2pn.a
v2pn.cpp: In function 'void set_dns_by_guid(PCHAR)':
v2pn.cpp:48:5: error: 'DNS_INTERFACE_SETTINGS' was not declared in this scope
   48 |     DNS_INTERFACE_SETTINGS settings = {DNS_INTERFACE_SETTINGS_VERSION1};
      |     ^~~~~~~~~~~~~~~~~~~~~~
v2pn.cpp:49:5: error: 'settings' was not declared in this scope
   49 |     settings.Flags = 0x0002;
      |     ^~~~~~~~
v2pn.cpp:51:17: error: 'SetInterfaceDnsSettings' was not declared in this scope
   51 |     DWORD ret = SetInterfaceDnsSettings(interfaceGUID, &settings);
      |                 ^~~~~~~~~~~~~~~~~~~~~~~

This is because the mingw netioapi.h header file is different from the Windows SDK netioapi.h.

And the Windows SDK netioapi.h header file contains the DNS_INTERFACE_SETTINGS declaration, but the mingw netioapi.h header file doesn't.

like image 584
Gary Allen Avatar asked Sep 16 '25 05:09

Gary Allen


2 Answers

I finally successfully built both static and shared libraries with MSVC.

  1. First, you need Mingw-w64 for go build. I would recommend WinLibs port. I have tested with GCC 13.2.0 (with MCF threads) + MinGW-w64 11.0.1 (UCRT) - release 2. Check your $env:PATH to make sure there is no other gcc.exe in your path: where.exe gcc.

  2. Then you can use go build to build your go code.

  3. To enable multithread support, use /MD flag with cl.exe. That's why you got error unresolved external symbol __imp__beginthread.

    If you using WinLibs's Mingw-w64, you will see error unresolved external symbol vfprintf instead of __mingw_vfprintf. That's because you need the library legacy_stdio_definitions.lib.

    But cgo still unable to initialize successfully. You also need some magic code.

    #ifdef _MSC_VER
    #ifdef __cplusplus
    extern "C"
    {
    #endif
    
        __pragma(comment(lib, "legacy_stdio_definitions.lib"));
    
        void _rt0_amd64_windows_lib();
    
        __pragma(section(".CRT$XCU", read));
        __declspec(allocate(".CRT$XCU")) void (*init_lib)() = _rt0_amd64_windows_lib;
    
        __pragma(comment(linker, "/include:init_lib"));
    
    #ifdef __cplusplus
    }
    #endif
    #endif
    

    Add this code to your C/C++ project (do not forget to remove extern "C" if the code is pure C). Special thanks to ks75vl. Seriously, I have no idea what this code does. But it does work.

  4. You can successfully build your C/C++ project with MSVC now.

  5. For dynamic-link library (.dll), you also need a .def file to export your functions. For example:

    EXPORTS
        _rt0_amd64_windows_lib
        MyFunction
    

    And a simple C file for compiling dll:

    #include "mylib.h"
    
    __pragma(comment(lib, "legacy_stdio_definitions.lib"));
    

For detailed commands, here is a example repo. I have written all the commands in readme and pretty simple example code with C, C++, Objective-C, and Swift. Yes, it also works with Clang on macOS. Just check the readme and src/cgo folder.

like image 162
Jat Avatar answered Sep 18 '25 18:09

Jat


Like you said cgo doesn't support MSVC C++ libraries to be linked. Even if you try to expose pure C-like API's and try to link mostly likely you will fall into the name mangling problem. One solution that will work is to write a C wrapper around the C++ APIs that needs to be exposed and use proper extern prefixes. This will get you out of the C++ name mangling problem and cgo does not differentiate which compiler a C library is compiled from.

As an example, API.cpp

class CppApi {
  int Run(int arg1);
}

CAPI.h

#ifdef API_EXPORTS
#define RUN_API __declspec(dllexport)
#else
#define RUN_API __declspec(dllimport)
#endif

#ifdef __cplusplus
extern "C" {
#endif
  RUN_API void* GetAPI();
  RUN_API int Run(void* obj, int arg1);
#ifdef __cplusplus
}
#endif

CAPI.cpp

void* GetAPI() {
  return (void*)(new CppApi());
}
int Run(void* obj, int arg1) {
  CppApi cppObj = CppApi*)obj;
  return cppObj->Run(arg1);
}

Note that this solution only works if you're willing to expose pure C functions without any C++ types or any Microsoft's types.

like image 22
Niranjan M Avatar answered Sep 18 '25 18:09

Niranjan M