透过汇编另眼看世界之DLL导出函数调用

Dependency Walker深度指南:解决DLL依赖与程序分析 动态链接库(DLL)是Windows程序模块化设计的核心机制,它允许代码复用和资源共享。其原理在于程序运行时通过导入表动态加载所需函数,这提升了系统效率,但也带来了依赖管理的复杂性。理解DLL依赖关系对于确保软件兼容性、排查启动故障至关重要,尤其在软件部署、逆向工程和系统维护等场景中。本文聚焦于经典工具Dependency Walker,它通过静态分析和动态剖析,能清晰展示模块间的调用链,精准定位“DLL丢失”或“函数未找到”等常见问题。工具的热门搜索如“dependency walker下载”和“depe 阅读详情

前言:我一直对DLL技术充满好奇,一方面是因为我对DLL的导入/导出机制还不是特别的了解,另一面是因为我发现:DLL技术在Windows平台下占有重要的地位,几乎所有的Win32 API都是以导出函数的形式存放于不同的DLL文件中,在DLL方面的学习是任何一个想深入研究Windows内部机制的Windows程序员都不可能回避的事实。我在查阅了大量的文章后,对DLL技术有了一定的了解,所以我写了这篇文章来总结和整理我的思路,也为以后深入的学习提供宝贵的资料。

对于如何制作DLL,网上有很多资料,我这里就不多罗嗦了。现在假设我们已经成功生成了一个Win32 DLL,这个DLL的名字是DllInDepth.dll,它只导出了一个简单的C++函数:

__declspec(dllexport) void foobar(int iValue) {
    printf(
"The value is %d.",iValue);
}

接下来我建立了一个Win32 Console Application。我想把这个应用程序(TestApp)作为客户程序,使用DLL导出的函数。首先我尝试在代码中直接使用这个函数:
int _tmain(int argc, _TCHAR* argv[]) {
    foobar(
5);
    
return 0;
}

编译器不高兴了,给出了这样的错误提示:
error C2065: “foobar” : 未声明的标识符

哦,编译器不知道符号"foobar"是什么东东,当然会报错了。这个好办,我就在TestApp程序中引用声明了foobar函数的头文件:
#include  " DllInDepth.h "
int  _tmain( int  argc, _TCHAR *  argv[]) {
    foobar(
5 );
    
return   0 ;
}

这下编译器是满意了,但是连接器又不高兴了,连接器报错:
error LNK2019: 无法解析的外部符号  " __declspec(dllimport) void __cdecl foobar(int) "  (__imp_ ? foobar@@YAXH@Z)

仔细想想,连接器不高兴是有道理的。连接器的主要任务就是将处于不同编译单元(Compilation Unit)的汇编代码"组合/加工"成一个可执行文件,找不到目标文件,连接器拿什么来"加工"呀。这个也好办,在编译DLL的时候,不是生成了一个导出库么?就让连接器连接这个库就可以了。这下问题都解决了,TestApp也成功生成了,怀着激动的心情迫不及待的运行TestApp,一个对话框和"嘣"的一声给了我当头一棒:无法找到需要的DLL

哦,应用程序在装载所需要的DLL时,会按照一定的顺序搜寻指定的目录来寻找需要的DLL。如果搜寻过程结束了也没有找到需要的DLL,应用程序就会停止运行。这个也好办,将DLLDllInDepth.dll复制到TestApp所在的目录不就可以了。

到了现在,TestApp总算能够正确的运行起来了。从以上的"挫折"可以看出,一个DLL的正确运行,需要三个"职能部门"的通力合作:
1.编译器。只有正确引用了相应的头文件,编译器才能正确编译。
2.连接器。只有正确连接了相应的导出库,连接器才能正确连接。
3.装载器。只有正确设置了DLL的路径,当需要它的时候它才能正确被装载到内存中。

接下来我们将深入DLL的编译和连接,了解更多实隐藏在编译器和连接器背后的东西。这次我们使用的"利器"仍然是汇编代码,希望能从汇编代码中找到一些"蛛丝马迹"。下面是TestApp中关键的代码和相应的汇编代码:
; foobar( 5 );
mov    esi, esp
push    
5
call    DWORD PTR __imp_
? foobar@@YAXH@Z
add    esp, 
4
cmp    esi, esp
call    __RTC_CheckEsp


除了看到奇怪的符号"__imp_?foobar@@YAXH@Z",我们并没有看到太多"新鲜"的东西,不过这里有两点内容引起了我的思考:
1."__imp_?foobar@@YAXH@Z"应该是foobar函数的外部引用,如果将这个符号拆成两个部分:"__imp_"和"?foobar@@YAXH@Z",对于"?foobar@@YAXH@Z"我们并不陌生,它是被C++编译器修饰后的函数名引用。这个符号经过"反修饰"后的结果是:

void  __cdecl foobar( int );

如果拿这个和foobar函数的声明进行比较:
__declspec(dllimport)  void  foobar( int  iValue);

这样就不难想像,"__imp_"应该是编译器看到"__declspec(dllimport)"后特殊处理的结果。

2.我们在前面提到,如果要将连接器正确的连接,我们需要给连接器提供导入库。连接器连接导入库或者静态库最主要的目的就是解析(resolution)外部引用。而导入库和静态库先天上的差别,又决定了连接器在处理他们的过程中需要区别对待。静态库可以看作是集中存放许多目标文件(.obj)的仓库,它里面存放的都是"货真价实"的经过编译器”处理“后的汇编代码。当连接器连接静态库的时候,连接器会将相应的代码拷贝到应用程序的代码段中,任何使用到这部分代码的引用被具体的代码所替换。而对于导入库,它并没有存放"货真价实"的代码,具体的代码存放在DLL中,所以连接器并不能将相应的代码拷贝到应用程序的代码段中,而只能提供一种"间接使用"方式,这种"间接使用"的方式首先应该将连接器满足,最重要的是要让应用程序在运行时能够获得他们需要的代码。分析到这里,我越来越糊涂了,这后面到底隐藏了什么秘密?

对于这一部分的内容,我并没有完整的资料,强烈的好奇心驱使我在博客,杂志,论坛,MSDN中寻找各种各样的"蛛丝马迹",当我试图将这些"蛛丝马迹"整理并将他们串联起来的时候,一个相对模糊的轮廓浮现在我的脑海里。我已略微能看到胜利的曙光了。

以上的代码是经过编译器处理后的汇编代码,当连接器连接导入库的时候,所以的外部连接将会被解析。那经过连接后的汇编代码是什么样子的呢?这时候,我们就需要使用VS2002提供的"反汇编"功能。我将TestApp在调试状态下运行起来,当进行到"foobar(5);"这一句的时候,我选择"转到反汇编"进行运行时的汇编分析。这时我又获得了这样的汇编代码:
    foobar( 5 );
00411A1E  mov         esi,esp
00411A20  push        
5    
00411A22  call        dword ptr [__imp_foobar (42A1BCh)]
00411A28  add         esp,
4
00411A2B  cmp         esi,esp
00411A2D  call        @ILT
+ 920 (__RTC_CheckEsp) (41139Dh) 

看到这个反汇编,再比较一下前面的编译器生成的汇编代码,我一下懵了。请注意这两次函数调用:
call    DWORD PTR __imp_ ? foobar@@YAXH@Z

00411A22  call        dword ptr [__imp_foobar (42A1BCh)]

在前一此函数调用中,__imp_?foobar@@YAXH@Z扮演的是函数名的角色,表示的是函数的"绝对地址"。而在后一次中,__imp_foobar却扮演着函数指针的角色:先找到__imp_foobar(它位于0x42A1BCH的位置),然后取它的值,作为函数的地址,然后转而调用那个函数。虽然这段代码使用陷入了更深的迷糊,但是我也从中找到一些细小的线索。我们可以看到,__imp_foobar在内存中位于0x0042A1BCH的位置,而我们再观察"call dword ptr [__imp_foobar (42A1BCh)]"这段代码所处的位置,发现它位于0x00411A22H的位置,我们就会意识到他们具有相同的内存段,再联想到模块的"基地址",我们就可以大胆猜测,__imp_foobar应该是TestApp的PE文件格式中的导入表的一项,而它的值应该是被导入的函数的地址。喔!"柳暗花明又一村"呀。

这里真正使我迷惑的是:连接器如果将函数的直接调用变成函数的间接调用? 我们知道连接器是不能修改编译器生成的结果,连接器只能解析外部引用符号,难道是连接器在解析外部引用符号的时候做了些"手脚"? 幸运的是,我找到了一篇文章,其中正好涉及到这方面的内容:
  "The compiler would generate a normal call instruction, leaving the linker to resolve the external. The linker then sees that the external is really an imported function, and, uh-oh, the direct call needs to be converted to an indirect call. But the linker can't rewrite the code generated by the compiler. What's a linker to do?
  The solution is to insert another level of indirection. (Warning: The information below is not literally true, but it's "true enough". We'll dig into the finer details later in this series.)
  For each exported function in an import library, two external symbols are generated. The first is for the entry in the imported functions table, which takes the name __imp__FunctionName. Of course, the naive compiler doesn't know about this fancy __imp__ prefix. It merely generates the code for the instruction call FunctionName and expects the linker to produce a resolution.
  That's what the second symbol is for. The second symbol is the longed-for FunctionName, a one-line function that consists merely of a jmp [__imp__FunctionName] instruction. This tiny stub of a function satisfies the external reference and in turn generates an external reference to __imp__FunctionName, which is resolved by the same import library to an entry in the imported function table.
  When the module is loaded, then, the import is resolved to a function pointer and stored in __imp__FunctionName, and when the compiler-generated code calls the FunctionName function, it calls the stub which trampolines (via the indirect call) to the real function entry point in the destination DLL.
  Note that with a naive compiler, if your code tries to take the address of an imported function, it gets the address of the FunctionName stub, since a naive compiler simply asks for the address of the FunctionName symbol, unaware that it's really coming from an import library."

这篇文章的作者是一名微软的资深工程师,应该具有很高的可靠性。老实说,这篇文章我看的不是太明白,不过从字面上来看,具体的过程应该是这样的:
1.当编译器看到"foobar(5)"的时候,生成这样的代码:
    call  ? foobar@@YAXH@Z

2.在连接阶段,外部引用?foobar@@YAXH@Z被解析成一个简单的存根函数:
    jmp [__imp_ ? foobar@@YAXH@Z]

3.在DLL被装载的时候,DLL导出函数的地址将被填充到应用程序的导入表(Import Table)中。

当我用这个说法和本文的汇编代码进行比对的时候,发现本文中编译器看到"foobar(5)"的时候,却生成这样的代码:
    call __imp_ ? foobar@@YAXH@Z

仔细想想,是不是"__declspec(dllimport)"在"捣鬼"?如果我把它从导出函数声明处删掉,会是什么结果呢?当我这样做了以后,生成的结果就完全和文章中的分析完全吻合。我在网上找到的一篇文章正好印证了我的猜想:
  "Let's examine what the call to an imported API looks like. There are two cases to consider: the efficient way and inefficient way. In the best case, a call to an imported API looks like this:
CALL DWORD PTR [0x00405030]
  If you're not familiar with x86 assembly language, this is a call through a function pointer. Whatever DWORD-sized value is at 0x405030 is where the CALL instruction will send control. In the previous example, address 0x405030 lies within the IAT.
The less efficient call to an imported API looks like this:
CALL 0x0040100C
•••
0x0040100C:
JMP DWORD PTR [0x00405030]
   In this situation, the CALL transfers control to a small stub. The stub is a JMP to the address whose value is at 0x405030. Again, remember that 0x405030 is an entry within the IAT. In a nutshell, the less efficient imported API call uses five bytes of additional code, and takes longer to execute because of the extra JMP.
You're probably wondering why the less efficient method would ever be used. There's a good explanation. Left to its own devices, the compiler can't distinguish between imported API calls and ordinary functions within the same module. As such, the compiler emits a CALL instruction of the form
CALL XXXXXXXX
where XXXXXXXX is an actual code address that will be filled in by the linker later. Note that this last CALL instruction isn't through a function pointer. Rather, it's an actual code address. To keep the cosmic karma in balance, the linker needs to have a chunk of code to substitute for XXXXXXXX. The simplest way to do this is to make the call point to a JMP stub, like you just saw.
Where does the JMP stub come from? Surprisingly, it comes from the import library for the imported function. If you were to examine an import library, and examine the code associated with the imported API name, you'd see that it's a JMP stub like the one just shown. What this means is that by default, in the absence of any intervention, imported API calls will use the less efficient form.
Logically, the next question to ask is how to get the optimized form. The answer comes in the form of a hint you give to the compiler. The __declspec(dllimport) function modifier tells the compiler that the function resides in another DLL and that the compiler should generate this instruction
CALL DWORD PTR [XXXXXXXX]
rather than this one:
CALL XXXXXXXX
In addition, the compiler emits information telling the linker to resolve the function pointer portion of the instruction to a symbol named __imp_functionname. For instance, if you were calling MyFunction, the symbol name would be __imp_MyFunction. Looking in an import library, you'll see that in addition to the regular symbol name, there's also a symbol with the __imp__ prefix on it. This __imp__ symbol resolves directly to the IAT entry, rather than to the JMP stub.
So what does this mean in your everyday life? If you're writing exported functions and providing a .H file for them, remember to use the __declspec(dllimport) modifier with the function:
__declspec(dllimport) void Foo(void);
   If you look at the Windows system header files, you'll find that they use __declspec(dllimport) for the Windows APIs. It's not easy to see this, but if you search for the DECLSPEC_IMPORT macro defined in WINNT.H, and which is used in files such as WinBase.H, you'll see how __declspec(dllimport) is prepended to the system API declarations."

这篇文章的内容正好解释了我发现的事实和我的疑惑:
1。如果导出函数的声明没有用
__declspec(dllimport) 修饰的话,编译器并不知道这个函数是由DLL导出的,所以编译器就把这个函数当作普通的外部引用来对待,产生一个外部引用的符号等着连接器解析。当连接器工作的时候,就会将导入库中的存根函数拷贝到应用程序的代码段中,并将外部引用解析成那个存根函数。
2。如果导出函数的声明用 __declspec(dllimport) 修饰的话,编译器就知道这个函数是DLL导出函数,就将这个函数调用直接编译成对IAT中对应项的调用,而在连接阶段,连接器对IAT中对应项的符号解析成一个函数,这种情况下就没有使用存根函数的必要了。

到了现在,可以说所有的谜底已经"大白于天下"了,可是我突发其想,想看看__imp_foobar到底存的是不是函数地址,还是在调试状态下,我打开观察内存的窗口,在里面输入"0x0042A1BCH",发现位于此内存下的值是:”7b 17 01 10 00“,由于一个指针占有4个字节,而且Window是小头排列(Little Endian),所以正确的值应该是:0x1001177BH。难道这就是函数地址?从这个内存值来看,它应该不位于TestApp所在的内存段,它应该是DLL模块中的某个部分。当我继续查看这个内存是什么内容的时候,我却得到了这样的结果:
@ILT + 1910 ( ? foobar@@YAXH@Z):
1001177B  jmp         foobar (10011F80h) 

这里只是一个简单的跳转指令,最终的秘密应该在0x10011F80H被揭开。我继续查看0x10011F80H的内容的时候,我很吃了一惊:
DLLINDEPTH_API  void  foobar( int  iValue) {
10011F80  push        ebp 
10011F81  mov         ebp,esp
10011F83  sub         esp,0C0h
10011F89  push        ebx 
.....


看我发现了什么!我终于找到了最终的函数调用的位置。直到现在,我才看到了导出函数的"庐山真面目"。应用程序的IAT中填充的并不是DLL中导出函数的真实地址,而是在处于DLL中的又一个"存根函数"的地址,而这个"存根函数"仅仅是一个简单的jmp指令跳转到DLL导出函数的入口处。为什么要这样处理却不得而知了。

参考文献:
1。强烈建议大家看看这位老兄的blog: http://blogs.msdn.com/oldnewthing/
这位老兄就是本文提到的那位微软的资深工程师,他的blog几乎每天都有更新,其中的内容包含很多Windows内部实现的内容,极具参考价值。其中他写了一系列的关于DLL导入/导出内容的文章,给了我很大的启发:
http://blogs.msdn.com/oldnewthing/archive/2006/07/27/680250.aspx

2。Matt Pietrek发表于MSDN专栏中的"Under The Hood"系列

也揭示了许多Windows内部的秘密,具有极高的参考价值:http://www.wheaty.net/Columns.htm
他的"Under The Hood"系列专栏中的一篇关于Linker的工作原理的文章给我很大的启发:
http://www.microsoft.com/msj/0498/hood0498.aspx

3。MSDN Magazine中的两篇好文章,内容涉及PE文件格式,作者依然是Matt Pietrek
http://msdn.microsoft.com/msdnmag/issues/02/02/PE/default.aspx
http://msdn.microsoft.com/msdnmag/issues/02/03/PE2/

历史:
11/19/2006 
v1.0
原文的第一个正式版

11/21/2006  v1.1
1。原文中对 __declspec(dllimport)这一块的理解还不是太清楚,本次修改添加了一篇文章中对这部分的解释,使所有的疑惑找到了答案。
2。添加了
Matt Pietrek的两篇关于PE文件格式的文章的引用。
 
Keil5汇编语言模拟仿真:从环境搭建到寄存器调试实战 本文详细介绍了Keil5汇编语言模拟仿真的全流程,从环境搭建到寄存器调试实战。通过Keil5的模拟仿真功能,开发者可以直观观察每条汇编指令对CPU状态的影响,特别适合ARM Cortex-M系列单片机的开发与教学。文章包含工程创建、设备选择、仿真器配置等实用技巧,并提供了完整的汇编案例与常见问题解决方案。 阅读详情

相关推荐

DLL互引用的代码(汇编y引用VC的DLL,C引用ASM的DLL)

DLL互引用的代码(汇编y引用VC的DLL,C引用ASM的DLL),一个VC编写的WIN32窗口程序引用汇编编写的DLL,并用地址做返回值。。。。。。。

使用Win32汇编开发一个dll并在C#中调用

使用RadASM,新建一个Dll Project; 下一步,默认; dll包含Def文件; 完成工程构建;都默认; 完成以后项目结构如下; asm代码; .386 .model flat, stdcall option casemap :none include windows.inc .data? dwCounter dd ? .code DllEntry proc

bcbobo21cn的专栏 1355

dll反汇编工具

dll反汇编工具可以将.NET程序集中的中间语言反编译成C#或者Visual Basic代码。除了能将IL转换为C#或Visual Basic以外,Reflector还能够提供程序集中类及其成员的概要信息、提供查看程序集中IL的能力以及提供对第三方插件的支持。

Win32汇编:实现远程DLL注入器

注入器的主要功能是将一个动态链接库(DLL)加载到目标进程的地址空间中,使其在目标进程中运行。汇编语言编写的Windows平台下的DLL注入器的原理是通过调用Windows API来将指定的DLL加载到目标进程的地址空间中,并执行DLL中的代码。

微软技术分享 8565

dll反汇编工具(ilasm、ildasm、sn)

dll反汇编工具(ilasm、ildasm、sn),内有64位和86位两个版本。

Blender学习笔记——制作向量场

#下载Blender - UE4 - FGA Vector Field Editor插件的压缩包,Github上的下载地址如下:https://github.com/isathar/Blender_UE4_VectorFieldEditor #下载并解压后将FGA VectorFields文件夹复制到C:\Program Files\Blender Foundation\Blender 2.83\2.83\scripts\addons目录下,此步骤可根据blender安装的位置自行调整,只要放入addon

wychao2020的博客 1725

win32汇编——dll远程注入

dll远程注入实现效果原理代码实现软件下载 实现效果 原理 程序运行的容器是进程,真正活动的是其中的线程。因此,改变程序流程的通常做法是改变线程 EIP 、创建新线程或修改目标进程内的某些代码,使其执行 LoadLibrary(Ex) 来加载目标 DLL CreateRemoteThead法 这是最经典的也是使用范围最广的方法,其基本思路是在目标进程中申请一块内存并向其中写入 DLL 路径,然后调用 CreateRemoteThread,在目标进程中创建一个线程。线程函数的地址就是 LoadLibrar

CarveStone的博客 975

铝合金外壳表面处理工艺全解析:从阳极氧化到瓷泳喷涂的7种方法对比

本文全面解析了铝合金外壳的7种主流表面处理工艺,包括阳极氧化、粉末喷涂、瓷泳喷涂等。通过对比其原理、性能、成本与应用场景,为产品设计师和工程师提供了从防护性、装饰性到功能性的立体选型框架,帮助您根据具体需求选择最佳工艺方案。

weixin_29278805的博客 743

c文件调用汇编中函数与汇编函数使用c中全局变量

环境:keil5 一、外部文件使用汇编中函数: 使用 EXPORT 关键字导出该汇编文件里面的函数,让外部文件可见。除了使用 EXPORT 导出外,还要在某个 C的头文件里面声明下这个函数,这样才可以在 C文件里面调用这个函数。 比如: ;在汇编文件中导出汇编函数 OSStartHighRdy、PendSV_Handler EXPORT OSStartHighRdy ; 该文件定义的函数 EXPORT PendSV_Handler

weixin_43689161的博客 1606

ASPICE标准快速掌握「1.1. 简介」

ASPICE是汽车软件过程改进能力模型(Automotive SPICE,Automotive - Software Process Improvement and Capability dEtermination)的缩写,是一种针对汽车软件开发过程的国际标准。该标准旨在帮助汽车制造商和供应商改进其软件开发过程,以确保其软件产品的质量和安全性。需要做什么为什么要做技术上的依赖性是什么。

雪云飞星的博客 2292

符合DO-178C和DO-331的机载软件研制体系

DO-178C的核心要求软件开发生命周期:DO-178C定义了软件开发的生命周期过程,包括需求获取、设计、实现、验证和确认等。它为机载软件开发提供了一套系统化的指导,确保在开发过程中考虑安全性和可靠性。安全等级(DAL):根据系统的重要性,DO-178C将软件划分为不同的开发保障等级(DAL A到E),DAL A的要求最为严格,适用于关键安全功能,而DAL E的要求较低,适用于非关键功能。DO-178C在非基于模型软件开发中的应用。

qq_41854911的博客 979

CTF逆向入门:从DLL分析与反汇编破解Flag实战

逆向工程是信息安全领域的核心技术之一,其核心原理在于通过分析程序的二进制代码、内存状态和运行行为,理解其内部逻辑与数据流,而无需依赖源代码。这项技术的价值在于漏洞挖掘、恶意软件分析、软件保护评估等场景,是安全研究员和CTF选手的必备技能。在CTF竞赛中,逆向题目常以可执行文件和动态链接库(DLL)为载体,要求选手通过静态分析与动态调试相结合的方式,定位关键校验函数并还原算法逻辑。本文以一道典型入门题为例,详细讲解如何运用IDA Pro、Ghidra等反汇编工具进行静态代码分析,并结合x64dbg进行动态调试

chuannaoxuan4674的博客 394

一文读懂 SAP Web Dispatcher 配置参考:让 Gateway、OData 与后端服务各走各路

SAP Web Dispatcher在Fiori项目中常被低估,实际上它作为企业系统的HTTP(s)入口,承担着流量分发和安全防护双重职责。该组件通过URL路径规则(如/sap/opu/odata/转发至Gateway系统)实现多系统路由,隐藏后端复杂性,提供统一访问入口。配置要点包括HTTPS参数设置、客户端证书转发、端口定义以及系统冲突处理策略。合理的配置能确保前端统一访问、网络边界清晰可控,同时降低跨团队协作成本。生产环境需根据实际网络架构调整官方示例配置。

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 156

windows核心编程以及其他的一些网站

http://technet.microsoft.com/zh-CN/sysinternals/bb896653 http://technet.microsoft.com/zh-CN/sysinternals/bb896657.aspx 自己写的C/C++编译器Doctors

zhangqinli的专栏 462

GhidraMCP:AI驱动的逆向工程革命——让LLM自主操作Ghidra进行自动化分析

(Ghidra Machine Control Protocol)应运而生,它通过将LLM与Ghidra(美国NSA开源逆向工具)结合,实现了AI驱动的自动化逆向工程流程。逆向工程是软件安全、漏洞分析和代码审计的核心环节,但传统方法依赖人工分析,耗时且易出错。未来,随着LLM技术的演进和逆向工程工具的优化,GhidraMCP有望成为安全研究人员和开发者的核心工具,推动逆向工程进入“AI辅助时代”。GhidraMCP通过LLM与Ghidra的深度整合,重新定义了逆向工程的自动化边界。

这里汇聚了前沿的技术分享与实用的开发技巧,带你探索从创意到企业的技术创业之路。 2115

从win系统远程使用服务器中的nsight system、nsight compute

本文介绍了如何在Windows系统中远程使用服务器上的Nsight System和Nsight Compute工具。主要内容包括:1)双系统同步安装相同版本的Nsight工具,配置环境变量;2)命令行生成profile文件或直接运行分析;3)Windows远程连接服务器的配置方法;4)解决权限问题的具体方案,如修改文件夹权限或调整NVIDIA驱动配置。文章提供了详细的安装步骤和常见问题解决方案,适用于需要进行GPU性能分析和调试的开发人员。

Justinyh的博客 401

qt编译c语言dll,QT编写DLL给外部程序调用,提供VC/C#/C调用示例(含事件)

最近这阵子,接了个私活,封装一个开发包俗称的SDK给客户调用,查阅了不少人家的SDK,绝大部分用VC编写,并且VC6.0居多,估计也是为了兼容大量的XP用户及IE浏览器,XP自带了VC6.0运行库,因此无需兼带其余DLL便可运行。也发现有些用汇编和C封装的,那估计是高手才能编写啊!那怎么办呢?眼看一星期的交货期要到了,因为本人历来没有写过SDK开发包,内心那个着急啊,赶忙想出了三种处理方案。编程方...

weixin_31411313的博客 1387

基于YOLOv11的工业装配完整性智能检测系统

在现代工业生产中,产品质量控制是确保企业竞争力的关键环节。其中,装配完整性检查——即检测产品是否安装了所有应有的部件,是质量检测中的重要一环。传统的人工检测方法存在效率低、易疲劳、主观性强等问题。本文提出了一种基于深度学习的目标检测算法YOLOv11的工业装配完整性智能检测系统,通过计算机视觉技术自动识别和验证产品装配的完整性。本文将详细阐述从数据准备、模型训练到系统部署的完整流程,提供可执行的代码实现,并介绍一个适用于该任务的参考数据集。

YOLO 551

RevokeMsgPatcher 防撤回补丁完全指南:3 分钟安装,撤回消息一眼看穿

> 概要:RevokeMsgPatcher 是一款完全免费开源的消息防撤回补丁工具,专为 Windows 平台的微信、QQ、TIM 而设计,并附带实用的微信多开能力。本文从一次真实的"撤回事故"讲起,用生活化的收发室类比拆解防撤回背后的字节码修改原理,再给出 3 分钟完成安装的逐步操作与常见问题避坑清单,帮你彻底告别"消息已撤回"的遗憾。 ## 一、从一条被撤回的消息说起 周五下午 13:59

gitblog_00140的博客 190

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

上一篇: VS2005的程序文件分发问题及解决
下一篇: 基于S3C44B0微处理器的uClinux内核引导剖析
stylehack
博客等级 码龄19年 0粉丝 9原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值