在包含线程局部存储(TLS)回调的非托管DLL上使用DllImport时出现异常

时间:2014-04-23 09:57:01

标签: c# .net pinvoke dllimport

我有一个.NET应用程序,它加载包含多个入口点的非托管DLL(C ++)。 设置工作正常,我可以使用.NET中的入口点返回的信息(即字符串)。 但是只要链接器包含依赖于线程的逻辑(例如_beginthread),就会将.tls部分添加到DLL中,这可以是 通过运行dumpbin / exports看到,程序抛出异常:访问冲突和EntryPointNotFound异常。

作为一个例子,我创建了两个简单的项目来演示我的案例:

  • TestApi:输出DLL的C ++项目
  • ConsoleApplication1:加载TestApi.dll并调用导出的DLL函数的C#.NET项目

上述项目不包括对_beginthread的调用,而是手动添加了线程局部存储(TLS)回调,这会导致运行时错误。 可以在此处下载示例项目:http://www.tempfiles.net/download/201404/343692/TestApi.html

ConsoleApplication1的C#代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

namespace ConsoleApplication1
{
    class Program
    {
        [DllImport("TestApi.dll", EntryPoint = "TestFunction", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
        [return: MarshalAs(UnmanagedType.LPStr)]
        private static extern string TestFunction();

        static void Main(string[] args)
        {
            try
            {
                string dllString = TestFunction();
                Console.WriteLine("String from DLL: " + dllString);
            }
            catch (System.Exception ex)
            {
                Console.WriteLine("Caught exception: " + ex);
            }
        }
    }
}

TestApi.dll的C ++代码:

#pragma unmanaged
#include <string>
#include <objbase.h>

extern "C" __declspec(dllexport) char* TestFunction()
{
    // Return string to managed side
    std::string cppString = "This is a string from the unmanaged DLL";
    size_t stringSize = strlen(cppString.c_str()) + sizeof(char);
    char* returnString = (char*)::CoTaskMemAlloc(stringSize);
    strcpy_s(returnString, stringSize, cppString.c_str());
    return returnString;
}

DLL包含1个预期的入口点(dumpbin / exports TestApi.dll):

ordinal hint    RVA         name
1       0       00001010    TestFunction = _TestFunction

Summary
1000    .data
7000    .rdata
1000    .reloc
1000    .rsrc
3000    .text

上面的.NET应用程序按预期工作,打印以下输出:

String from DLL: This is a string from the unmanaged DLL

如果我添加以下代码片段,它会向DLL添加TLS回调,一切都会中断:

#pragma comment(linker, "/INCLUDE:__tls_used")
#pragma comment(linker, "/INCLUDE:_tls_entry")
#pragma data_seg(".CRT$XLB" )

VOID NTAPI MyCallback(PVOID handle, DWORD reason, PVOID resv)
{
}

extern "C" PIMAGE_TLS_CALLBACK tls_entry = MyCallback;

DLL现在按预期包含1个入口点,但在“摘要”末尾包含.tls部分:

ordinal hint    RVA         name
1       0       00001010    TestFunction = _TestFunction

Summary
1000    .data
7000    .rdata
1000    .reloc
1000    .rsrc
3000    .text
1000    .tls

.NET应用程序现在输出:

Unhandled Exception:
   Unhandled Exception:
Segmentation fault

在Debug中运行应用程序时,我得到:

An unhandled exception of type 'System.AccessViolationException' occurred in ConsoleApplication1.exe
Additional information: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.

Dumpbin没有表明DLL应该是格式错误的,但是我得到了上面的例外。 当我运行我的原始应用程序时,症状是相同的:

  • 如果dumpbin中有.tls部分,则程序崩溃 访问违规行为
  • 如果没有.tls部分可用,程序运行正常

我在这里想念一下吗?是否有可能DllImport只处理不包含.tls部分的DLL? 我明显的解决方案是重构代码,以便我的入口点不依赖于线程逻辑,因此链接器不会将.tls部分添加到DLL。 但是,我仍然想知道为什么会这样。有什么想法吗?

使用的环境:

  • Windows 7 Enterprise SP1
  • Visual Studio 2012
  • .NET Framework 4

1 个答案:

答案 0 :(得分:0)

事实证明,/ clr标志使用DLL的PE格式做了一些讨厌的事情。如果启用了/ clr标志,则从Python 2.7加载DLL时会得到以下输出:

>>> import ctypes
>>> apidll = ctypes.cdll.LoadLibrary('TestApi.dll')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Python27\lib\ctypes\__init__.py", line 443, in LoadLibrary
    return self._dlltype(name)
  File "C:\Python27\lib\ctypes\__init__.py", line 365, in __init__
    self._handle = _dlopen(self._name, mode)
WindowsError: [Error 193] %1 is not a valid Win32 application

禁用/ clr标志会给我:

>>> import ctypes
>>> apidll = ctypes.cdll.LoadLibrary('TestApi.dll')
>>>

我的解决方案(正如@HansPassant在上面的评论中也提到的那样)是禁用/ clr标志,即使DLL包含.tls部分,一切都有效。