如何使我的VBA代码与64位Windows兼容?

46

我有一个在Excel 2007中开发的VBA应用程序,其中包含以下代码,允许访问Shell32.dll中的ShellExecute函数:

Private Declare Function ShellExecute Lib "shell32.dll" Alias "ShellExecuteA" (ByVal hwnd As Long, ByVal lpOperation As String, ByVal lpFile As String, ByVal lpParameters As String, ByVal lpDirectory As String, ByVal nShowCmd As Long) As Long

我最开始说:

显然,该应用程序无法在64位的Windows上编译(仍在使用32位的Office 2007)。我猜想这是因为Declare声明需要更新。

我读到Office 2010引入了一个新的VBA运行时(VB7),它具有一些新关键字,可以在Declare语句中使用以使其在64位Windows上正常工作。VB7还有新的预定义编译器常量,支持条件编译,其中将使用旧版或新版声明,取决于应用程序是否在32位或64位Windows上运行。

但是,由于我被困在Office 2007中,我需要另一种解决方案。我的选择是什么?(如果可能的话,我真的不想发布2个单独的应用程序版本)。

但是,根据下面David的答案,我的假设是错误的,导致Declare语句不能工作的情况只有Office 2010 64位在Windows 64位上。因此,Office 2007不是问题。

7个回答

72

在我自己的工具上,我已经遇到了这个问题,在新的64位机器上使用Office 2010。

我所要做的就是更改像这样的代码行:

Private Declare Function ShellExecute Lib "shell32.dll" Alias "ShellExecuteA" _
    (ByVal hwnd As Long, ByVal lpOperation As String, ByVal lpFile As String, ByVal lpParameters As String, ByVal lpDirectory As String, ByVal nShowCmd As Long) As Long

变成这样:

#If VBA7 Then
    Private Declare PtrSafe Function ShellExecute Lib "shell32.dll" Alias "ShellExecuteA" _
        (ByVal hwnd As Long, ByVal lpOperation As String, ByVal lpFile As String, ByVal lpParameters As String, ByVal lpDirectory As String, ByVal nShowCmd As Long) As Long
#Else
    Private Declare Function ShellExecute Lib "shell32.dll" Alias "ShellExecuteA" _
        (ByVal hwnd As Long, ByVal lpOperation As String, ByVal lpFile As String, ByVal lpParameters As String, ByVal lpDirectory As String, ByVal nShowCmd As Long) As Long
#End If

当然,你需要确保所使用的库在两台机器上都可用,但是到目前为止我使用的所有库都没有问题。

请注意,在旧版VB6中,PtrSafe不是一个有效的命令,因此它将以红色出现,就好像你有一个编译错误一样,但它实际上永远不会给出错误,因为编译器将跳过if块的第一部分。

代码外观

使用以上代码的应用程序可以在Office 2003、2007和2010的32位和64位版本上编译和运行得完美无缺。


2
如果问题是64位的office,那么VBA7不是答案。您需要使用win64编译常量。VBA7仍然适用于Office 2010 32位。 - Anonymous Type
6
这些指示来自于MSDN有关VB7和PtrSafe声明的文档:http://msdn.microsoft.com/en-us/library/ee691831.aspx 。需要使用PtrSafe是因为使用了VB7,而不是在64位系统上使用Office。只是碰巧64位系统仅支持VB7,所以这一举措一箭双雕。 - Alain
6
只有在需要在32位和64位系统上加载两个不同的dll或使用具有不同签名的函数时,才需要使用#if Win64。如果只是为了使旧的电子表格在64位机器上工作并强制使用VB7,则不需要使用该指令。根据文档,32位Office 2010有意与所有遗留的VB6电子表格兼容,无需进行任何修改。 - Alain
4
@Alain 我知道这是几年前的事了,但在VBA7语句中,Long应该改为LongLong,或者最好使用LongPtr以确保32位和64位版本之间的兼容性。(来源:https://msdn.microsoft.com/zh-cn/library/office/gg264421(v=office.14).aspx) - eisb

6
我找到了这段代码(注意有些Long被改成了LongPtr):
Declare PtrSafe Function ShellExecute Lib "shell32.dll" _
Alias "ShellExecuteA" (ByVal hwnd As LongPtr, ByVal lpOperation As String, _
ByVal lpFile As String, ByVal lpParameters As String, ByVal lpDirectory As _
String, ByVal nShowCmd As Long) As LongPtr

来源:http://www.cadsharp.com/docs/Win32API_PtrSafe.txt

Win32 API PtrSafe是用于在64位版本的Microsoft Office中调用Windows API函数的一种方法。 在64位Office中,指针数据类型(如LongPtr)的大小不再是4个字节,而是8个字节。 因此,在调用Windows API时,需要使用PtrSafe关键字来确保指针参数正确传递。


5

Office 2007仅支持32位,因此没有问题。您只有在使用既有32位又有64位版本的Office 64位时才会遇到问题。

如果您只有Office 2007,无法支持使用64位Office 2010的用户。解决方案是升级。

如果您唯一拥有的DeclareShellExecute,那么一旦获取了64位Office,您将没有太多事情要做,但是当您无法运行您提供的程序时,支持用户实际上并不可行!想象一下他们报告错误时您会怎么做?


问题“64位”是由同事报告的,然后他离开了办公楼,因此详细信息并不清楚,我填错了空白部分。 - Gary McGill
所以,为了澄清...我的现有Declare语句将在32位和64位Windows上的Office 2007中工作?并且将在32位Windows上的Office 2010中工作?但是对于64位Windows上的Office 2010,我需要使用新的VB7功能-这意味着我需要一个针对2010的特定版本的应用程序?难道没有一种向后兼容的方式来支持所有版本(就像以前的Office版本总是这样)吗? - Gary McGill
你应该能够拥有单一版本的代码,但你真正想要测试它自己。 - David Heffernan
感谢您关于如何测试和支持应用程序的建议。我认为我真正需要的是有关如何发布问题(或者更确切地说,什么时候不要发布问题!)的建议。 - Gary McGill
好的,你可以更新和更改问题,但这只会让这个答案看起来很愚蠢。在我看来,你最好提出一个新问题。我认为这个问题已经得到了回答。 - David Heffernan

4

使用PtrSafe并查看它在Excel 2010上的效果。

从书籍“Microsoft Excel 2010 Power Programming with VBA”中更正了一个错别字。

#If vba7 and win64 then
  declare ptrsafe function ....
#Else
  declare function ....
#End If

val(application.version)>12.0无法工作,因为Office 2010有32位和64位版本。


3

实际上,检查32位或64位平台的正确方法是使用Win64常量,在所有版本的VBA(16位、32位和64位版本)中都定义了该常量。

#If Win64 Then 
' Win64=true, Win32=true, Win16= false 
#ElseIf Win32 Then 
' Win32=true, Win16=false 
#Else 
' Win16=true 
#End If

来源:VBA编译器常量帮助


那是系统架构,不是 Excel。 - HaveAGuess
3
根据微软的文档,它确实是Office架构而不是系统架构。引用文章中的话:“Win64条件编译常量用于确定运行的Office版本(32位或64位)。 - wooobie

3
为了在所有版本的Office中编写代码,需要同时使用较新的VBA7和Win64条件编译器常量。 VBA7可以判断代码是否在VB编辑器的第7个版本中运行(这是Office 2010+中提供的VBA版本)。 Win64可以确定正在运行哪个版本(32位或64位)的Office。
#If VBA7 Then
'Code is running VBA7 (2010 or later).

     #If Win64 Then
     'Code is running in 64-bit version of Microsoft Office.
     #Else
     'Code is running in 32-bit version of Microsoft Office.
     #End If

#Else
'Code is running VBA6 (2007 or earlier).

#End If

请查看Microsoft支持文章以获取更多详细信息。

0

这个答案可能与上下文不符。我以为现在的VBA是在CLR上运行的,但事实并非如此。无论如何,这个回复可能对某人有用。或者也可能没有。


如果您在Office 2010 32位模式下运行,则与Office 2007相同。(“问题”是Office以64位模式运行)。这里重要的是执行上下文(VBA / CLR)的位数,而加载的VBA / CLR的位数取决于主机进程的位数。

在32/64位调用之间,最常见的问题是对“指针类型”使用longint(在CLR中为常量大小),而不是IntPtr(根据位数动态调整大小)。

ShellExecute函数的签名为:

HINSTANCE ShellExecute(
  __in_opt  HWND hwnd,
  __in_opt  LPCTSTR lpOperation,
  __in      LPCTSTR lpFile,
  __in_opt  LPCTSTR lpParameters,
  __in_opt  LPCTSTR lpDirectory,
  __in      INT nShowCmd
);

在这种情况下,HWND是重要的IntPtr(这是因为HWND是一个“HANDLE”,它是void* /“void指针”),而不是long。请参见pinvoke.net ShellExecute作为示例。(虽然pinvoke.net上有一些“解决方案”看起来可疑,但这是一个最初查找信息的好地方)。
祝编码愉快。

至于任何“新语法”,我一无所知。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接