在包含线程本地存储(TLS)回调的非托管DLL上使用DllImport时发生异常

本文关键字:DLL DllImport 异常 线程 包含 存储 回调 TLS | 更新日期: 2023-09-27 17:59:39

我有一个.NET应用程序,它加载一个包含多个入口点的非托管DLL(C++)。设置运行良好,我可以使用.NET中入口点返回的信息(即字符串)。但是,只要链接器包含依赖于线程的逻辑(例如_beginthread),就会向DLL中添加.tls部分,该部分可以如果运行dumpbin/exports,则程序抛出异常:Access违规和EntryPointNotFound异常。

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

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

上面的项目不包括对_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

如果我添加以下片段,它将TLS回调添加到DLL,那么一切都会中断:

#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现在包含一个预期的入口点,但在"摘要"的末尾还有一个.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.

垃圾箱没有显示DLL应该被错误格式化的任何迹象,但尽管如此,我还是得到了上面的异常。当我运行我的原始应用程序时,症状是相同的:

  • 如果.tls节可从dumpbin中获得,则程序将崩溃访问违规
  • 如果没有可用的.tls部分,则程序运行良好

我错过什么了吗?DllImport是否可能只处理不包含.tls节的DLL?我显而易见的解决方案是重构代码,这样我的入口点就不依赖于线程逻辑,这样链接器就不会将.tls部分添加到DLL中。但是,我仍然想知道为什么会发生这种情况。有什么想法吗?

使用的环境:

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

在包含线程本地存储(TLS)回调的非托管DLL上使用DllImport时发生异常

原来/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部分,一切都正常。